How to make property nullable for Symfony Serializer - symfony

I'm trying to deserialize to an object with property which might take an array of objects as value or be null.
I have no problem deserializing arrays but I need to deserialize null to an empty array or to null itself.
For example { "items": null }
class A {
/**
* #var null|Item[]
*/
private $items = [];
/**
* #return Item[]|null
*/
public function getItems(): ?array
{
return $this->items ?? [];
}
/**
* #param Item $param
* #return A
*/
public function addItem(Item $param)
{
if (!is_array($this->items)) $this->items = [];
if (!in_array($param, $this->items))
$this->items[] = $param;
return $this;
}
// /** tried with this as well
// * #param array|null $param
// * #return A
// */
// public function setItems(?array $param)
// {
// $this->items = $param ?? [];
// return $this;
// }
/**
* #param Item $item
* #return A
*/
public function removeItem(Item $item): A
{
if (!is_array($this->items)) $this->items = [];
if (in_array($item, $this->items))
unset($this->items[array_search($item, $this->items)]);
return $this;
}
/**
* #param Item $item
* #return bool
*/
public function hasItem(Item $item): bool
{
return in_array($item, $this->items);
}
}
Serializer looks like this
$defaultContext = [
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER =>
function ($articles, $format, $context) {
return $articles->getId();
},
AbstractObjectNormalizer::SKIP_NULL_VALUES => false
];
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
$encoders = [new JsonEncoder()];
$serializer = new Serializer([
new ArrayDenormalizer(),
new DateTimeNormalizer(),
new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter, null,
new ReflectionExtractor(), null, null, $defaultContext
),
], $encoders);
$a = $serializer->deserialize('{ "items": null }', A::class, 'json');
The error I get when items is null
[Symfony\Component\Serializer\Exception\InvalidArgumentException]
Data expected to be an array, null given.
Is it possible to have nullable property?

Traced down to the Serializer source code and found three possible options to have a nullable array.
Option 1
Remove addItem, hasItem, removeItem methods and it allows to set null, array, whatever. This is less preffed solution in my case.
Option 2
Adding a constructor helps as well. https://github.com/symfony/serializer/blob/5.3/Normalizer/AbstractNormalizer.php#L381
/**
* A constructor.
* #param array|null $items
*/
public function __construct($items)
{
$this->items = $items ?? [];
}
Option 3
Extended ArrayDenormalizer and overrided denormalize method to handle nulls
public function denormalize($data, string $type, string $format = null, array $context = []): array
{
if (null === $this->denormalizer) {
throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!');
}
if (!\is_array($data) && !is_null($data)) {
throw new InvalidArgumentException('Data expected to be an array or null, ' . get_debug_type($data) . ' given.');
}
if (!str_ends_with($type, '[]')) {
throw new InvalidArgumentException('Unsupported class: ' . $type);
}
if(is_null($data))
return [];
$type = substr($type, 0, -2);
$builtinType = isset($context['key_type']) ? $context['key_type']->getBuiltinType() : null;
foreach ($data as $key => $value) {
if (null !== $builtinType && !('is_' . $builtinType)($key)) {
throw new NotNormalizableValueException(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, $builtinType, get_debug_type($key)));
}
$data[$key] = $this->denormalizer->denormalize($value, $type, $format, $context);
}
return $data;
}

Related

Symfony Serializer Component AbstractNormalizer::CALLBACKS denormalize

