Extend Doctrine EntityRepository - symfony

I have written a class BasicRepository in order to use it instead of the EntityRepository to add some basic modification like remove all deleted-flaged items.
<?php
namespace AppBundle\Repository;
use AppBundle\DataFixtures\ORM\LoadEventPrioData;
use AppBundle\Entity\Location;
use Doctrine\ORM\EntityRepository;
class BasicRepository extends EntityRepository
{
public function createQueryBuilder($alias, $indexBy = null)
{
$query = parent::createQueryBuilder($alias);
dump(parent::getClassName());
dump($this->getClassName());
if (property_exists($this->getClassName(), 'isDeleted')) {
dump("Ping");
$query->andWhere($alias.'.isDeleted = :false')->setParameter('false', false);
}
else {
dump("Pong");
}
return $query;
}
}
Controller:
...
public function searchAction(Request $request) {
$em = $this->getDoctrine()->getManager();
$meta = new ClassMetadata('AppBundle:Location');
$er = new BasicRepository($em, $meta);
$query = $er->createQueryBuilder('u');
...
My aim is that - if the property "isDeleted" (boolean) exists in the Entity - the Query should contain an additional Where-Statement.
For some strange reason property_exists always return false - even when the property exits in the class.

I get your idea. The correct place you're looking for is Doctrine Filters. Check this package: https://github.com/DeprecatedPackages/DoctrineFilters#usage
There you can find example exactly with your use case:
<?php
use Doctrine\ORM\Mapping\ClassMetadata;
use Symplify\DoctrineFilters\Contract\Filter\FilterInterface;
final class SoftdeletableFilter implements FilterInterface
{
/**
* {#inheritdoc}
*/
public function addFilterConstraint(ClassMetadata $entity, $alias)
{
if ($entity->getReflectionClass()->hasProperty('isDeleted')) {
return "$alias.isDeleted = 0";
}
return '';
}
}

Related

doctrine override default finder methods for all entity

There is a way to override the default finder methods for all entity repository
for exemple this is the default methode findBy in Doctrine\ORM\EntityRepository
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
{
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return $persister->loadAll($criteria, $orderBy, $limit, $offset);
}
But my need is something like this
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
{
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
$criteria['foo'] = 'bar';
return $persister->loadAll($criteria, $orderBy, $limit, $offset);
}
I want create a service to override this method by modifying the criteria array and adding some custom criteria attribute if needed.
I know that I can override the repository for every entity but my project has been enlarged and for now I want a practical solution to avoid changing in all entity repository.
You can make a class that extends ServiceEntityRepository and in that class define all the classes you wish to override. Then finally you will need to update all your repositories to extend this new class instead of the ServiceEntityRepository.
You shouldn't do it this way. Embrace Doctrine Filters. Using one you can introduce any kind of logic globally to all of your queries.
Refer to the answer of #emix this is more detailed solution
Now let's create the annotation. This will be added to the class to indicate which fields that will be filtered by doctrine.
/**
* #Annotation
* #Target("CLASS")
*/
final class CenterSelector
{
public $centerFieldName;
}
And use it in your class
/**
* #CenterSelector(centerFieldName="you-name-field")
*/
class CommercialPiece{
protected $you-name-field;
}
Create your Filter class that extends from SQLFilter
class CenterFilter extends SQLFilter
{
protected $reader;
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
if (empty($this->reader)) {
return '';
}
// The Doctrine filter is called for any query on any entity
// Check if the current entity is (marked with an annotation)
$centerSelector = $this->reader->getClassAnnotation(
$targetEntity->getReflectionClass(),
CenterSelector::class
);
if (!$centerSelector) {
return '';
}
// FieldName parameter in annotation
$fieldName = $centerSelector->centerFieldName;
try {
$mySelector= $this->getParameter('my-selector');
} catch (\InvalidArgumentException $e) {
// No my-selector has been defined
return '';
}
if (empty($fieldName) || empty($mySelector)) {
return '';
} else{
var_dump($mySelector);
// Add the Where clause in the request
$query = sprintf('%s.%s = %s', $targetTableAlias, $fieldName, $mySelector);
}
return $query;
}
public function setAnnotationReader(Reader $reader)
{
$this->reader = $reader;
}
}
And I created a listener onKernelRequest
public function __construct(
ObjectManager $em,
SessionInterface $session,
Reader $reader)
{
$this->em = $em;
$this->session = $session;
$this->reader = $reader;
}
public function onKernelRequest(GetResponseEvent $event)
{
$globalSelector = $this->session->get('my-global-selector');
$filter = $this->em->getFilters()->enable('center_filter');
$filter->setParameter('my-selector', $globalSelector );
$filter->setAnnotationReader($this->reader);
}
Finally don't forget to add this in config.yml
orm:
entity_managers:
default:
auto_mapping: true
filters:
center_filter:
class: your-name-space\Filter\CenterFilter
enabled: true

