Adding a Supplier Bundle
Supplier bundles encapsulate all integration logic for a specific vendor: API communication, entities, import commands, and queue processors. This guide walks through creating a new one from scratch.
:::info Reference
BigbuyBundle is the canonical template for supplier bundles. Study its structure before creating a new one:
src/BigbuyBundle/
:::
1. Create the Bundle Structure
src/NewSupplierBundle/
├── NewSupplierBundle.php # Bundle class
├── Controller/
│ └── NewSupplierController.php # HTTP endpoints (if needed)
├── Entity/
│ └── NewSupplierProduct.php # Supplier-specific entities
├── Repository/
│ └── NewSupplierProductRepository.php
├── Services/
│ ├── NewSupplierService.php # Main service
│ └── Queue/
│ └── NewSupplierImportProcessor.php # Queue processors
├── Command/
│ └── ImportNewSupplierProductsCommand.php
└── Resources/
└── config/
└── services.yml # Service definitions
Bundle Class
<?php
// src/NewSupplierBundle/NewSupplierBundle.php
namespace NewSupplierBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class NewSupplierBundle extends Bundle
{
}
2. Register in AppKernel
Add the bundle to app/AppKernel.php:
// app/AppKernel.php
public function registerBundles()
{
$bundles = [
// ... existing bundles
new NewSupplierBundle\NewSupplierBundle(),
];
return $bundles;
}
3. Create the API Client
If the supplier has an external API, create the client in src/CoreBundle/Api/ (shared clients go here) or within the bundle if it is purely supplier-specific:
<?php
// src/CoreBundle/Api/NewSupplierApi.php
namespace CoreBundle\Api;
class NewSupplierApi
{
private $baseUrl;
private $apiKey;
private $logger;
public function __construct(array $config, $logger)
{
$this->baseUrl = $config['base_url'];
$this->apiKey = $config['api_key'];
$this->logger = $logger;
}
public function getProducts(array $filters = [])
{
// API call to fetch supplier products
}
public function getStock()
{
// API call to fetch stock levels
}
public function submitOrder(array $orderData)
{
// API call to place an order with the supplier
}
}
Add credentials to app/config/parameters.yml.dist:
parameters:
# NewSupplier Integration
new_supplier_api_base_url: 'https://api.newsupplier.example.com'
new_supplier_api_key: 'your-key-here'
See the Adding an Integration guide for the full API client pattern.
4. Create Entities
Define entities for supplier-specific data that does not fit the core product model:
<?php
// src/NewSupplierBundle/Entity/NewSupplierProduct.php
namespace NewSupplierBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="NewSupplierBundle\Repository\NewSupplierProductRepository")
* @ORM\Table(name="new_supplier_product")
*/
class NewSupplierProduct
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $supplierReference;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $ean;
/**
* @ORM\Column(type="decimal", precision=10, scale=2)
*/
private $purchasePrice;
/**
* @ORM\Column(type="integer")
*/
private $stockQuantity;
/**
* @ORM\Column(type="datetime")
*/
private $lastSyncAt;
// Getters and setters...
}
After creating entities, generate the migration:
php bin/console doctrine:schema:update --dump-sql
Review the generated SQL carefully before applying. Never run --force in production without reviewing the output first.
5. Create Import Commands
Supplier bundles typically need import commands for product and stock synchronization:
<?php
// src/NewSupplierBundle/Command/ImportNewSupplierProductsCommand.php
namespace NewSupplierBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Command\LockableTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ImportNewSupplierProductsCommand extends ContainerAwareCommand
{
use LockableTrait;
protected function configure()
{
$this
->setName('newsupplier:import:products')
->setDescription('Import products from NewSupplier catalog');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!$this->lock()) {
$output->writeln('Command already running.');
return 0;
}
try {
$service = $this->getContainer()->get('new_supplier.service');
$result = $service->importProducts();
$output->writeln(sprintf('Imported: %d, Errors: %d',
$result['imported'], $result['errors']));
} finally {
$this->release();
}
return 0;
}
}
6. Wire Services
Register everything in src/NewSupplierBundle/Resources/config/services.yml:
services:
new_supplier.api:
class: CoreBundle\Api\NewSupplierApi
arguments:
- base_url: '%new_supplier_api_base_url%'
api_key: '%new_supplier_api_key%'
- '@logger'
new_supplier.service:
class: NewSupplierBundle\Services\NewSupplierService
arguments:
- '@new_supplier.api'
- '@doctrine.orm.entity_manager'
- '@logger'
new_supplier.command.import_products:
class: NewSupplierBundle\Command\ImportNewSupplierProductsCommand
tags:
- { name: console.command }
Checklist
- Bundle class created and registered in
AppKernel - API client created (in
CoreBundle/Api/or bundle) - Credentials added to
parameters.yml.dist - Entities created for supplier-specific data
- Schema migration generated and reviewed
- Import commands created with
LockableTrait - Services wired in
services.yml - Queue processors added for async tasks (see Adding a Queue Processor)
- Crontab entries added for scheduled imports (see Adding a Cronjob)
- Tests written for service and processor logic
See Also
- Architecture: Bundle Responsibilities for the bundle structure overview
- Adding an Integration for the API client and service pattern
- Adding a Queue Processor for async processing within the bundle
- Adding a Cronjob for scheduling import commands