Skip to main content

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
warning

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