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
Related
I am making a query with Doctrine which calculates a custom field using a CASE WHEN like this:
public function findLatestPaginator($page = 1, $itemsPerPage)
{
$qb = $this->createQueryBuilder('n');
$qb = $qb
->select(['n AS news', 'CASE WHEN lu.id IS NOT NULL THEN 1 ELSE 0 END AS n.liked'])
->leftJoin('n.likingUsers', 'lu')
;
$qb = $qb
->orderBy('n.date', 'DESC')
->setFirstResult($itemsPerPage * ($page - 1))
->setMaxResults($itemsPerPage)
->getQuery()
;
return $qb->getResult();
}
In my entity I have a field called $liked which is not mapped. Is it possible to make the query (or the hydrator?) automatically set the field on the resulting entity?
Right now I am making a foreach loop and manually setting the property:
/**
* #return News[]
*/
private function convertNews(array $records)
{
$newsList = [];
foreach ($records as $record) {
if (isset($record['liked'], $record['news'])) {
/** #var News */
$news = $record['news'];
$news->liked = boolval($record['liked']);
$newsList[] = $news;
}
}
return $newsList;
}
Maybe a DTO could be useful here : doctrine documentation
Basically, you define a PHP class which is not mapped to your model.
You can then select what you need and trigger the instantiation of an object of this PHP class from the DQL query.
Hope this helps !
Default hydrator does not include unmapped properties (wow, right?).
You want to override/extend doctrine/orm/lib/Docrtrine/ORM/Internal/Hydration/AbstractHydrator
line 386 where it only checks for $classMetadata fieldMappings
Or you can hack around by using DTO and query in transformer, if performance is not needed
Orders // orders
Comments // comments for every order
I would like to find latest comment written in this order.
My
Controller:
$orders = $this->getDoctrine()->getRepository(Orders::class)->findAll();
foreach($orders as $order) {
$temp = array(
$order->getId(),
$order->getComments()->findLatest( $order->getId() )
Entity (Comments):
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Orders", inversedBy="comments")
*/
private $orders;
Entity(Order):
/**
* #return Collection|Comment[]
*/
public function getComments(): Collection
{
return $this->comments;
}
Comment Repository:
public function findLatest($value)
{
return $this->createQueryBuilder('c')
->andWhere('c.orders = :val')
->setParameter('val', $value)
->orderBy('c.id', 'DESC')
->setMaxResults(1)
->getQuery()
->getResult()
;
}
But looks like it not working in this way :(
Error:
Attempted to call an undefined method
named "findLatest" of class "Doctrine\ORM\PersistentCollection".
you are trying to call a repository function from another entity
try to change this line :
$order->getComments()->findLatest( $order->getId()
with:
$this->getDoctrine()->getRepository(Comments::class)->findLatest($order->getId);
a better soulution will be that you work with $orders->getComments() array to avoid requesting data from the database inside a loop
You can do this using the class Doctrine\Common\Collections\Criteria.
Entity(Order):
use Doctrine\Common\Collections\Criteria;
...
/**
* Returns the latest comment or false if no comments found under that criteria
*/
public function findLatestComment()
{
$criteria = Criteria::create()
->orderBy(array("id" => Criteria::DESC))
;
return $this->getComments()->matching($criteria)->first();
}
And then you can simply use it like this:
$order->findLatestComment();
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);
}
//Suppose Entity Notes has property 'creationdate' & 'getCreationDate()' method to access.
DefaultController extends Controller {
public function indexAction(){
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('Bundle:Notes');
$notes = $repository->findBy(array('userid' => $userId);
//Now I want to sort the notes array as per creation date using usort
usort($notes, array($this,"cmp"));
}
function cmp($a, $b) {
return strtotime($a->getCreationDate()) > strtotime($b->getCreationDate())? -1:1;
}
}
You can set the order in your call to the repository rather than after like so...
$notes = $repository->findBy(
array('userid' => $userId), // search criteria
array('creationdate' => 'ASC') // order criteria
);
I know you said you wanted to use usort but it seems kind of unnecessary.
I'm trying to display a form with a collection. The collection should display an empty sub-form. Due to the projects nature I can't rely on JavaScript to do so.
Googling didn't help and I does not seem to work by adding an empty entity to the collection field.
What I have so far:
public function indexAction($id)
{
$em = $this->getDoctrine()->getManager();
$event = $em->getRepository('EventBundle:EventDynamicForm')->find($id);
$entity = new Booking();
$entity->addParticipant( new Participant() );
$form = $this->createForm(new BookingType(), $entity);
return array(
'event' => $event,
'edit_form' => $form->createView()
);
}
In BookingType.php buildForm()
$builder
->add('Participants', 'collection')
In the Twig template
{{ form_row(edit_form.Participants.0.companyName) }}
If I put the line $entity->addParticipant( new Participant() ); in indexAction() I get an error saying:
The form's view data is expected to be of type scalar, array or an
instance of \ArrayAccess, but is an instance of class
Yanic\EventBundle\Entity\Participant. You can avoid this error by
setting the "data_class" option to
"Yanic\EventBundle\Entity\Participant" or by adding a view transformer
that transforms an instance of class
Yanic\EventBundle\Entity\Participant to scalar, array or an instance
of \ArrayAccess.
If I delete the said line Twig complains:
Method "0" for object "Symfony\Component\Form\FormView" does not exist in
/Applications/MAMP/htdocs/symfony-standard-2.1/src/Yanic/EventBundle/Resources/views/Booking/index.html.twig
at line 27
EDIT: The addParticipant is the default methos generated by the doctrine:generate:entities command
/**
* Add Participants
*
* #param \Yanic\EventBundle\Entity\Participant $participants
* #return Booking
*/
public function addParticipant(\Yanic\EventBundle\Entity\Participant $participants)
{
$this->Participants[] = $participants;
return $this;
}
I'm sure that I'm doing something wrong, but can't find the clue :-(
I guess you are a bit lost on Symfony2 form collection, though I think you already read http://symfony.com/doc/current/cookbook/form/form_collections.html.
Here I will just emphasize the doc, help other SO readers, and exercise myself a bit on answering question.. :)
First, you must have at least two entities. In your case, Booking and Participant. In Booking entity, add the following. Because you use Doctrine, Participant must be wrapped in ArrayCollection.
use Doctrine\Common\Collections\ArrayCollection;
class Booking() {
// ...
protected $participants;
public function __construct()
{
$this->participants = new ArrayCollection();
}
public function getParticipants()
{
return $this->participants;
}
public function setParticipants(ArrayCollection $participants)
{
$this->participants = $participants;
}
}
Second, your Participant entity could be anything. Just for example:
class Participant
{
private $name;
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
}
Third, your BookingType should contain collection of ParticipantType, something like this:
// ...
$builder->add('participants', 'collection', array('type' => new ParticipantType()));
Fourth, the ParticipantType is straightforward. According to my example before:
// ...
$builder->add('name', 'text', array('required' => true));
Last, in BookingController, add the necessary amount of Participant to create a collection.
// ...
$entity = new Booking();
$participant1 = new Participant();
$participant1->name = 'participant1';
$entity->getParticipants()->add($participant1); // add entry to ArrayCollection
$participant2 = new Participant();
$participant2->name = 'participant2';
$entity->getParticipants()->add($participant2); // add entry to ArrayCollection
think you have to add here the type:
->add('Participants', 'collection', array('type' => 'YourParticipantType'));
Could you also paste in here the declaration of your addParticipant function from the model? Seems that there's something fishy too.