Doctrine mapping type entity to id, id to entity

As I said in the title, I want to convert an object to an ID (int/string) and backwards from ID to an object. Usually I would work with entity relations, but in this case I do not know the other entity/bundle and it should work independant.
I guess, I could use doctrine mapping types for that, but how can I inject my custom entity loader? So maybe I can use a callback for fetching the data.
Thats my idea (pseudocode):
class User {
public function getId() { return 'IAmUserBobAndThisIdMyId'; }
}
class Meta {
private $user; // <== HERE I NEED THE MAGIC
public function setUser($user) { $this->user = user; }
}
$user = new User();
$meta = new Meta();
$meta->setUser($user);
$em->persist($meta); // <== HERE THE MAPPING TYPE SHOULD CONVERT THE ENTITY
Know I want the entity in my database like that: user:IAmUserBobAndThisIdMyId
And backwards:
$meta = $repository->findOneById(1); // HERE I NEED THE MAGIC AGAIN
$user = $meta->getUser();
echo $user->getId();
// output: IAmUserBobAndThisIdMyId
So far, so easy... But now I need some logic and database access to restore that entity. The loading is easy, but how can I inject that into my mapping type class?
I read the doctrine documentation and I was wondering, if I could use the event manager I get from AbstractPlatform $platform via parameter. Or is there maybe a better way?
You can try something like this, but i did not test this. Also you can use doctrine postLoad and postPersist/postUpdate events to transform your User entity to integer and back.
doctrine.yaml
doctrine:
dbal:
...
types:
user: App\Doctrine\DBAL\Type\UserType
...
UserType.php
<?php
namespace App\Doctrine\DBAL\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\IntegerType;
use App\Repository\UserRepository;
use App\Entity\User;
class UserType extends IntegerType
{
const NAME = 'user';
private $userRepository;
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return $this->userRepository->find($value);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
if (!$value instanceof User) {
throw new \InvalidArgumentException("Invalid value");
}
return $value->getId();
}
public function getName()
{
return self::NAME;
}
}
I found a proper solution without hacking any classes to inject some service. The type mapping class fires an event and the conversion is handled outside.
class EntityString extends Type
{
// ...
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return $this->dispatchConverterCall($platform, TypeMapperEventArgs::TO_PHP_VALUE, $value);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return $this->dispatchConverterCall($platform, TypeMapperEventArgs::TO_DB_VALUE, $value);
}
protected function dispatchConverterCall(AbstractPlatform $platform, $name, $value)
{
$event = new TypeMapperEventArgs($name, $value);
$platform->getEventManager()->dispatchEvent(TypeMapperEventArgs::NAME, $event);
return $event->getResult();
}
// ...
}
Probably there are some better solutions, but for the moment that code does what I need. ;-)

Example Usage of ObjectManagerAware inside entity

I would like to use entity manager inside entity and no idea for usage.
use Doctrine\Common\Persistence\ObjectManagerAware;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use SomeBundle\Entity\Boarding;
use SomeBundle\Entity\User;
class Entity extends ApiUserEntity implements ObjectManagerAware
{
private $em;
public function ___construct(User $user)
{
$this->board = $this->getData(123);
}
public function injectObjectManager(ObjectManager $objectManager, ClassMetadata $classMetadata)
{
$this->em = $objectManager;
}
private function getData($leadId)
{
//return gettype($this->em); //return null
$repository =$this->em->getRepository(Boarding::class);
$query = $repository->createQueryBuilder('b')
->where('b.lead = :lead')
->setParameter('lead', $leadId)
->getQuery();
$boards = $query->getResult();
return $boards;
}
}
Using this code get me error
Call to a member function getRepository() on null"
The entity manager is null also
//return gettype($this->em); //return null
Any idea for example usage?
You can try to create a repository like here. Just add
* #ORM\Entity(repositoryClass="App\Repository\EntityRepository")
or to YAML, Xml depends on your configuration and then create the repository file. Like this one:
// src/AppBundle/Repository/ProductRepository.php
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function findAllOrderedByName()
{
return $this->getEntityManager()
->createQuery(
'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC'
)
->getResult();
}
}

Symfony2 : Automatically map query string in Controller parameter

