Empty array from query native builder symfony6 - symfony

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.

Related

symfony no supporting normalizer found while normalizing an entity

I'm working on symfony 4.1.
I defined two normalizer in my service.yml.
api.tone_normalizer:
class: App\Serializer\Normalizer\JnToneNormalizer
tags: [serializer.normalizer]
and
api.wskeytone_normalizer:
class: App\Serializer\Normalizer\ApiWsKeyToneToneNormalizer
tags: [serializer.normalizer]
Here the first normalizer. Is aware about JnTone entities.
<?php
namespace App\Serializer\Normalizer;
use App\Entity\JnTone;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* JnTone normalizer
*/
class JnToneNormalizer implements NormalizerInterface
{
/**
* {#inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
return [
'id' => $object->getId(),
'name' => $object->getName(),
];
}
/**
* {#inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof JnTone;
}
}
And the normalizer where I want to call the first one. rootTone is an instance of JnTone entity so I want to call my JnTone normalizer.
<?php
namespace App\Serializer\Normalizer;
use App\Entity\JnWsKey;
use App\Entity\JnTone;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\Normalizer\SerializerAwareNormalizer;
use Symfony\Component\Serializer\SerializerAwareTrait;
use Symfony\Component\Serializer\SerializerAwareInterface;
/**
* JnWsKey normalizer
*/
class ApiWsKeyNormalizer implements NormalizerInterface, SerializerAwareInterface
{
use NormalizerAwareTrait;
use SerializerAwareTrait;
private $tones;
/**
* {#inheritdoc}
*/
public function normalize($object, $format = null, array $context = array())
{
return [
'id'=>$object->getId(),
'name'=>$object->getName(),
'rootTone'=>$this->serializer->normalize($object->getRootTone(),$format,$context)
];
}
/**
* {#inheritdoc}
*/
public function supportsNormalization($data, $format = null)
{
return $data instanceof JnWsKey ;
}
}
I can't get this working. The first normalizer isn't find
Could not normalize object of type App\Entity\JnTone, no supporting normalizer found.
What I am doing wrong?
I just did not realize that I have to declare all needed normailizer in the serializer definition. I solved it doing:
$encoder = new JsonEncoder();
$serializer = new Serializer(array(
new JnToneNormalizer(),
new JnWsKeyToneNormalizer()
), array($encoder));
Symfony seems to have ObjectNormalizer. I think you may take advantage of it. Check the installation and usage. I think there is also a way to perform serialization of complex nested objects using annotations and groups.

symfony2 get field from controller without getting whole entities

I am having an issue. Am new to symfony2 and I am trying to get a field of a repository in my controller without getting all of it. I tried with the findAll() and it works 'except that am getting all the fields'.
Here is my controller:
<?php
namespace Extranet\RepportsBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class UtilisateurController extends Controller
{
/**
* Retrieves utilisateurs name(called entite)
*
* #Route("/utilisateur", name="extranet_repports_utilisateur")
* #Template("ExtranetRepportsBundle:Utilisateur:index.html.twig")
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
// $entity = $em->getRepository("ExtranetUtilisateurBundle:Utilisateur")->findAll(array());
$entity = $em->getRepository("ExtranetUtilisateurBundle:Utilisateur")
->createQueryBuilder(
'SELECT u.name FROM ExtranetUtilisateurBundle:Utilisateur u ORDER BY u.name ASC'
)
->getResult();
;
var_dump($entity);die();
return array('entity' => $entity);
}
}
// $em = $this->getDoctrine()->getManager();
// $query = $em->createQuery(
// 'SELECT p
// FROM AcmeStoreBundle:Product p
// WHERE p.price > :price
// ORDER BY p.price ASC'
// )->setParameter('price', '19.99');
// $products = $query->getResult();
I am trying to get only the username from the repository and the commented code below is the example from the symfony book
Thanks for your help
You can specify your properties in select() method of query builder which you want to select
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository("ExtranetUtilisateurBundle:Utilisateur");
$result= $entity->createQueryBuilder('u')
->select('u.name')
->orderBy('u.name')
->getQuery()
->getArrayResult();

Using doctrine findBy method

I am working on blog project where is lots of posts from different authors and i want to create a simple filter that would return me only posts by given author:
This is my controller that takes data from user:
/**
* #Route("/author/{author}", name="post_author")
* #Template()
*/
public function findByAuthorAction($author)
{
$criteria = /*this is what i need*/
$posts=$this->get('cvut_fit_biwt1_blog')->findPostBy($criteria);
return array(
'posts'=>$posts
);
}
This is how findPostBy looks like:
public function findPostBy(array $criteria)
{
return $this->postRepository->findBy($criteria);
}
And finally implementation of findBy in Doctrine 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);
}
Can you tell me how to build criteria array in my contoller so it would filter my posts (if it is even possible) ?
Assuming the {author} parameter is the author's URI name or something (this is purely speculative), you have to fetch either the author object or the author ID from Doctrine first:
$authorObj = $this->getDoctrine()->getManager()
->getRepository('AcmeBundle:Author')->findOneByName(urldecode($author));
Then pass the ID or Author object into the criteria using an associative array:
$criteria = array('author' => $authorObj);
If $author is in fact the ID of the author, then you can just do:
$criteria = array('author' => $author);
Note that you will get a Collection of objects even if you just get one result. You should use findOneBy:
public function findPostBy(array $criteria)
{
return $this->postRepository->findOneBy($criteria);
}

