Skip to 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.

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
<?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:

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
<?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:

yaml
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
<?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:

bash
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
<?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:

yaml
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 ​

Internal developer documentation for Menzzo systems.