I have a MappedSuperclass entity Person, and a number of child entities:
Teacher
Student
Janitor
I want to implement a Repository class for all these entities with a method findByString() like this:
public function findByString($searchStr)
{
return $this->createQueryBuilder('C')
->where('C.name like :val')
->orWhere('C.surnames like :val')
->orWhere('C.dni like :val')
->setParameter('val', "%$searchStr%")
->orderBy('C.surnames', 'ASC')
->getQuery()
->getResult();
}
My first approach was to create a PersonRepository and then extend all the other repositories from this class, but something's wrong with the entity class in the repository.
When calling $teacherRepository->findByString($search_str); I get this error:
Cannot autowire service "App\Repository\PersonRepository": argument "$className" of method "__construct()" is type-hinted "string", you should configure its value explicitly.
<?php
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Tools\Pagination\Paginator;
use App\Entity\Person;
/**
* #method Person|null find($id, $lockMode = null, $lockVersion = null)
* #method Person|null findOneBy(array $criteria, array $orderBy = null)
* #method Person[] findAll()
* #method Person[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class PersonRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry, string $className)
{
parent::__construct($registry, $className);
}
public function findByString($searchStr)
{
return $this->createQueryBuilder('C')
->where('C.name like :val')
->orWhere('C.surnames like :val')
->orWhere('C.dni like :val')
->setParameter('val', "%$searchStr%")
->orderBy('C.surnames', 'ASC')
->getQuery()
->getResult();
}
And then, TeacherRepository like this:
class TeacherRepository extends PersonRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Teacher::class);
}
}
If I use the standard __contruct() in PersonRepository with Person::class like this:
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Person::class);
}
Then I get this error:
An exception occurred while executing a query: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'person' doesn't exist
What is the correct approach in this case? or is it possible to create a Repository class for a MappedSuperClass and then have all child classes inherit its methods?
The problem is that autowire does not know that PersonRepository is just a base class and should not be a service. Autowire gets confused by the string argument.
So make PersonRepository abstract and autowire will ignore it. You can also remove the constructor as it does not do anything.
abstract class PersonRepository
{
public function findByString($searchStr)
{
Another approach would be to move the findBy stuff into a trait and eliminate the PersonRepository class completely.
Related
I'm new on symfony 6.1 and i would like to understand what wrong with my custom sql request.
I try many things but with no success can you help me ?
This is my Accueil Controller where i want to get back the sql result from my repository :
<?php
namespace App\Controller;
use App\Entity\Mission;
use App\Entity\Tag;
use App\Entity\User;
use App\Form\AddMissionFormType;
use App\Form\RegistrationFormType;
use App\Repository\MissionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class AccueilProspectorController extends AbstractController
{
#[Route('/accueil/prospector', name: 'app_accueil_prospector')]
public function index(Request $request,ManagerRegistry $doctrine,Security $security): Response
{
$mission = new Mission();
//Récupération de toutes les missions.
$allmission = $doctrine->getManager()->getRepository(Mission::class)->selectmissionswithtags();
//Création du formulaire pour ajouter une mission
$mission->setIduser($security->getUser());
$form = $this->createForm(AddMissionFormType::class, $mission)->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$entityManager = $doctrine->getManager();
$entityManager->persist($mission);
$entityManager->flush();
return $this->redirectToRoute('app_accueil_prospector');
}
return $this->render('accueil_prospector/index.html.twig', [
'controller_name' => 'AccueilProspectorController',
'addmissionForm' => $form->createView(),
'missionsvalues' => $allmission,
]);
}
}
This is my repository where is the request :
<?php
namespace App\Repository;
use App\Entity\Mission;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\ManagerRegistry;
/**
* #extends ServiceEntityRepository<Mission>
*
* #method Mission|null find($id, $lockMode = null, $lockVersion = null)
* #method Mission|null findOneBy(array $criteria, array $orderBy = null)
* #method Mission[] findAll()
* #method Mission[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class MissionRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Mission::class);
}
public function save(Mission $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function remove(Mission $entity, bool $flush = false): void
{
$this->getEntityManager()->remove($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function selectmissionswithtags(){
$sql = "SELECT descriptionmission,onsetdate,deadline,prioritymission,remote, GROUP_CONCAT(tg.nomtag SEPARATOR ',') as tag From mission m
left join mission_tag mt on m.id = mt.mission_id
left join tag tg on mt.tag_id = tg.id
GROUP BY descriptionmission;";
$rsm = new ResultSetMapping();
$rsm->addEntityResult(Mission::class, 'mission');
$em = $this->getEntityManager();
return $result = $em->createNativeQuery($sql,$rsm)->getArrayResult();
}
This is my selectmissionwithtags witch return empty array.
enter image description here
Querybuilder -> i don't have GROUP_CONCAT so i can't use this kind of query builder.
NativeQuery -> i use this method for the moment.
The last thing that i can do is to create entity for an database view and create the entity related to. But i would like to understand the querynative method for the moment.
Many thanks ;)
The ResultSetMapping needs to be really explicit to work, you'd have to do something like
$rsm->addEntityResult(Mission::class, 'mission');
$rsm->addFieldResult('mission', 'id', "id");
//same for all fields
$rsm->addJoinedEntityResult(MissionTag::class, 'mt', 'mission', 'mission_tag');
$rsm->addFieldResult('mt', 'id', 'id);
//same for all fields of related entities
But there is ResultSetMappingBuilder to the rescue
$rsmb->addRootEntityFromClassMetadata(Mission::class);
That's all you should have to do, with doctrine handling the relation and hydration. It will try to hydrate from your SELECT tho, so you need all fields used in the hydrator in the SELECT from your query.
You can specify the joined relation with:
$rsm->addJoinedEntityFromClassMetadata(MissionTag::class, 'mt', 'mission', 'relationName', array('id' => "tag_id"));
But then you need the join in the query (it will look for the relation in the select) and you need to be careful to when to use the doctrine name (relationName) and when to use the database name (tag_id)
You could also archived your ends by adding GROUP_CONCAT to doctrine, by leveraging Doctrine's User Defined Functions
I've found a snippet doing exactly that, it's not mine and I have not tested it.
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();
}
}
I have a task to cache doctrine result with custom keys using redis. Here is repository class:
<?php
namespace AppBundle\Repository;
class JobRepository extends EntityRepository implements JobRepositoryInterface
{
/**
* #return Job[]
*/
public function getActiveWithCategory()
{
$qb = $this->createQueryBuilder('j');
return $qb
->addSelect('c')
->leftJoin('j.category', 'c')
->addCriteria(JobCriteria::active())
->getQuery()
->getResult();
}
}
Interface:
<?php
namespace AppBundle\Repository;
interface JobRepositoryInterface
{
/**
* #return Job[]
*/
public function getActiveWithCategory();
}
Is it possible to create Decorator for this repository and say somehow to doctrine to return needed implementation?
Or I have to create service JobRepositoryService which implements interface. Here I call repository methods. And then create another JobCachedRepositoryService which is decorator for JobRepositoryService. And don't use $this->getDoctrine()->getRepository('Job') in whole project.
Is it right solution? How would you resolve this problem?
Thanks in advance
If you need to cache doctrine's result with redis custom key, you can to it like that:
<?php
namespace AppBundle\Repository;
class JobRepository extends EntityRepository implements JobRepositoryInterface
{
/**
* #return Job[]
*/
public function getActiveWithCategory()
{
$qb = $this->createQueryBuilder('j');
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheDriver = new Doctrine\Common\Cache\RedisCache();
$cacheDriver->setRedis($redis);
$cacheDriver->setNamespace('some_namespace_');
$query->setResultCacheDriver($cacheDriver);
$query->useResultCache(true, 3600, 'result_key_in_redis');
$query = $qb->addSelect('c')
->leftJoin('j.category', 'c')
->addCriteria(JobCriteria::active())
->getQuery();
return $query->getResult();
}
}
You can install redis lib via pecl.
I am creating an application that fetches and search for product name from different sources (DB, XML, JSON, ...)(for this code Im testing only with the DB), my idea was to create an interface for that.
I created the interface ProductRepositoryInterface and the class DoctrineProductRepository then I declared them both as services.
In my controller, I call the search function with the product name as param.
Here is my interface ProductRepositoryInterface :
namespace Tyre\TyreBundle\Repository;
interface ProductRepositoryInterface
{
function search(string $needle);
}
My interface DoctrineProductRepository:
namespace Tyre\TyreBundle\Repository;
class DoctrineProductRepository implements ProductRepositoryInterface
{
public function __constructor(EntityManager $em)
{
$this->em = $em;
}
public function search(string $needle)
{
$repository = $this->em->getRepository('TyreTyreBundle:Products');
$query = $repository->createQueryBuilder('u')
->where("u.name LIKE '%".$needle."%' or u.manufacturer LIKE '%".$needle."%'")
->getQuery();
return $query->getArrayResult();
}
}
My Service.yml
services:
Tyre\TyreBundle\Repository\DoctrineProductRepository:
class: Tyre\TyreBundle\Repository\DoctrineProductRepository
Tyre\TyreBundle\Repository\ProductRepositoryInterface:
class: Tyre\TyreBundle\Repository\ProductRepositoryInterface
and finally my controller :
namespace Tyre\TyreBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Tyre\TyreBundle\Repository\DoctrineProductRepository;
use Tyre\TyreBundle\Repository\ProductRepositoryInterface;
class DefaultController extends Controller
{
public function indexAction()
{
return $this->render('TyreTyreBundle:Default:search.html.twig');
}
public function searchAction(Request $request) {
$repositoryMap = [
'db' => DoctrineProductRepository::class,
];
$serviceName = $repositoryMap[$request->get('db')]; /***This is Line 56 ***/
/** #var ProductRepositoryInterface */
$repository = $this->get($serviceName);
$results = $repository->search($request->get('search_for'));
return $this->render('TyreTyreBundle:Default:detail.html.twig', array('results' => $results));
}
public function detailAction()
{
//forward the user to the search page when he tries to access directly to the detail page
return $this->render('TyreTyreBundle:Default:search.html.twig');
}
}
But I get an error :
EDIT
When I try http://localhost:8000/search?db=db , I get other error (I var_dumped $repositoryMap) :
click to view
Am I missing anything?
The reason for your 'ContextErrorException' is :
$request->get('search_for')
is empty because you are passing nothing in the url for that key. Pass 'search_for' also in addition with 'db' like:
http://localhost:8000/search?db=db&search_for=myvalue
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()