PHP code example of maxkain / eav-bundle

1. Go to this page and download the library: Download maxkain/eav-bundle library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

maxkain / eav-bundle example snippets


namespace App\Entity\Product\Attribute;

use App\Repository\Product\Attribute\MyAttributeRepository;
use Doctrine\ORM\Mapping as ORM;
use Maxkain\EavBundle\Contracts\Entity\EavAttributeInterface;

#[ORM\Entity(repositoryClass: MyAttributeRepository::class)]
#[ORM\Table('product_my_attribute')]
class MyAttribute implements EavAttributeInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column]
    private string $name;

    public function __toString(): string
    {
        return $this->name;
    }
    
    // ...getters and setters
}

namespace App\Entity\Product\Attribute;

use App\Entity\Product\Product;
use App\Repository\Product\Attribute\MyEavRepository;
use Doctrine\ORM\Mapping as ORM;
use Maxkain\EavBundle\Contracts\Entity\EavAttributeInterface;
use Maxkain\EavBundle\Contracts\Entity\EavInterface;
use Maxkain\EavBundle\Contracts\Entity\EavEntityInterface;

#[ORM\Entity(repositoryClass: MyEavRepository::class)]
#[ORM\Table('product_my_eav')]
#[ORM\UniqueConstraint(fields: ['entity', 'attribute', 'value'])]
class MyEav implements EavInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\ManyToOne(Product::class, inversedBy: 'myEavs')]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    private Product $entity;

    #[ORM\ManyToOne(MyAttribute::class)]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    private MyAttribute $attribute;

    #[ORM\Column]
    private string $value;

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

namespace App\Entity\Product\Attribute;

use App\Repository\Product\Attribute\MyValueRepository;
use Doctrine\ORM\Mapping as ORM;
use Maxkain\EavBundle\Contracts\Entity\EavValueInterface;

#[ORM\Entity(repositoryClass: MyValueRepository::class)]
#[ORM\Table(name: 'product_my_attribute_value')]
#[ORM\UniqueConstraint(fields: ['attribute', 'title'])]
class MyValue implements EavValueInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\ManyToOne(MyAttribute::class, inversedBy: 'values')]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    private MyAttribute $attribute;

    #[ORM\Column]
    private string $title;

    public function __toString(): string
    {
        return $this->title;
    }
}

#[ORM\ManyToOne(MyValue::class)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private MyValue $value;

/**
 * @var Collection<MyValue>
 */
#[ORM\OneToMany(MyValue::class, mappedBy: 'attribute', cascade: ['persist'], orphanRemoval: true)]
private Collection $values;

/**
 * @var Collection<MyEav>
 */
#[Assert\Valid]
#[ORM\OneToMany(MyEav::class, 'entity', cascade: ['persist'], orphanRemoval: true)]
private Collection $myEavs;

public function __construct()
{
    $this->myEavs = new ArrayCollection();
}

public function getMyEavs(): Collection
{
    return $this->myEavs;
}

use Maxkain\EavBundle\Inverter\EavInverterInterface;
use Maxkain\EavBundle\Inverter\EavMultipleInputItem;
use Maxkain\EavBundle\Options\EavOptions;
//...

class MyService
{
    public function __construct(
        private EavInverterInterface $eavInverter,
    ) {
    }
    
    public myMethod()
    {
        // ...receive your $entity
        
        // then, define your data:
        $myItems = [
            new EavMultipleInputItem(
                attribute: 1, 
                values: ['test1', 'test2']
            ),
            new EavMultipleInputItem(
                attribute: 2,
                values: ['test3', 'test4']
            )
        ];
                
        // also, you can use arrays:
        $myItems = [
            [
                'attribute': 1, 
                'values': ['test1', 'test2']
            ],
            [
                'attribute': 2, 
                'values': ['test3', 'test4']
            ]
        ];
    
        // ...or pass value ids or `MyValue` entities, if you use enum values
        
        // set options:
        $options = new EavOptions(
            eavFqcn: MyEav::class,
            entityFqcn: Product::class,
            attributeFqcn: MyAttribute::class,
            valueFqcn: MyValue::class, // if you have enum value
            multiple: true
        );	
    
        //	and call the inverter:
        $eavInverter = $this->eavInverter;
        $eavInverter->invert($entity, $myItems, $entity->getMyEavs(), $options);
        if (!$eavInverter->isValid()) {
            $violations = $eavInverter->getViolations();
    
            // ...set the response with violations
            return $response;
        }
    
        $em->flush();
    
        // ...set the response
        return $response;
    }
}