I am trying to use Serialize with callbacks like in https://symfony.com/doc/current/components/serializer.html#using-callbacks-to-serialize-properties-with-object-instances . But it doesn't seem to go into the callback. I can ignore attributes with 'IGNORED_ATTRIBUTES' just fine, just CALLBACKS not working. What could I be doing wrong?
$dateCallback = function ($innerObject, $outerObject, string $attributeName, string $format = null, array $context = []) {
dump('foo');
return 'faa';
};
$defaultContext = [
AbstractNormalizer::CALLBACKS => [
'order_date' => $dateCallback,
]
];
$normalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext);
$serializer = new Serializer([$normalizer], []);
$order = $serializer->denormalize($data, Orderform::class, 'array');
The data is a simple array.
$data = ['order_date' => '2020-07-07',
'order_number' => '123'];
I would expect the $dateCallback to be called. But it doesn't seem to do that. The Orderform entity is getting populated but not with the value I would expect from the callback.
I tried making all this with json and xml too, since array doesn't show up in the documentation (but it works except for the callback)
Symfony 4.4
I didn't debug your code, instead I made a working version of the deserialization example based on the docs, hope it's serve as guide:
<?php
include 'vendor/autoload.php';
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Serializer;
//
//Inializing the Serializer, enconders and callbacks
//
$encoders = [new XmlEncoder(), new JsonEncoder()];
$dateCallback = function ($innerObject, $outerObject, string $attributeName, string $format = null, array $context = []) {
return $innerObject instanceof \DateTime ? $innerObject->format('Y-m-d H:i:sP') : '';
};
$defaultContext = [
AbstractNormalizer::CALLBACKS => [
'createdAt' => $dateCallback,
]
];
$normalizer = [new ObjectNormalizer(null, null, null, null, null, null, $defaultContext)];
$serializer = new Serializer($normalizer, $encoders);
//
//Creating Object Person that will be serialized
//
$person = new Person();
$person->setName('foo');
$person->setAge(99);
$person->setSportsperson(false);
$person->setCreatedAt(new \DateTime());
$arrayContent = $serializer->serialize($person, 'json');
// $arrayContent contains ["name" => "foo","age" => 99,"sportsperson" => false,"createdAt" => null]
//print_r($arrayContent); // or return it in a Response
// Lets deserialize it
$desrializedPerson = $serializer->deserialize($arrayContent, Person::class, 'json');
var_dump($desrializedPerson);
//
// Foo Bar stuff
//
// Entity Person declaration
class Person
{
private $age;
private $name;
private $sportsperson;
private $createdAt;
// Getters
public function getName()
{
return $this->name;
}
public function getAge()
{
return $this->age;
}
public function getCreatedAt()
{
return $this->createdAt;
}
// Issers
public function isSportsperson()
{
return $this->sportsperson;
}
// Setters
public function setName($name)
{
$this->name = $name;
}
public function setAge($age)
{
$this->age = $age;
}
public function setSportsperson($sportsperson)
{
$this->sportsperson = $sportsperson;
}
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
}
}

Method not found in entity Paginator from doctrine bundle