Symfony2 OneToMany does not use custom repository class

I have to tables
preset_item
id
and
preset_item_element
preset_item_id -> reference to prese_item
element_type
element_id
in PresetItemElement entity there is:
/**
* #var \GGG\ManagerBundle\Entity\PresetItem
*
* #ORM\ManyToOne(targetEntity="GGG\ManagerBundle\Entity\PresetItem", inversedBy="elements")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="preset_item_id", referencedColumnName="id")
* })
*/
private $presetItem;
and in PresetItem
/**
* #ORM\OneToMany(targetEntity="PresetItemElement", mappedBy="presetItem")
*/
private $elements;
public function getElements()
{
return $this->elements;
}
And preset Item have custom Repository class:
/**
* PresetItem
*
* #ORM\Table(name="preset_item")
* #ORM\Entity(repositoryClass="GGG\ManagerBundle\Entity\PresetItemRepository")
*/
class PresetItem
it's looks like that:
<?php
namespace GGG\ManagerBundle\Entity;
use Doctrine\ORM\EntityRepository;
use Doctrine\DBAL\LockMode;
class PresetItemElementRepository extends EntityRepository
{
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) {
return $this->decorate(parent::findBy($criteria, $orderBy, $limit, $offset));
}
public function find($id, $lockMode = LockMode::NONE, $lockVersion = null) {
return $this->decorateElement(parent::find($id, $lockMode, $lockVersion));
}
public function findOneBy(array $criteria, array $orderBy = null) {
return $this->decorateElement(parent::findOneBy($criteria, $orderBy));
}
private function decorateElement($element) {
$object = $this->getEntityManager()
->getRepository(
'GGGManagerBundle:'.$element
->getPresetItemElementType()
->getRepresentationObject()
)->findOneBy(array('id' => $element->getElementId()));
$element->setObject($object);
}
private function decorate($elements) {
foreach($elements as $element) {
$this->decorateElement($element);
}
return $elements;
}
}
So i'm decorating each PresetItemElement with some additional data
and it's work when i;m getting single PresetItemElement object but when i'm getting PresetItem and try iterate getElements()
$entity = $em->getRepository('GGGManagerBundle:PresetItem')->find($id);
foreach($entity->getElements() as $a) {
var_dump($a->getObject());
}
i have null here, it looks like custom PresetItemElementRepository was not executed
What i do wrong?
Doctrine doesn't use repositories internally when loading related entities. Also, when using for example a query or the query builder to retrieve entities, the repository wouldn't be used either.
To do something every time an entity is loaded from the database, you should register an event handler for the postLoad event. See http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html for how to write and register event listeners, and http://docs.doctrine-project.org/en/latest/reference/events.html#lifecycle-events for the available lifecycle events.

