Adding a new API provider

If you're using a headless commerce system that isn't integrated with Frontastic, you can extend your project to do this. That includes product, content, search, cart, wishlist, and account.

By implementing extensions and integrations in Frontastic, you can use the same interfaces and communication inside the implementation, and it can be used with any API type, for example, REST(ful), GraphQL, or even SOAP.

In this example, we'll add an extension for our products to ABOUT YOU.

  1. Open the project.yml file in the /config folder of your project
  2. Update the product configuration to the new system, including any access credentials
configuration:
    [...]
        product:
            engine: ABOUT YOU
            clientId: YOUR_CLIENT_ID
            projectKey: YOUR_PROJECT_KEY
    [...]
  1. Add the below to your services.xml in one of your project bundles (if there isn't a bundle yet, you can create one using bin/console frontastic:create:bundle ProductApi or see the bundles article for more information)
<service id="AboutYout\ProductApiBundle\Domain\ProductApiFactory"
    decorates="Frontastic\Common\ProductApiBundle\Domain\DefaultProductApiFactory">
    <argument type="service" id="AboutYou\ProductApiBundle\Domain\ProductApiFactory.inner"/>
</service>
  1. Create a file called ProductApiBundle/Domain/ProductApiFactory.php in the /src/php folder in your project and add the below code to the file, this integrates with our factory
namespace AboutYou\ProductApiBundle\Domain;

use Frontastic\Common\ProductApiBundle\Domain\ProductApiFactory as BaseProductApiFactory;
use Frontastic\Common\ProductApiBundle\Domain\ProductApi as BaseProductApi;
use Frontastic\Common\ReplicatorBundle\Domain\Project;

class ProductApiFactory implements BaseProductApiFactory
{
    private $aggregate;

    public function __construct(BaseProductApiFactory $aggregate)
    {
        $this->aggregate = $aggregate;
    }

    public function factor(Project $project): BaseProductApi
    {
        $productConfig = $project->getConfigurationSection('product');

        switch ($productConfig->engine) {
            case 'ABOUT YOU':
                return new ProductApi();

            default:
                return $this->aggregate->factor($project);
        }
    }
}
  1. Then you need to implement the API
    A product API must implement the corresponding interface and you can do whatever you want within it. To enable parallel fetching of streams you should always return promises for all data (where documented). Below is an example of what a minimal implementation could look like:
namespace AboutYou\ProductApiBundle\Domain;

use Frontastic\Common\ProductApiBundle\Domain\ProductApi as BaseProductApi;
use Frontastic\Common\ProductApiBundle\Domain\ProductApi\Query\CategoryQuery;
use Frontastic\Common\ProductApiBundle\Domain\ProductApi\Query\ProductQuery;
use Frontastic\Common\ProductApiBundle\Domain\ProductApi\Query\ProductTypeQuery;
use Frontastic\Common\ProductApiBundle\Domain\ProductApi\Result;
use Frontastic\Common\ProductApiBundle\Domain\Product;
use Frontastic\Common\ProductApiBundle\Domain\Variant;
use Frontastic\Common\ProductApiBundle\Domain\Category;
use Frontastic\Common\ProductApiBundle\Domain\ProductType;

use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\FulfilledPromise;

class ProductApi implements BaseProductApi
{
    /**
     * @param CategoryQuery $query
     * @return Category[]
     */
    public function getCategories(CategoryQuery $query): array
    {
        return [];
    }

    /**
     * @param ProductTypeQuery $query
     * @return ProductType[]
     */
    public function getProductTypes(ProductTypeQuery $query): array
    {
        return [];
    }

    /**
     * @param ProductQuery $query
     * @param string $mode One of the QUERY_* constants. Execute the query synchronously or asynchronously?
     * @return Product|PromiseInterface|null A product or null when the mode is sync and a promise if the mode is async.
     */
    public function getProduct(ProductQuery $query, string $mode = self::QUERY_SYNC): ?object
    {
        return new FulfilledPromise(
            new Product([
                'name' => 'My Dummy Test Product',
                'variants' => [
                    new Variant(),
                ],
            ])
        );
    }

    /**
     * @param ProductQuery $query
     * @param string $mode One of the QUERY_* constants. Execute the query synchronously or asynchronously?
     * @return Result|PromiseInterface A result when the mode is sync and a promise if the mode is async.
     */
    public function query(ProductQuery $query, string $mode = self::QUERY_SYNC): object
    {
        return new FulfilledPromise(
            new Result([
                'items' => [
                    new Product([
                        'name' => 'My Dmmy Test Product',
                        'variants' => [
                            new Variant(),
                        ],
                    ]),
                ],
            ])
        );
    }
    /**
     * Get *dangerous* inner client
     *
     * This method exists to enable you to use features which aren't part
     * of the abstraction layer yet.
     *
     * Be aware that any usage of this method might seriously impact backwards
     * compatibility and the future abstractions might differ a lot from the
     * vendor provided abstraction.
     *
     * Use this with care for features necessary in your customer and talk with
     * Frontastic about providing an abstraction.
     *
     * @return mixed
     */
    public function getDangerousInnerClient()
    {
        return null;
    }
}