new EavOptions(
    reversePropertyMapping: new ReversePropertyMapping(
        attribute: 'any_attribute_name',
        values: 'any_values_name'
    )
);

new EavOptions(
    propertyMapping: new PropertyMapping(
        entity: 'product',
        entityId: 'guid'
        // and others...
    )
);

new EavOptions(
    entityInputType: 'integer',
    attributeInputType 'integer',
    valueInputType: 'integer'
);

namespace App\Eav;

class ProductConfigurator implements EavConfiguratorInterface
{
    /**
     * @return array<int|string, EavOptionsInterface>
     */
    public function configure(): array
    {
        return [
            new EavOptions(
                eavFqcn: MyEav::class,
                entityFqcn: Product::class,
                attributeFqcn: MyAttribute::class,
                valueFqcn: MyValue::class, // if you have enum value
                multiple: true
            )
        ];
    }
}

use Maxkain\EavBundle\Options\EavOptionsRegistry

//...

public function __construct(
	private EavOptionsRegistry $optionsRegistry;
) {
    $options = $optionsRegistry->get(MyEav::class);
    
    // or pass the string as option parameter to inverter or converter
    $eavInverter->invert($entity, $myItems, $entity->getMyEavs(), MyEav::class);
}

namespace App\Entity\Product\Attribute;

use App\Entity\Product\Category;
use App\Repository\Product\Attribute\MyTagRepository;
use Doctrine\ORM\Mapping as ORM;
use Maxkain\EavBundle\Contracts\Entity\Tag\EavAttributeTagInterface;

#[ORM\Entity(repositoryClass: MyTagRepository::class)]
#[ORM\Table(name: 'product_my_attribute_tag')]
#[ORM\UniqueConstraint(fields: ['attribute', 'tag'])]
class MyTag implements EavAttributeTagInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\ManyToOne(MyAttribute::class, inversedBy: 'tags')]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    private MyAttribute $attribute;

    #[ORM\ManyToOne(Category::class)]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    private Category $tag;

    /**
     * @var bool Maybe you want to add any additional properties
     */
    #[ORM\Column]
    private bool $showInFilter = true;
}

/**
 * @var Collection<MyTag>
 */
#[ORM\OneToMany(MyTag::class, mappedBy: 'attribute', cascade: ['persist'], orphanRemoval: true)]
private Collection $tags;

#[ORM\Column]
private bool $forAllTags = false;

public function getEavTags(string $tagKey): iterable
{
    return match ($tagKey) {
        Category::class => isset($this->category) ? [$this->category] : [],
        default => []
    };
}

public function getEavTags(string $tagKey): iterable
{
    return match ($tagKey) {
        Category::class => $this->categories,
        default => []
    };
}

new EavOptions(
    // ...
    tagFqcn: Category::class,
    attributeTagFqcn: MyTag::class
    propertyMapping: new PropertyMapping( // If you have different name of your category field
    	entityTag: 'category', //for ManyToOne
    	entityTags: 'categories' //for ManyToMany
    	// and see others...
    )
),

use Doctrine\ORM\EntityManagerInterface;
use Maxkain\EavBundle\Bridge\Doctrine\Query\EavQueryFactory;
use Maxkain\EavBundle\Query\EavExpression;
use Maxkain\EavBundle\Query\EavComparison;

class MyService
{
    public function __construct(
        private EntityManagerInterface $em,
        private EavQueryFactory $eavQueryFactory
    ) {
    }
	
