Custom Choice Field Type - adamwojs/ezplatform-fieldtype-library GitHub Wiki

ezplatform-fieldtype-library provides abstract implementation of choice field type.

Example #1: Enumerated values

The following example shows how to build the U.S. State choice field type. Implementation assumes that U.S. State will be a Value Object and list of all available choices will be stored in-memory.

1. Create Value Object representing the state

Create the Value Object representing the U.S. State with two properties: the name and code of the state.

<?php

declare(strict_types=1);

namespace App\FieldType\State;

final class State
{
    /** @var string */
    private $name;

    /** @var string */
    private $code;

    public function __construct(string $name, string $code)
    {
        $this->name = $name;
        $this->code = $code;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getCode(): string
    {
        return $this->code;
    }

    public function __toString()
    {
        return $this->name;
    }
}

2. Create Field Type Definition

<?php

declare(strict_types=1);

namespace App\FieldType\State;

use AdamWojs\EzPlatformFieldTypeLibrary\Core\FieldType\AbstractChoice\Type as AbstractChoiceType;

final class Type extends AbstractChoiceType
{
    public function getFieldTypeIdentifier(): string
    {
        return 'state';
    }
}

3. Provide ChoiceProvider implementation

List of the U.S. States (represented as State Value Object) is pretty small so in this example we will keep it in the memory.

<?php

declare(strict_types=1);

namespace App\FieldType\State;

use AdamWojs\EzPlatformFieldTypeLibrary\API\FieldType\AbstractChoice\ChoiceProvider as ChoiceProviderInterface;
use AdamWojs\EzPlatformFieldTypeLibrary\API\FieldType\AbstractChoice\ChoiceCriteria;
use AdamWojs\EzPlatformFieldTypeLibrary\API\FieldType\AbstractChoice\ChoiceList;

final class ChoiceProvider implements ChoiceProviderInterface
{
    /** @var \App\FieldType\State\State[] */
    private $states = [];

    public function __construct()
    {
        // Initialize states list
        foreach ($this->getAvailableStates() as $code => $name) {
            $this->states[] = new State($name, $code);
        }
    }

    public function getChoiceList(ChoiceCriteria $criteria, ?int $offset = null, ?int $limit = null): ChoiceList
    {
        $states = $this->states;
        if ($criteria->hasValues()) {
            $states = array_filter($this->states, static function (State $state) use($criteria) {
                return in_array($state->getCode(), $criteria->getValues());
            });
        }

        return new ChoiceList($states, count($states));
    }

    public function getValueForChoice($choice): string
    {
        /** @var \App\FieldType\State\State $choice */
        return $choice->getCode();
    }

    public function getLabelForChoice($choice): string
    {
        /** @var \App\FieldType\State\State $choice */
        return $choice->getName();
    }

    private function getAvailableStates(): array
    {
        return [
            'AL' => 'Alabama',
            'AK' => 'Alaska',
            'AZ' => 'Arizona',
            'AR' => 'Arkansas',
            'CA' => 'California',
            'CO' => 'Colorado',
            'CT' => 'Connecticut',
            'DE' => 'Delaware',
            'FL' => 'Florida',
            'GA' => 'Georgia',
            'HI' => 'Hawaii',
            'ID' => 'Idaho',
            // ...    
        ];
    }
}

4. Configure services

services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    app.field_type.state:
        class: App\FieldType\State\Type
        arguments:
            $choiceProvider: '@app.field_type.state.choice_provider'
        tags:
            - { name: ezplatform.field_type, alias: state }

    app.field_type.state.choice_provider:
        class: App\FieldType\State\ChoiceProvider

    app.field_type.state.converter:
        class: AdamWojs\EzPlatformFieldTypeLibrary\Core\Persistence\Legacy\Converter\ChoiceConverter
        tags:
            - { name: ezplatform.field_type.legacy_storage.converter, alias: state }

    app.field_type.state.indexable:
        class: AdamWojs\EzPlatformFieldTypeLibrary\Core\FieldType\AbstractChoice\SearchField
        tags:
            - { name: ezplatform.field_type.indexable, alias: state }

    app.field_type.state.form_mapper.value:
        class: AdamWojs\EzPlatformFieldTypeLibrary\Core\FieldType\AbstractChoice\FormMapper\FieldValueFormMapper
        arguments:
            $choiceProvider: '@app.field_type.state.choice_provider'
        tags:
            - { name: ezplatform.field_type.form_mapper.value, fieldType: state }

    app.field_type.state.form_mapper.definition:
        class: AdamWojs\EzPlatformFieldTypeLibrary\Core\FieldType\AbstractChoice\FormMapper\FieldDefinitionFormMapper
        tags:
            - { name: ezplatform.field_type.form_mapper.definition, fieldType: state }

Example 2: Doctrine Entity

The following example shows how to build the Product Category choice field type.

1. Create Entity representing the product category

<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class ProductCategory
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }
}

2. Create Field Type Definition

