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);
}
Related
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();
There is a standard feature in sonata-admin-bundle to export data using exporter; But how to make export current entity AND mapped ManyToOne entity with it?
Basically what I want, is to download exactly same data as defined in ListFields.
UPD: In docs, there is only todo
UPD2: I've found one solution, but I do not think it is the best one:
/**
* Add some fields from mapped entities; the simplest way;
* #return array
*/
public function getExportFields() {
$fieldsArray = $this->getModelManager()->getExportFields($this->getClass());
//here we add some magic :)
$fieldsArray[] = 'user.superData';
$fieldsArray[] = 'user.megaData';
return $fieldsArray;
}
I created own source iterator inherited from DoctrineORMQuerySourceIterator.
If value in method getValue is array or instance of Traversable i call method getValue recursive to get value for each "Many" entity:
protected function getValue($value)
{
//if value is array or collection, creates string
if (is_array($value) or $value instanceof \Traversable) {
$result = [];
foreach ($value as $item) {
$result[] = $this->getValue($item);
}
$value = implode(',', $result);
//formated datetime output
} elseif ($value instanceof \DateTime) {
$value = $this->dateFormater->format($value);
} elseif (is_object($value)) {
$value = (string) $value;
}
return $value;
}
In your admin class you must override method getDataSourceIterator to return your own iterator.
This
$this->getModelManager()->getExportFields($this->getClass());
returns all entity items. Better practice is to create explicit list of exported items in method getExportFields()
public function getExportFields()
{
return [
$this->getTranslator()->trans('item1_label_text') => 'entityItem1',
$this->getTranslator()->trans('item2_label_text') => 'entityItem2.subItem',
//subItem after dot is specific value from related entity
....
Key in array is used for export table headers (here is traslated).
//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'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
I am using the FOS bundle and I want to retrieve all users with a given ROLE from the database.
What is the best way to do this?
Just add this in your UserRepository or replace $this->_entityName by YourUserBundle:User:
/**
* #param string $role
*
* #return array
*/
public function findByRole($role)
{
$qb = $this->_em->createQueryBuilder();
$qb->select('u')
->from($this->_entityName, 'u')
->where('u.roles LIKE :roles')
->setParameter('roles', '%"'.$role.'"%');
return $qb->getQuery()->getResult();
}
If you are using FOSUser Groups you should use:
/**
* #param string $role
*
* #return array
*/
public function findByRole($role)
{
$qb = $this->_em->createQueryBuilder();
$qb->select('u')
->from($this->_entityName, 'u')
->leftJoin('u.groups', 'g')
->where($qb->expr()->orX(
$qb->expr()->like('u.roles', ':roles'),
$qb->expr()->like('g.roles', ':roles')
))
->setParameter('roles', '%"'.$role.'"%');
return $qb->getQuery()->getResult();
}
Well, if there is no better solution, I think I will go to a DQL query:
$query = $this->getDoctrine()->getEntityManager()
->createQuery(
'SELECT u FROM MyBundle:User u WHERE u.roles LIKE :role'
)->setParameter('role', '%"ROLE_MY_ADMIN"%');
$users = $query->getResult();
If you have this requirement and your user list will be extensive, you will have problems with performance. I think you should not store the roles in a field as a serialized array. You should create an entity roles and many to many relationship with the users table.
As #Tirithen states, the problem is that you will not get the users that have an implicit role due to role hierarchy. But there is a way to work around that!
The Symfony security component provides a service that gives us all child roles for a specific parent roles. We can create a service that does almost the same thing, only it gives us all parent roles for a given child role.
Create a new service:
namespace Foo\BarBundle\Role;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Core\Role\Role;
/**
* ReversedRoleHierarchy defines a reversed role hierarchy.
*/
class ReversedRoleHierarchy extends RoleHierarchy
{
/**
* Constructor.
*
* #param array $hierarchy An array defining the hierarchy
*/
public function __construct(array $hierarchy)
{
// Reverse the role hierarchy.
$reversed = [];
foreach ($hierarchy as $main => $roles) {
foreach ($roles as $role) {
$reversed[$role][] = $main;
}
}
// Use the original algorithm to build the role map.
parent::__construct($reversed);
}
/**
* Helper function to get an array of strings
*
* #param array $roleNames An array of string role names
*
* #return array An array of string role names
*/
public function getParentRoles(array $roleNames)
{
$roles = [];
foreach ($roleNames as $roleName) {
$roles[] = new Role($roleName);
}
$results = [];
foreach ($this->getReachableRoles($roles) as $parent) {
$results[] = $parent->getRole();
}
return $results;
}
}
Define your service for instance in yaml and inject the role hierarchy into it:
# Provide a service that gives you all parent roles for a given role.
foo.bar.reversed_role_hierarchy:
class: Foo\BarBundle\Role\ReversedRoleHierarchy
arguments: ["%security.role_hierarchy.roles%"]
Now you are ready to use the class in your own service. By calling $injectedService->getParentRoles(['ROLE_YOUR_ROLE']); you will get an array containing all parent roles that will lead to the 'ROLE_YOUR_ROLE' permission. Query for users that have one or more of those roles... profit!
For instance, when you use MongoDB you can add a method to your user document repository:
/**
* Find all users with a specific role.
*/
public function fetchByRoles($roles = [])
{
return $this->createQueryBuilder('u')
->field('roles')->in($roles)
->sort('email', 'asc');
}
I'm not into Doctrine ORM but I'm sure it won't be so different.
You can use just this on your DQL:
SELECT u FROM YourFavouriteBundle:User u WHERE u.roles [NOT] LIKE '%ROLE_YOUR_ROLE%'
Of course with QueryBuilder it's more elegant:
// $role = 'ROLE_YOUR_ROLE';
$qb->where('u.roles [NOT] LIKE :role')
->setParameter('role', "%$role%");
Finally i solved it, following is an exact solution:
public function searchUsers($formData)
{
$em = $this->getEntityManager();
$usersRepository = $em->getRepository('ModelBundle:User');
$qb = $usersRepository->createQueryBuilder('r');
foreach ($formData as $field => $value) {
if($field == "roles"){
$qb->andWhere(":value_$field MEMBER OF r.roles")->setParameter("value_$field", $value);
}else{
$qb->andWhere("r.$field = :value_$field")->setParameter("value_$field", $value);
}
}
return $qb->getQuery()->getResult();
}
Cheers!
In case you need to filter users by role using a DQL filter in a YAML file (In EasyAdminBundle for instance)
entities:
Admin:
class: App\Entity\User
list:
dql_filter: "entity.roles LIKE '%%ROLE_ADMIN%%'"
Here I give an alternative solution :
I find users of roles for a given array
In controller I call the function like that
$users = $userRepository->findUsersOfRoles(['ROLE_ADMIN', 'ROLE_SUPER_USER']);
Then in my repository I make a loop to generate condition and set the parameters :
public function findUsersOfRoles($roles)
{
$condition = 'u.roles LIKE :roles0';
foreach ($roles as $key => $role){
if ($key !== 0){
$condition .= " OR u.roles LIKE :roles".$key;
}
}
$query = $this->createQueryBuilder('u')
->where($condition);
foreach ($roles as $key => $role){
$query ->setParameter('roles'.$key, '%"'.$role.'"%');
}
return $query->getQuery() ->getResult();
}