I'm trying to clean up some code done in a messy way and right now I'm having trouble with doctrine's paginator.
When I'm accessing a page that handle paginator in order to show all different articles of my blog I'm getting this error:
Neither the property "id" nor one of the methods "id()", "getid()"/"isid()"/"hasid()" or "__call()" exist and have public access in class "Doctrine\ORM\Tools\Pagination\Paginator".
In doctrine vendor bundle those methods are not set but my entity have them and I know that it is forbidden to edit a vendor file. I'm missing something because I don't know if I should extend my paginator entity and add those missing methods or is there a little bit more to do ?
I just started symfony and I know that my bases are not enough to understand it all by myself.
Thank you very much for you time and attention.
Here is my Article controller for route category:
/**
* #Route("/categorie/{id}", name="categorie")
*
* #param Request $request
* #param Helper $helper
* #param AuthorizationCheckerInterface $authChecker
* #param DocumentCategory $categorie
* #param TwitterService $twitterService
*
* #return RedirectResponse|Response
*/
public function categorie(
Request $request,
Helper $helper,
AuthorizationCheckerInterface $authChecker,
DocumentCategory $categorie,
TwitterService $twitterService
) {
if (!$authChecker->isGranted('IS_AUTHENTICATED_FULLY')
&& !$authChecker->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
return $this->redirectToRoute('login');
}
$page = (int) ($request->get('page'));
if (0 === $page) {
$page = 1;
}
$userType = $this->getDoctrine()->getRepository('App:User')
->getManagerExpertCollabo($this->getUser());
$articleAlaUneListe = [];
$articleIdListe = $helper->getArticleIdAuth($authChecker);
$articleListe = $this->getDoctrine()
->getRepository('App:Document')
->getPage(
self::ITEM_PER_PAGE * ($page - 1),
self::ITEM_PER_PAGE,
'document.dateCreated',
'DESC',
'(documentCategory.id = \''.$categorie->getId().'\' and '.$helper->baseRequestArticle().')',
7,
[],
$articleIdListe
);
[$articlePopulaireListe, $categorieListe, $totalPage] = $this->getPopularArticleList(
$articleListe,
$helper,
$articleIdListe
);
$articlesList->getDocuments();
$feedData = $twitterService->getTwitterFeed();
return $this->render('article/list.html.twig', [
'pageClass' => 'backoffice withFooterLarge dashboard',
'totalPage' => $totalPage,
'page' => $page,
'feedData' => $feedData,
'categorieListe' => $categorieListe,
'categorie' => $categorie,
'articleAlaUneListe' => $articleAlaUneListe,
'articlePopulaireListe' => $articlePopulaireListe,
'articleListe' => $articleListe, ]);
}
Here is the document entity for categories field:
/**
* #ORM\JoinTable(name="ht_lk_document_category"),
* #ORM\ManyToMany(targetEntity="App\Entity\DocumentCategory", inversedBy="documents")
*/
private $categories;
/**
* #return Collection|array<DocumentCategory>
*/
public function getCategories(): Collection
{
return $this->categories;
}
public function setCategories($category): self
{
$this->categories = $category;
return $this;
}
Here is the DocumentCategory entity :
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Document", mappedBy="categories")
*/
private $documents;
/**
* #return Collection|Document[]
*/
public function getDocuments(): Collection
{
return $this->documents;
}
Here is the Document Repository :
public function getPage($first_result, $max_results, $orderby, $direction, $criteria, $documentType = null, $searchWordArray = [], $articleIdListe = '')
{
$qb = $this->createQueryBuilder('docArticle');
$qb->select('docArticle')
->addSelect('documentCategory', 'documentCategory')
->addSelect('user', 'user')
/*
if(sizeof($searchWordArray) > 0){
$fieldIndice = 1;
foreach($searchWordArray as $searchWord){
$qb->andWhere('(document.name_fr LIKE ?'.$fieldIndice.' or document.name_en LIKE ?'.$fieldIndice.' or document.content_fr LIKE ?'.$fieldIndice.' or document.content_en LIKE ?'.$fieldIndice.')');
$qb->setParameter($fieldIndice++, '%'.$searchWord.'%');
}
} */
->leftJoin('docArticle.categories', 'documentCategory')
->leftJoin('docArticle.author', 'user')
->setFirstResult($first_result)
->setMaxResults($max_results);
if (!empty($criteria)) {
$qb->where('('.$criteria.')');
}
if (!empty($orderby)) {
$qb->orderBy($orderby, $direction);
}
$pag = new Paginator($qb->getQuery());
$qb->setFirstResult(0);
$qb->setMaxResults(PHP_INT_MAX);
$sql = $qb->getQuery()->getSql();
if ('()' !== $articleIdListe) {
$qb->where('(docArticle.id IN '.$articleIdListe);
}
$compte = \count($qb->getQuery()->getScalarResult());
return ['page' => $pag, 'compte' => $compte];
}
And finally here is the Document Category Repository :
/**
* #param $first_result
* #param $max_results
* #param $orderby
* #param $direction
* #param $criteria
* #param int|null $documentType
* #param array $searchWordArray
* #param string $articleIdListe
*
* #return array
*/
public function getPage(
$first_result,
$max_results,
$orderby,
$direction,
$criteria,
$documentType = null,
$searchWordArray = [],
$articleIdListe = ''
) {
$qb = $this->createQueryBuilder('document');
$qb->select('document')
->addSelect('documentCategory', 'documentCategory')
->addSelect('user', 'user')
->addSelect('documentType', 'documentType');
if (\count($searchWordArray) > 0) {
$fieldIndice = 1;
foreach ($searchWordArray as $searchWord) {
$qb->andWhere(
'('
.'document.name_fr LIKE ?'.$fieldIndice
.' or document.name_en LIKE ?'.$fieldIndice
.' or document.content_fr LIKE ?'.$fieldIndice
.' or document.content_en LIKE ?'.$fieldIndice
.')'
);
$qb->setParameter($fieldIndice++, '%'.$searchWord.'%');
}
}
if ($documentType) {
if (\mb_strlen($articleIdListe) > 3) {
$qb->andWhere('(documentType.id = :documentType OR document.id IN '.$articleIdListe.')')
->setParameter('documentType', $documentType);
} else {
$qb->andWhere('(documentType.id = :documentType)')
->setParameter('documentType', $documentType);
}
}
$qb->leftJoin('document.categories', 'documentCategory')
->leftJoin('document.documentType', 'documentType')
->leftJoin('document.author', 'user')
->setFirstResult($first_result)
->setMaxResults($max_results)
->andWhere('document.documentType<>6');
if (!empty($criteria)) {
$qb->andWhere('('.$criteria.')');
}
if (!empty($orderby)) {
$qb->orderBy($orderby, $direction);
}
$sql = $qb->getQuery()->getSql();
$pag = new Paginator($qb->getQuery());
dump($pag);
$qb->setFirstResult(0);
$qb->setMaxResults(PHP_INT_MAX);
$sql = $qb->getQuery()->getSql();
$compte = \count($qb->getQuery()->getScalarResult());
return ['page' => $pag, 'compte' => $compte];
}
/**
* #param int|null $documentType
*
* #return array
*/
public function getArticleIdList($documentType = null)
{
$qb = $this->createQueryBuilder('document');
$qb->select('document.id');
if ($documentType) {
$qb->where('(document.documentType = :documentType)')
->setParameter('documentType', $documentType);
}
$compte = $qb->getQuery()->getScalarResult();
return $compte;
}
(I deleted all unnecessary method for this question)
Add the following code to the entity class from which you want to show data:
public function getId(): ?string
{
return $this->id;
}
Or
if you do not want the property "id" to be displayed in the view, comment out or delete the lines of code in your Twig template for the entity in question. For example, delete
<td>{{ Category.id }}</td>

