Esto es un poco de mi experiencia. En este caso vamos a poder filtrar entidades por una Cuenta.
Cada usuario va a pertencer a una Cuenta (Account.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
// src/App/Entity/User.php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table(name="fos_user") */ class User extends BaseUser { /** * @ORM\ManyToOne(targetEntity="Account", inversedBy="users", cascade={"persist"}) * @ORM\JoinColumn(name="account_id", referencedColumnName="id") */ protected $account; /** * Set account * * @param \App\Entity\Account $account * * @return User */ public function setAccount(\App\Entity\Account $account = null) { $this->account = $account; return $this; } /** * Get account * * @return \App\Entity\Account */ public function getAccount() { return $this->account; } } |
Aca definimos la cuenta, de vago no copie todos los setters y getters… usen la imaginacion… o el bin/console make:entity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; /** * Account * * @ORM\Table(name="account") * @ORM\Entity */ class Account { // ... } |
Luego, si queremos filtrar los clientes por cuenta… para no repetir código utilizamos un Trait…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// src/Entity/Client.php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use App\Entity\Traits\AccountAwareEntity; /** * @ORM\Entity */ class Client { use AccountAwareEntity; // ... } |
Aquí el Trait… como ven muy simple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// src/Entity/Traits/AccountAwareEntity.php namespace App\Entity\Traits; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; trait AccountAwareEntity { /** * @ORM\ManyToOne(targetEntity="App\Entity\Account") * @ORM\JoinColumn(name="account_id", referencedColumnName="id") */ private $account; /** * Set account * * @param \AppBundle\Entity\Account $account * * @return Venta */ public function setAccount(\App\Entity\Account $account = null) { $this->account = $account; return $this; } /** * Get account * * @return \AppBundle\Entity\Account */ public function getAccount() { return $this->account; } public function shouldBeFilteredByAccount() { return true; } } |
Ahora arrancamos con la magia… creamos un SQLFilter donde se agrega la logica.. de como vamos a filtrar y tambien importante cuando… el cuando lo sabemos con un Helper que definimos más abajo…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// src/Filter/AccountFilter.php namespace App\Filter; use Doctrine\ORM\Mapping\ClassMetaData; use Doctrine\ORM\Query\Filter\SQLFilter; use App\Helpers\AccountAwareHelper; class AccountFilter extends SQLFilter { public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) { if ('cli' === PHP_SAPI && is_null($this->getParameter('id'))) { return '1 = 1'; } $class = $targetEntity->reflClass->name; if (AccountAwareHelper::classIsAccountAware($class)) { $fieldName = 'account_id'; $accountId = $this->getParameter('id'); $query = sprintf('%s.%s = %s', $targetTableAlias, $fieldName, $accountId); return $query; } return '1 = 1'; } |
Para que esto funcione, debemos definir el «Id» a filtrar en el código anterior… esto se hace con un Configurator, el cual no es otra cosa que un EventListener al que se le hacemos un inject de Doctrine y el manejo de la sesión de usuarios. Tomando el usuario conectado, le preguntamos a que cuenta pertenece, tomamos el ID y lo seteamos al filtro de Doctrine.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// src/Filter/Configurator.php namespace App\Filter; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Doctrine\Common\Persistence\ObjectManager; class Configurator { protected $em; protected $tokenStorage; public function __construct(ObjectManager $em, TokenStorageInterface $tokenStorage) { $this->em = $em; $this->tokenStorage = $tokenStorage; } public function onKernelRequest() { if ($user = $this->getUser()) { if (!in_array('ROLE_SUPER_ADMIN', $user->getRoles())) { $filter = $this->em->getFilters()->enable('account_filter'); $filter->setParameter('id', $this->getUser()->getAccount()->getId()); } else { try { // $this->em->getFilters()->enable('account_filter'); // $this->em->getFilters()->disable('account_filter'); } catch (\Expcetion $e) { } } } else { $this->em->getFilters()->enable('account_filter'); $this->em->getFilters()->disable('account_filter'); } } private function getUser() { $token = $this->tokenStorage->getToken(); if (!$token) { return null; } $user = $token->getUser(); if (!($user instanceof UserInterface)) { return null; } return $user; } } |
Este Helper es para reconocer si la entidad debe ser filtrada por el AccountFilter o solo dejarla pasar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// src/Helpers/AccountAwareHelper.php namespace App\Helpers; class AccountAwareHelper { public static function isAccountAware($object) { $class = get_class($object); return self::classIsAccountAware($class); } public static function classIsAccountAware($class) { if (in_array('App\Entity\Traits\AccountAwareEntity', self::classUses($class))) { return true; } if (\is_subclass_of($class, 'App\Entity\AccountAware')) { return true; } return false; } private function classUses($class, $autoload = true) { $traits = []; do { $traits = array_merge(class_uses($class, $autoload), $traits); } while ($class = get_parent_class($class)); foreach ($traits as $trait => $same) { $traits = array_merge(class_uses($trait, $autoload), $traits); } return array_unique($traits); } } |
Y aca las configuraciones… primero para que lo reconozca Doctrine al Filter.
1 2 3 4 5 6 7 |
# config/packages/doctrine.yaml doctrine: orm: account_filter: class: App\Filter\AccountFilter enabled: true |
Y ahora la definición para que se reconozca el EventListener.
1 2 3 4 5 6 7 8 |
# config/services.yaml app.filter.account.configurator: class: 'App\Filter\Configurator' arguments: - "@doctrine.orm.entity_manager" - "@security.token_storage" tags: - { 'name': kernel.event_listener, 'event': kernel.request, priority: 5 } |
That’s all folks.