Code Style: Doctrine2 queries from entity repository? - symfony

As I understand the best code style is to put the complex SQL/DQL queries to the entity repositories.
For example there is an entity named "News". It has an own entity repository named "NewsRepository".
In the controller there is this code:
/**
* #Route("/news", name="news")
*/
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$paginator = $this->get('knp_paginator');
$news = $paginator->paginate(
$em->createQuery('SELECT n FROM AppBundle:News n'),
$request->query->getInt('page', 1),
10
);
return $this->render('app/news/list.html.twig', array(
'news' => $news,
));
}
Now I like to add further features (filtering, order by, ..). That because I think the query should be moved to any service or the entity repository. But how and what is the best coding style?
(And does anybody have nice generic ideas how to easily add filtering, order by ... ?)

Sure,
Every request should be in the repository if you want use it again. Not only complexiest.
I'll try to answer your first question and after give you a tip that could help you with your "generic filtering"
If you want to put your request in the repo just make in your NewsRepository :
public function findAllOrderedByDate() {
$qb = $this->createQueryBuilder('n');
$qb->orderBy('creationDate');
return $qb->getQuery()->getResult();
}
And in your controller :
public function indexAction(Request $request)
{
$newsRepo = $this->get('doctrine')->getRepository('AppBundle:News');
$em = $this->getDoctrine()->getManager();
$paginator = $this->get('knp_paginator');
$news = $newsRepo->findAllOrderedByDate();
$pagination = $paginator->paginate(
$news,
$request->query->getInt('page', 1),
10
);
return $this->render('app/news/list.html.twig', array(
'news' => $pagination,
));
}
For the filtering, you have a trick in your repor that consist by returning the qb and not directly results.
As well, you can make a function that add you orderBy with a given parameter (using addOrderBy() or andWhere) and which return the queryBuilder. After all of that you can process.
EDIT :
The solution i read on this thread :
public function findAllOrderedByDate(callable $func = null) {
$qb = $this->createQueryBuilder('n');
$qb->orderBy('creationDate');
if (is_callable($func)) {
return $func($qb);
}
return $qb->getQuery()->getResult();
}
and in your controller :
$func = function (QueryBuilder $qb) use ($paginator, $request) {
return $paginator->paginate($qb, $request->query->getInt('page', 1), 10);
};
$pagination = $em->getRepository('AppBundle:News')->findAllOrderedByDate($func);

Model part must be in model part. Move queries to repo's.
And what do you mean, saying this: "nice generic ideas how to easily add filtering, order by"?
OrderBy
Where - same link

Related

return random data form database in random order and with limit

I'm new with symfony and I'm trying to view data from one of my tables with random order and a limit of 4. I tried doing it in the repository but RAND() is not working so I'm trying in the controller.
The error is the following one:
"Warning: array_rand() expects parameter 1 to be array, object given"
And I don't understand why, when in the $response I set the data into an array.
This is my actual code:
/**
* #Route("/ws/random/superviviente", name="ws_random_survi")
*/
public function randomSurvi(Request $request): Response
{
$data = $request->request->all();
$entityManager = $this->getDoctrine()->getManager();
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks();
$response = new JsonResponse();
$response -> setStatusCode(200);
$response -> setData(array('random perk' => $randomPerks));
$resultRandom = array_rand($response);
return $resultRandom;
}
You are trying to use array_rand on a doctrine array collection.
You could either convert it as array and back to a doctrine array :
use Doctrine\Common\Collections\ArrayCollection;
public function randomSurvi(Request $request): Response
{
$data = $request->request->all();
$entityManager = $this->getDoctrine()->getManager();
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks();
$resultRandom = new ArrayCollection(array_rand($randomPerks->toArray()));
return new JsonResponse($resultRandom);
}
Otherwise it would work with shuffle :
$randomPerks = $entityManager->getRepository(Perks::class)->getRandomPerks();
$randomPerks = shuffle($randomPerks);
Or get random perks directly through your method in your repository.
See example from #Krzysztof Trzos:
public function getRandomProducts($amount = 7)
{
return $this->getRandomProductsNativeQuery($amount)->getResult();
}
/**
* #param int $amount
* #return ORM\NativeQuery
*/
public function getRandomProductsNativeQuery($amount = 7)
{
# set entity name
$table = $this->getClassMetadata()
->getTableName();
# create rsm object
$rsm = new ORM\Query\ResultSetMapping();
$rsm->addEntityResult($this->getEntityName(), 'p');
$rsm->addFieldResult('p', 'id', 'id');
# make query
return $this->getEntityManager()->createNativeQuery("
SELECT p.id FROM {$table} p ORDER BY RAND() LIMIT 0, {$amount}
", $rsm);
}
You could write your own query to achieve this, so create a new method inside the repository like so:
public function getRandomPerks(int $limit): array
{
$queryBuilder = $this->createQueryBuilder('p');
return $queryBuilder
->setMaxResults($limit)
->orderBy('RAND()')
->getQuery()
->getResult();
}
Then in your controller all you would to do is call the method and pass a limit:
$randomPerks = $entityManager->getRepository(Perks::class)
->getRandomPerks(4);

Best way to manage filer form and result page

What's the best way to manage filter page and result page in Symfony?
I have a controller that manage filter form and execute query. The result of this query must pass in another controller action. The result must show in another controller action because I used knp_paginator. If I render the result in same controller action of filter form, when change page the controller show filter form and not result.
this is the approach that I used:
Action for create find form:
public function findAction(Request $request)
{
$form = $this->createFindForm($request);
$form->handleRequest($request);
if(($form->isSubmitted() && !$request->isXmlHttpRequest()))
{
if(($this->isValidFindForm($form) && $form->isValid()))
{
$parm = $request->request->get('findForm');
return $this->redirect($this->generateUrl('list_documents',$parm));
}
}
return $this->render(
'myBundle:Documents:document\document_find.html.twig',
array('form' => $form->createView())
);
}
private function createFindForm(Request $request)
{
$form = $this->createForm(
new findDocumentType(
$this->getDoctrine()->getManager(),
$request
),
null,
array(
'action' => $this->generateUrl('find_documents'),
'method' => 'POST',
)
);
return $form;
}
I used $parm = $request->request->get('findForm'); to get the querystring. "findForm" is the name of my filter form.
The redirecting action :
public function listAction(Request $request)
{
$documents = $this->searchDocument($request);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$documents,
$this->get('request')->query->get('page', 1)
);
return $this->render(
'myBundle:Documents:document\document_list.html.twig',
array('pagination' => $pagination)
);
}
The request is passed to search function (request contains querystring)
In search action:
private function searchDocument(Request $request)
{
$parm = $request->query;
$repository = $this->getDoctrine()->getRepository('docliteBundle:Document\\document');
$query = $repository
->createQueryBuilder('d');
....
With $request->query->get() I have access to all parameter.
Hope this help someone.
P.S. for the pagination I used KNP_paginator

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);
}