In Symfony2, the route parameters can be automatically map to the controller arguments, eg: http://a.com/test/foo will return "foo"
/**
* #Route("/test/{name}")
*/
public function action(Request $request, $name) {
return new Response(print_r($name, true));
}
see http://symfony.com/doc/current/book/routing.html#route-parameters-and-controller-arguments
But I want to use query string instead eg: http://a.com/test?name=foo
How to do that ?
For me there are only 3 solutions:
re-implement ControllerResolverInterface
use a custom ParamConverter
$name = $request->query->get('name');
Is there another solution ?
I provide you the code for those which want to use a converter :
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Put specific attribute parameter to query parameters
*/
class QueryStringConverter implements ParamConverterInterface{
public function supports(ParamConverter $configuration) {
return 'querystring' == $configuration->getConverter();
}
public function apply(Request $request, ParamConverter $configuration) {
$param = $configuration->getName();
if (!$request->query->has($param)) {
return false;
}
$value = $request->query->get($param);
$request->attributes->set($param, $value);
}
}
services.yml :
services:
querystring_paramconverter:
class: AppBundle\Extension\QueryStringConverter
tags:
- { name: request.param_converter, converter: querystring }
In your controller:
/**
* #Route("/test")
* #ParamConverter("name", converter="querystring")
*/
public function action(Request $request, $name) {
return new Response(print_r($name, true));
}
An improved solution based on Remy's answer which will map the parameter to an entity :
<?php
namespace AppBundle\Extension;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DoctrineParamConverter;
/**
* Put specific attribute parameter to query parameters
*/
class QueryStringConverter extends DoctrineParamConverter {
protected function getIdentifier(Request $request, $options, $name)
{
if ($request->query->has($name)) {
return $request->query->get($name);
}
return false;
}
}
services.yml:
services:
querystring_paramconverter:
class: MBS\AppBundle\Extension\QueryStringConverter
arguments: ['#doctrine']
tags:
- { name: request.param_converter, converter: querystring }
in your controller:
/**
* #Route("/test")
* #ParamConverter("myobject")
*/
public function action(Request $request, AnyEntity $myobject) {
return new Response(print_r($myobject->getName(), true));
}
like #2, To solve private method (getIdentifier) first set attributes and execute normally (parent::apply). Tested on Symfony 4.4
<?php
namespace App\FrameworkExtra\Converters;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DoctrineParamConverter;
use Symfony\Component\HttpFoundation\Request;
class QueryStringEntityConverter extends DoctrineParamConverter
{
public function supports(ParamConverter $configuration)
{
return 'querystringentity' == $configuration->getConverter();
}
public function apply(Request $request, ParamConverter $configuration)
{
$param = $configuration->getName();
if (!$request->query->has($param)) {
return false;
}
$value = $request->query->get($param);
$request->attributes->set($param, $value);
return parent::apply($request, $configuration);
}
}
I havn't checked, but it seems that the FOSRestBundle provides the #QueryParam annotation which does that :
http://symfony.com/doc/current/bundles/FOSRestBundle/param_fetcher_listener.html

Custom Repository (REQUEST DQL)

I have an error when I would like to add a custom method with DQL Request.
Error:
Undefined method 'getAll'. The method name must start with either
findBy or findOneBy!
My Controller:(SheetController.php)
<?php
namespace Test\FrontBundle\Controller;
use Doctrine\ORM\EntityNotFoundException;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Test\FrontBundle\Entity\Sheet;
class SheetController extends Controller
{
public function sheetListAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('TestFrontBundle:Sheet');
$sheets = $repository->getAll();
var_dump($sheets);
return $this->render('TestFrontBundle:Sheet:sheetList.html.twig');
}
public function sheetAction($id, Request $request)
{
$repository = $this->getDoctrine()->getManager()->getRepository('TestFrontBundle:Sheet');
$sheet = $repository->find($id);
if(!$sheet)
{
throw new EntityNotFoundException();
}
return $this->render('TestFrontBundle:Sheet:sheet.html.twig', array('sheet' => $sheet));
}
}
?>
My Repository:(SheetRepository.php)
<?php
namespace Test\FrontBundle\Entity;
/**
* SheetRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class SheetRepository extends \Doctrine\ORM\EntityRepository
{
public function getAll()
{
$qb = $this->createQueryBuilder('s');
$query = $qb;
$result = $query->getQuery()->execute();
return $result;
}
}
Please, Could you help me? :)
Why don't you use native Doctrine query findAll() for this?
public function sheetListAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('TestFrontBundle:Sheet');
$sheets = $repository->findAll();
/***/
}
EDIT - With class repository:
class SheetRepository extends \Doctrine\ORM\EntityRepository
{
public function getAll()
{
return $this->createQueryBuilder('s')
->select('s')
->getQuery()
->getResult()
;
}
}
And in your controller : replace findAll() by getAll()

Resources