How to sort findAll Doctrine's method?

I've been reading Doctrine's documentation, but I haven't been able to find a way to sort findAll() Results.
I'm using symfony2 + doctrine, this is the statement that I'm using inside my Controller:
$this->getDoctrine()->getRepository('MyBundle:MyTable')->findAll();
but I want the results to be ordered by ascending usernames.
I've been trying to pass an array as an argument this way:
findAll( array('username' => 'ASC') );
but it doesn't work (it doesn't complain either).
Is there any way to do this without building a DQL query?
As #Lighthart as shown, yes it's possible, although it adds significant fat to the controller and isn't DRY.
You should really define your own query in the entity repository, it's simple and best practice.
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
public function findAll()
{
return $this->findBy(array(), array('username' => 'ASC'));
}
}
Then you must tell your entity to look for queries in the repository:
/**
* #ORM\Table(name="User")
* #ORM\Entity(repositoryClass="Acme\UserBundle\Entity\Repository\UserRepository")
*/
class User
{
...
}
Finally, in your controller:
$this->getDoctrine()->getRepository('AcmeBundle:User')->findAll();
$this->getDoctrine()->getRepository('MyBundle:MyTable')->findBy([], ['username' => 'ASC']);
Simple:
$this->getDoctrine()->getRepository('AcmeBundle:User')->findBy(
array(),
array('username' => 'ASC')
);
It's useful to look at source code sometimes.
For example findAll implementation is very simple (vendor/doctrine/orm/lib/Doctrine/ORM/EntityRepository.php):
public function findAll()
{
return $this->findBy(array());
}
So we look at findBy and find what we need (orderBy)
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
This works for me:
$entities = $em->getRepository('MyBundle:MyTable')->findBy(array(),array('name' => 'ASC'));
Keeping the first array empty fetches back all data, it worked in my case.
Look at the Doctrine API source-code :
class EntityRepository{
...
public function findAll(){
return $this->findBy(array());
}
...
}
You need to use a criteria, for example:
<?php
namespace Bundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\Common\Collections\Criteria;
/**
* Thing controller
*/
class ThingController extends Controller
{
public function thingsAction(Request $request, $id)
{
$ids=explode(',',$id);
$criteria = new Criteria(null, <<DQL ordering expression>>, null, null );
$rep = $this->getDoctrine()->getManager()->getRepository('Bundle:Thing');
$things = $rep->matching($criteria);
return $this->render('Bundle:Thing:things.html.twig', [
'entities' => $things,
]);
}
}
findBy method in Symfony excepts two parameters. First is array of fields you want to search on and second array is the the sort field and its order
public function findSorted()
{
return $this->findBy(['name'=>'Jhon'], ['date'=>'DESC']);
}
You can sort an existing ArrayCollection using an array iterator.
assuming $collection is your ArrayCollection returned by findAll()
$iterator = $collection->getIterator();
$iterator->uasort(function ($a, $b) {
return ($a->getPropery() < $b->getProperty()) ? -1 : 1;
});
$collection = new ArrayCollection(iterator_to_array($iterator));
This can easily be turned into a function you can put into your repository in order to create findAllOrderBy() method.
Try this:
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('MyBundle:MyTable')->findBy(array(), array('username' => 'ASC'));
I use an alternative to the solution that wrote nifr.
$resultRows = $repository->fetchAll();
uasort($resultRows, function($a, $b){
if ($a->getProperty() == $b->getProperty()) {
return 0;
}
return ($a->getProperty()< $b->getProperty()) ? -1 : 1;
});
It's quicker than the ORDER BY clause, and without the overhead of the Iterator.
Modify the default findAll function in EntityRepository like this:
public function findAll( array $orderBy = null )
{
return $this->findBy([], $orderBy);
}
That way you can use the ''findAll'' on any query for any data table with an option to sort the query

Resources