Symfony 3.3.2 Doctrine EntityRepository constructor argument

I'm newbie on Symfony. I am trying to update an old project under symfony 2.6 to symfony 3.3.
After multiple bug fixes I am stuck on a point: I have an Error in my EntityRepository.php file with the constructor.
Type error: Too few arguments to function Doctrine\ORM\EntityRepository::__construct(), 1 passed in /Users/.../var/cache/dev/appDevDebugProjectContainer.php on line 3434 and exactly 2 expected
I understand the error, but my EntityRepository file not contain any __construct method. Should I fix something between Symfony 2 and 3 for the consructor to work?
Thanks a lot.
Here is my MilestoneRepository.php file:
namespace MilestonesBundle\Entity\Repository;
use DateTime;
use Doctrine\ORM\EntityRepository;
use Milestones\Entity\Factory\MilestoneFactoryInterface;
use Milestones\Entity\Repository\MilestoneRepositoryInterface;
class MilestoneRepository extends EntityRepository implements MilestoneFactoryInterface, MilestoneRepositoryInterface
{
protected $current = false;
/**
* #see MilestoneFactoryInterface
*/
public function create()
{
$class = $this->getClassName();
return new $class;
}
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
{
if (!$orderBy) {
$orderBy['startsAt'] = 'ASC';
}
return parent::findBy($criteria, $orderBy, $limit, $offset);
}
/**
* #see MilestoneRepositoryInterface
*/
public function findCurrent()
{
$now = new DateTime;
if ($this->current === false) {
$this->current = $this->createQueryBuilder('m')
->where('m.startsAt <= :now')
->andWhere('(m.endsAt IS NULL OR :now < m.endsAt)')
->setParameter('now', $now->format('Y-m-d'))
->orderBy('m.startsAt', 'ASC')
->setMaxResults(1)
->getQuery()
->getOneOrNullResult()
;
}
return $this->current;
}
/**
* #see MilestoneRepositoryInterface
*/
public function isOpen()
{
$current = $this->findCurrent();
return $current && $current->isStart();
}
}
And here is my EntityRepository.php file:
namespace Common\Doctrine\ORM;
use Doctrine\ORM\EntityRepository as BaseEntityRepository;
use Doctrine\ORM\QueryBuilder;
class EntityRepository extends BaseEntityRepository
{
protected $alias = 'x';
public function add($entity)
{
$em = $this->getEntityManager();
$em->persist($entity);
$em->flush();
}
public function remove($entity)
{
$em = $this->getEntityManager();
$em->remove($entity);
$em->flush();
}
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null, $result = true)
{
$alias = $this->alias;
$builder = $this->createQueryBuilder($alias);
$this->applyCriteria($builder, $alias, $criteria);
$this->applyOrderBy($builder, $alias, $orderBy);
$this->applyLimit($builder, $limit);
$this->applyOffset($builder, $offset);
if (!$result) {
return $builder;
}
return $builder->getQuery()->getResult();
}
public function findOneBy(array $criteria, array $orderBy = null, $result = true)
{
$alias = $this->alias;
$builder = $this->createQueryBuilder($alias);
$this->applyCriteria($builder, $alias, $criteria);
$this->applyOrderBy($builder, $alias, $orderBy);
if (!$result) {
return $builder;
}
return $builder->getQuery()->getOneOrNullResult();
}
protected function applyCriteria(QueryBuilder $builder, $alias, array $criteria)
{
$map = $this->getCriteriaMap();
foreach ($criteria as $property => $value) {
if (array_key_exists($property, $map)) {
call_user_func_array($map[$property], [$builder, $alias, $property, $value]);
} else {
$this->applyDefaultCriterion($builder, $alias, $property, $value);
}
}
}
protected function getCriteriaMap()
{
return [];
}
protected function applyDefaultCriterion($builder, $alias, $property, $value)
{
if (null === $value) {
$builder->andWhere($alias.'.'.$property.' IS NULL');
} else {
$parameter = 'p_' . uniqid();
$builder->andWhere($alias.'.'.$property.' = :'.$parameter);
$builder->setParameter($parameter, $value);
}
}
/**
* Apply order by
*
* #param QueryBuilder $builder
* #param string $alias
* #param array|null $orderBy
* #return void
*/
protected function applyOrderBy(QueryBuilder $builder, $alias, array $orderBy = null)
{
if (empty($orderBy)) {
$orderBy = $this->getDefaultOrder();
}
$map = $this->getOrderingMap();
foreach ($orderBy as $property => $direction) {
if (array_key_exists($property, $map)) {
call_user_func_array($map[$property], [$builder, $alias, $property, $direction]);
} else {
$this->applyDefaultOrder($builder, $alias, $property, $direction);
}
}
}
protected function getDefaultOrder()
{
return [];
}
protected function getOrderingMap()
{
return [];
}
protected function applyDefaultOrder(QueryBuilder $builder, $alias, $property, $direction)
{
$builder->orderBy($alias.'.'.$property, $direction);
}
protected function applyLimit(QueryBuilder $builder, $limit = null)
{
if ($limit) {
$builder->setMaxResults($limit);
}
}
protected function applyOffset(QueryBuilder $builder, $offset = null)
{
if ($offset) {
$builder->setFirstResult($offset);
}
}
}
I think I'm accessing through a service, with this:
services:
# Factories
milestones.factory.milestone:
alias: milestones.repository.milestone
arguments: [ MilestonesBundle\Entity\Milestone ]
# Repositories
milestones.repository.milestone:
class: MilestonesBundle\Entity\Repository\MilestoneRepository
factory_service: doctrine.orm.default_entity_manager
factory_method: getRepository
arguments: [ MilestonesBundle\Entity\Milestone ]
replace this code:
milestones.repository.milestone:
class: MilestonesBundle\Entity\Repository\MilestoneRepository
factory_service: doctrine.orm.default_entity_manager
factory_method: getRepository
arguments: [ MilestonesBundle\Entity\Milestone ]
with this one:
milestones.repository.milestone:
class: MilestonesBundle\Entity\Repository\MilestoneRepository
factory: ['#doctrine.orm.entity_manager', getRepository]
arguments: [ MilestonesBundle\Entity\Milestone ]
factory method - getRepository
I think somewhere in jour code this method is called:
public function create()
{
$class = $this->getClassName();
return new $class;
}
And this is calling the constructor of Doctrine\ORM\EntityRepository:
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
{
$this->_entityName = $class->name;
$this->_em = $em;
$this->_class = $class;
}
therefore you will have to inject the arguments if you want to use your create method... I think it should be something like new $class($entityManager, Entity::class)