Sorting Object Array in Symfony on the basis of date & time

//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.

Symfony2 flash messages from Entity Repository

Is there a way to write flash messages from entity repository. I have a Tag and Category entity.
I am adding tags via Category form, where i added a custom input field that receives tags separated with ", ". Every time i submit a form i check if the tags in the input already exist, if not i add them to the database and then i add them to the Category entity.
Here is my tag repository where im trying to write the flashmessage:
namespace Kpr\CentarZdravljaBundle\Entity;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Kpr\CentarZdravljaBundle\Entity\Tags;
use Symfony\Component\HttpFoundation\Request;
class TagsRepository extends EntityRepository
{
public function findByTagInput($arg)
{
$request = Request::createFromGlobals();
$args = explode(", ", $arg);
$em = $this->getEntityManager();
$tagArray = array();
$addaedTags = "";
foreach($args as $name){
$tag = $this->findByName($name);
if(!$tag){
$addaedTags .= $name.", ";
$newTag = new Tags();
$newTag->setName($name);
$newTag->setApproved(1);
$em->persist($newTag);
$tagArray[] = $newTag;
}
}
if($addaedTags!="") $request->get('session')->getFlashBag()->add('info', substr($addaedTags,0,strlen($addaedTags)-2));
$qb = $em->createQueryBuilder();
$qb->add('select', 'tag')
->add('from', 'KprCentarZdravljaBundle:Tags tag')
->add('where', $qb->expr()->in('tag.name', $args));
// $qb instanceof QueryBuilder
$query = $qb->getQuery();
// Set additional Query options
$query->useResultCache('my_cache_id');
$results = $query->getResult();
/*
$sql = "SELECT * FROM categories WHERE $whereS";
$stmt = $this->getEntityManager()->getConnection()->prepare($sql);
$results = $stmt->execute();
*/
$totalResults = array_merge($tagArray, $results);
$tags = new ArrayCollection($totalResults);
return $tags;
}
public function findByName($name)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->add('select', 'tag')
->add('from', 'KprCentarZdravljaBundle:Tags tag')
->add('where', 'tag.name = :namearg')
->setParameter('namearg', $name);
// $qb instanceof QueryBuilder
$query = $qb->getQuery();
$result = $query->getResult();
return $result;
}
}
The error i get:
FatalErrorException: Error: Call to a member function getFlashBag()
on a non-object in /home/kprhr/public_html/CZ_Symfony/src/Kpr/CentarZdravljaBundle/Entity/TagsRepository.php line 31
I don't think you should handle the request from the EntityManager ; their purposes are different. The EntityManager manages the communication between the DB and your application, it's not its purpose to handle session management, neither is it to add flashes messages.
This job should go to a dedicated service, which will handle that.
Now, your error comes from the way you get your request. You are actually creating a brand new object, which is not properly instanciated, by using the static method createFromGlobals. Instead, you should handle the flash messages in your controller.
This would translate as something like that:
//Controller
public function mypageAction()
{
// Your logic
if (count($args) > 0) {
$this->getRequest()->get('session')->getFlashBag()->add('info', implode(',', $args));
}
// ...
}

Resources