    public function myMethod(): array
    {
    	$qb = $this->em->getRepository(Product::class)->createQueryBuilder('e')->select();

        // attribute id => values (or value ids for enum EAV)
        $this->eavQueryFactory->addEavFilters($qb, 'e', MyEav::class, [
            777 => [111, 222],
            888 => [333, new EavComparison('>', 10)]
            // ...
        ]);
        
        $this->eavQueryFactory->addEavFilters($qb, 'e', MyAnotherEav::class, [
            999 => 'myValue1',
            555 => 'myValue2'
            111 => new EavComparison('>', 10),
            333 => new EavComparison('LIKE', 'myValue%'),
            222 => new EavExpression(':fieldTitle LIKE '. $qb->createNamedParameter('myValue%'))
            444 => new EavExpression(':fieldTitle > 18 AND :field < 30')
            777 => new EavExpression(':field IN (' . $qb->createNamedParameter([111, 222, 333]) . ')')
            // ...
        ]);
		
        $result = $qb->getQuery()->getResult();
        
        // ...do something with result 
        return $result;
    }
}


// attribute id => values
$myEavFilter = [
    22 => [212, 220]
];

// selects the value or value title (if enum) of attribute 22
// if there are multiple values, then MIN(:value) is selected by default
$eavQueryFactory->addValueSelect($qb, 'e', 22, MyEav::class);

// you may customize `select` and `as` expressions
// `as` expression by default has doctrine's option `HIDDEN`, so, the value is selected, but not hydrated
// 'attributeName' placeholder generates automatically, but you can change it  
$eavQueryFactory->addValueSelect($qb, 'e', 22, MyEav::class,
    // default values:
    select: 'MIN(:value)',
    as: 'HIDDEN :attributeName'
);
// the query will be like this:
// 'SELECT e, (SELECT MIN(:value) FROM ...) AS HIDDEN :attributeName FROM ...'

// get the generated attribute name,
// it looks like 'attribute_0_22', where 0 is eav options index
$attribute22Name = $eavQueryFactory->getAttributeName(MyEav::class, 22);

// add order by it as by regular column
$qb->addOrderBy($attribute22Name, 'ASC');

//or simply use any other attribute name
$eavQueryFactory->addValueSelect($qb, 'e', 22, MyEav::class,
    select: 'MAX(:value)',
    as: 'HIDDEN myColumn'
);
$qb->addOrderBy('myColumn', 'DESC');

// restricts selected values to values from the list $myEavFilter[22]
$eavQueryFactory->addValueSelect($qb, 'e', 22, MyEav::class, $myEavFilter[22]);

namespace App\Controller\Admin\Product;

//...
use Maxkain\EavBundle\Bridge\EasyAdmin\EavFieldFactory;

class ProductCrudController extends AbstractCrudController
{
    public function __construct(
        private EavFieldFactory $eavFieldFactory,
    ) {
    }

    public function configureFields(string $pageName): iterable
    {
        return [
            $this->eavFieldFactory->create('myEavs', 'My attributes', MyEav::class),
            $this->eavFieldFactory->create('anotherMyEavs', 'Another attributes', AnotherMyEav::class)
            //...
        ];
    }
}

public function configureAssets(): Assets
{
    $assets = parent::configureAssets();
    $assets->addCssFile('bundles/maxkaineav/styles/compact_ea_collection.css');

    return $assets;
}

public function configureCrud(): Crud
{
    $crud = parent::configureCrud();
    $crud->setFormThemes(['@Eav/easy-admin/theme.html.twig', '@EasyAdmin/crud/form_theme.html.twig']);

    return $crud;
}

CollectionField::new('values')->useEntryCrudForm()->renderExpanded()
    ->addCssClass('compact-ea-collection')
    ->setFormTypeOption('entry_options', [
    	'block_prefix' => 'compact_ea_collection_entry'
    ])

$this->eavFieldFactory->create('myEavs', null, MyEav::class, null, [], [
    EavType::VALUE_CONSTRAINTS => [new Assert\Email()]
])