Symfony2 get parameters.yml in entity, special case

I know its against the framework to make an entity container aware, but this is a special case, i have a credit card entity, and i want to do this:
/**
* #return mixed
*/
public function getNumber()
{
$number = $this->number;
$crypt = base64_decode($number);
$number = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $crypt, MCRYPT_MODE_ECB);
return trim($number);
}
/**
* #param $number
* #return $this
*/
public function setNumber($number)
{
$crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $number, MCRYPT_MODE_ECB);
$number = trim(base64_encode($crypt));
$this->number = $number;
return $this;
}
And i want the $key to be the secret from parameters.yml, since i dont want to save it in the code.
I can't pass it as a parameter, when i use the FormType, cause the form type will not pass it when it binds the request.
$credit_card = new CreditCard();
$credit_card->setCustomer($customer);
$payment_form = $this->createPaymentForm($credit_card);
$payment_form->handleRequest($request);
Remove data modifications from your Entity, and add it to your FormType.
public function getNumber()
{
return $this->number;
}
public function setNumber($number)
{
$this->number = $number;
return $this;
}
Make your FormType take the parameter as argument.
class CreditCardType extends AbstractType {
private $key;
public function __construct($key) {
$this->key = $key;
}
// ...
// Listen on the PRE_BIND event to update your value before binding
$builder->addEventListener(FormEvents::PRE_BIND, function (FormEvent $event)
{
$data = $event->getData();
$number = $data['number'];
$crypt = $this->encryptNumber($number);
$data['number'] = $crypt;
$event->setData($data);
});
public function encryptNumber($key, $number)
{
$crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $number, MCRYPT_MODE_ECB);
$number = trim(base64_encode($crypt));
return $number;
}
}
Now, in your createPaymentForm, add the parameter to your FormType instance, like this :
$param = $this->container->getParameter('yourkeyparam');
$form = $this->createForm(new CreditCardType($param), $credit_card, array());
Hope this is what you need.