<?php

declare(strict_types=1);

namespace App\FieldType\ProductCategoryChoice;

use AdamWojs\EzPlatformFieldTypeLibrary\Core\FieldType\AbstractChoice\Type as AbstractChoiceType;

final class Type extends AbstractChoiceType
{
    public function getFieldTypeIdentifier(): string
    {
        return 'productcategorychoice';
    }
}

3. Provide ChoiceProvider implementation

<?php

declare(strict_types=1);

namespace App\FieldType\ProductCategoryChoice;

use AdamWojs\EzPlatformFieldTypeLibrary\API\FieldType\AbstractChoice\ChoiceCriteria;
use AdamWojs\EzPlatformFieldTypeLibrary\API\FieldType\AbstractChoice\ChoiceList;
use AdamWojs\EzPlatformFieldTypeLibrary\API\FieldType\AbstractChoice\ChoiceProvider as ChoiceProviderInterface;
use App\Entity\ProductCategory;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;

final class ChoiceProvider implements ChoiceProviderInterface
{
    /** @var \Doctrine\ORM\EntityRepository */
    private $repository;

    public function __construct(EntityManagerInterface $em)
    {
        $this->repository = $em->getRepository(ProductCategory::class);
    }

    public function getChoiceList(ChoiceCriteria $criteria, ?int $offset = null, ?int $limit = null): ChoiceList
    {
        $totalCount = $this->countChoices($criteria);

        $choices = [];
        if ($totalCount !== 0) {
            $choices = $this->findChoices($criteria, $offset, $limit)->toArray();
        }

        return new ChoiceList($choices, $totalCount);
    }

    public function getValueForChoice($choice): string
    {
        /** @var \App\Entity\ProductCategory $choice */
        return $choice->getId();
    }

    public function getLabelForChoice($choice): string
    {
        /** @var \App\Entity\ProductCategory $choice */
        return $choice->getName();
    }

    private function findChoices(ChoiceCriteria $criteria, ?int $offset = null, ?int $limit = null): Collection
    {
        $qb = $this->createChoiceQueryBuilder($criteria, 'c');
        $qb->addOrderBy('c.name', 'DESC');
        $qb->setFirstResult($offset);
        $qb->setMaxResults($limit);

        return $qb->getQuery()->execute();
    }

    private function countChoices(ChoiceCriteria $criteria): int
    {
        $qb = $this->createChoiceQueryBuilder($criteria, 'c');
        $qb->select($qb->expr()->count('c.id'));

        return (int)$qb->getQuery()->getSingleScalarResult();
    }

    private function createChoiceQueryBuilder(ChoiceCriteria $criteria, string $alias = 'c'): QueryBuilder
    {
        $qb = $this->repository->createQueryBuilder($alias);

        if ($criteria->hasValues()) {
            $qb->andWhere($qb->expr()->in(
                $alias . '.id',
                $criteria->getValues()
            ));
        }

        if ($criteria->getSearchTerm() !== null) {
            $qb->andWhere($qb->expr()->like(
                $alias . '.name',
                $criteria->getSearchTerm() . '*'
            ));
        }

        return $qb;
    }
}

4. Configure services

services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false

    app.field_type.productcategorychoice:
        class: App\FieldType\ProductCategoryChoice\Type
        arguments:
            $choiceProvider: '@app.field_type.productcategorychoice.choice_provider'
        tags:
            - { name: ezplatform.field_type, alias: productcategorychoice }

    app.field_type.productcategorychoice.choice_provider:
        class: App\FieldType\ProductCategoryChoice\ChoiceProvider

    app.field_type.productcategorychoice.converter:
        class: AdamWojs\EzPlatformFieldTypeLibrary\Core\Persistence\Legacy\Converter\ChoiceConverter
        tags:
            - { name: ezplatform.field_type.legacy_storage.converter, alias: productcategorychoice }

    app.field_type.productcategorychoice.indexable:
        class: AdamWojs\EzPlatformFieldTypeLibrary\Core\FieldType\AbstractChoice\SearchField
        tags:
            - { name: ezplatform.field_type.indexable, alias: productcategorychoice }

    app.field_type.productcategorychoice.form_mapper.value:
        class: AdamWojs\EzPlatformFieldTypeLibrary\Core\FieldType\AbstractChoice\FormMapper\FieldValueFormMapper
        arguments:
            $choiceProvider: '@app.field_type.productcategorychoice.choice_provider'
        tags:
            - { name: ezplatform.field_type.form_mapper.value, fieldType: productcategorychoice }

    app.field_type.productcategorychoice.form_mapper.definition:
        class: AdamWojs\EzPlatformFieldTypeLibrary\Core\FieldType\AbstractChoice\FormMapper\FieldDefinitionFormMapper
        tags:
            - { name: ezplatform.field_type.form_mapper.definition, fieldType: productcategorychoice }

Build-in ChoiceProvider implementations

InMemoryChoiceProvider