symfony2 restrict model based on user - upgrade from symfony1

I'm building a multi-tenant application. In Symfony1 I would restrict access to data by accessing the user details and extending the createQuery function:
class PersonTable extends Doctrine_Table{
public function createQuery($alias = '')
{
$query = parent::createQuery($alias);
try {
$user = sfContext::getInstance()->getUser();
}catch(Exception $e){
if ($e->getMessage() == 'The "default" context does not exist.'){
return $query;
}else{
throw $e;
}
}
if ($user->hasGroup('Team1')){
//all good
}else if ($user->hasGroup('Team2')){
$user_id = $user->getGuardUser()->getStaff()->getId();
$alias = $query->getRootAlias();
$time = date('Y-m-d H:i:s',time());
$query->andWhere("$alias.type='type1' and pe.assigned_psw_id");
}
$query->orderBy('name asc');
return $query;
}
}
I know there are downsides to accessing the user object through sfContext in sf1, but this method seemed superior to others, as you can't "forget" to secure a controller against wrong user access.
How can I achieve the same in Symfony2?
I have solved this problem the following way.
Standardise how EntityRepository is fetched among controllers:
public function getUserRestrictedRepository($entity, $em = null )
{
$securityContext = $this->get( 'security.context' );
if (!$em){
$em = $this->getDoctrine()->getManager();
}
return $em
->getRepository( 'MyBundle:' . $entity )
->setSecurityContext( $securityContext );
}
Add a trait to provide queries with injected security query:
trait UserRestrictedEntityRepository {
private $securityContext;
/**
* #return mixed
*/
public function getSecurityContext()
{
return $this->securityContext;
}
/**
* #param mixed $securityContext
*/
public function setSecurityContext($securityContext)
{
$this->securityContext = $securityContext;
return $this;
}
/**
* #return mixed
*/
public function getUser()
{
return $this->getSecurityContext()->getToken()->getUser();
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name)
{
$this->name = $name;
}
function secureQueryWithUser($alias, $qb)
{
$qb->where("1=0");
}
function appendOrderBy($qb, $orderBy)
{
$first = true;
foreach ($orderBy as $field => $dir) {
if (!$dir) $dir = 'asc';
if ($first) {
$qb->orderBy('c.' . $field, $dir);
$first = false;
}else{
$qb->addOrderBy('c.' . $field, $dir);
}
}
}
public function createUnrestrictedQueryBuilder($alias)
{
return parent::createQueryBuilder($alias);
}
/**
* Creates a new QueryBuilder instance that is prepopulated for this entity name.
*
* #param string $alias
*
* #return QueryBuilder
*/
public function createQueryBuilder($alias, $indexBy=NULL)
{
if ($this->getUser()) {
$qb = $this->_em->createQueryBuilder()
->select($alias)
->from($this->_entityName, $alias);
if (isset($this->defaultOrder) && $this->defaultOrder){
$this->appendOrderBy($qb, $this->defaultOrder);
}
if ($this->getUser()->isSuperAdmin()){
return $qb;
}else{
return $this->secureQueryWithUser($alias, $qb);
}
}else{
throw new Exception('Run setUser() before querying ' . $this->getName() .' model.');
}
}
/**
* Finds all entities in the repository.
*
* #return array The entities.
*/
public function findAll()
{
return $this->findBy(array());
}
/**
* Finds entities by a set of criteria.
*
* #param array $criteria
* #param array|null $orderBy
* #param int|null $limit
* #param int|null $offset
*
* #return array The objects.
*/
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
{
$qb = $this->createQueryBuilder('c');
foreach ($criteria as $fkey => $fval){
$qb->andWhere($fkey.' = :'.$fval);
}
if ($limit){
$qb->setMaxResults($limit);
}
if ($offset){
$qb->setFirstResult($offset);
}
$query = $qb->getQuery();
return $query->getResult();
}
}
Implement query additions based on user access in the EnityRepository
class FarmerRepository extends EntityRepository
{
use UserRestrictedEntityRepository;
private $name = 'Farmer';
private $defaultOrder = array('name' => 'asc');
function secureQueryWithUser($alias, $qb)
{
if ($this->getSecurityContext()->isGranted( 'ROLE_CLINIC_ADMIN' )) {
return $qb
->innerJoin("$alias.vet", 'v')
->innerJoin("v.clinic", "cl")
->innerJoin("cl.VetsOfClinic", "vc")
->andWhere('vc.user_id= :userid')
->setParameter('userid', $this->getUser()->getId());
}else if ($this->getSecurityContext()->isGranted( 'ROLE_VET' )){
return $qb
->innerJoin("$alias.vet", 'v')
->andWhere('v.user_id= :userid')
->setParameter('userid', $this->getUser()->getId());
}else{
return $qb
->where("$alias.user_id= :userid")
->setParameter('userid', $this->getUser()->getId());
}
}
}

Resources