Custom QueryBuilder in Doctrine - php

I have this problem, I would like to create "smart" criteria. Suppose there's a model of 1 Author : n Books.
So, instead of:
$qb = $em->getRepository('Books')->createQueryBuilder('b')
->join('b.author', 'a')
->where('a.dod is null')
->where('a.name = :name')
->setParameter('name', 'Mozart');
;
...i'd like to do something like:
$qb = $em->getRepository('Books')->createQueryBuilder('b')
->whereAuthorIsAlive()
->whereAuthorName('Mozart');
I am aware of the possibility to creating custom EntityManager but this is not quite it. Custom QueryBuider would be more suitable.

You could extend the QueryBuilder with your custom methods, but have a little overhead by overwriting the createQueryBuilder method of your repository:
Extend the default QueryBuilder class:
class BookQueryBuilder extends \Doctrine\ORM\QueryBuilder
{
public function whereAuthorIsAlive(): self
{
return $this->join($this->getRootAlias() . '.author', '_a')
->andWhere('_a.alive = true');
}
}
In your Repository, overwrite the createQueryBuilder method:
class BookRepository extends EntityRepository
{
public function createQueryBuilder($alias, $indexBy = null)
{
return (new BookQueryBuilder($this->_em))
->select($alias)
->from($this->_entityName, $alias, $indexBy);
}
}
Use the new method
$qb = $em->getRepository('Books')->createQueryBuilder('b')
->whereAuthorIsAlive();

I used such kind of the same in a repository.
I created methods in the repository class that added QueryBuilder parts to a query.
In example, based on yours :
namespace App\Repository;
class BooksRepository extends EntityRepository
{
private function whereAuthorIsAlive($qb)
{
$qb->where('a.dod is null');
return $qb;
}
private function whereAuthorName($qb, $name)
{
$qb->where('a.name = :name')
->setParameter('name', $name);
return $qb;
}
public function getBooksByAliveAuthorName($name)
{
$qb = $this->createQueryBuilder('b')
->join('b.author', 'a')
$qb = $this->whereAuthorIsAlive($qb);
$qb = $this->whereAuthorName($qb, $name);
return $qb->getQuery()->getResult();
}
}
To register this repository with your entity :
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\BooksRepository")
*/
class Books
{
// your entity
}
And then, in a controller :
$books = $this->getDoctrine()
->getRepository('App:Books')
->getBooksByAliveAuthorName('Mozart');

Related

Symfony 3.4 Doctrine: Change referenced table on runtime

In my symfony application products are stored in the table products. There is an Entity Product and Repository ProductRepository for it.
Some products automatically get archived and stored in another table, called products_archived. I created an Entity ProductArchived and duplicated the ProductRepository file to the file ProductArchivedRepository.php.
The tables products and products_archived have exactly the same structure and fields.
My goal:
When in the code a product is identified as an archived product, I want to be able to apply a function from the ProductRepository and NOT having to refer to a separate ProductArchivedRepository. I want to avoid having to use duplicated code.
Example:
ProductRepository.php:
public function getProductDataById($productId)
{
$qb = $this->createQueryBuilder('p');
// ...
return $qb->getQuery()->getArrayResult();
}
ProductArchivedRepository.php:
public function getProductDataById($productId)
{
$qb = $this->createQueryBuilder('p');
// ...
return $qb->getQuery()->getArrayResult();
}
ProductService.php:
public function getProductDataById($productId)
{
$repoProduct = $this->productRepository;
$repoProductArchived = $this->container->productArchivedRepository;
if ($repoProduct->findOneBy(['id' => $productId]) instanceof Product) {
$repoP = $repoProduct;
} else if ($repoProductArchived->findOneBy(['id' => $productId]) instanceof ProductArchived) {
$repoP = $repoProductArchived;
} else {
throw new NotFoundHttpException(
'Product neither found in table product nor in table product_archived.'
);
}
$productData = $repoP->getProductDataById($productId);
return $productData;
}
How do I achieve that ProductArchivedRepository.php becomes redundant?
You can use an abstract class that regroup the duplicated method(s)
abstract class AbstractProductRepository extends EntityRepository
{
public function getProductDataById($productId)
{
$qb = $this->createQueryBuilder('p');
// ...
return $qb->getQuery()->getArrayResult();
}
}
And now, both of your repository can extend the abstract class instead of EntityRepository :
class ProductRepository extends AbstractProductRepository
{
// ...
}
class ProductArchivedRepository extends AbstractProductRepository
{
// ...
}

How to dry-up similar Doctrine functions from two Entity Repositories?

I'm using Symfony 4.
namespace App\Repository;
use ...
class CountryRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Country::class);
}
...
public function deleteMultipleByIds(array $ids): void
{
$builder = $this->createQueryBuilder('l');
$builder->delete()
->where('l.id IN(:ids)')
->setParameter(
':ids',
$ids,
Connection::PARAM_INT_ARRAY
);
$query = $builder->getQuery();
$query->execute();
}
Same method exists in CountryI18nRepository class.
I'd want there to be just one function like that, which will just use correct Entity (Country v CountryI18n).
How and where do I create a new class? Should that class be of ServiceEntitiyRepository class or otherwise which?
If your problem is about duplication, you can make a GenericRepo (not necessarily a doctrine repository; please choose a better name) that you can inject and use where you need.
Something like
class GenericRepo
{
public function deleteMultipleByIds(QueryBuilder $qb, string $rootAlias, array $ids): void
{
$qb->delete()
->where(sprintf('%s.id IN(:ids)', $rootAlias))
->setParameter(':ids', $ids, Connection::PARAM_INT_ARRAY);
$qb->getQuery()->execute();
}
}
And in your, for instance CountryI18nRepository
class CountryI18nRepository
{
private $genericRepo;
public function __construct(GenericRepo $genericRepo)
{
$this->genericRepo = $genericRepo;
}
public function deleteMultipleByIds(array $ids): void
{
$builder = $this->createQueryBuilder('l');
$this-> genericRepo->deleteMultipleByIds($builder, 'l', $ids);
}
}
You could also extend from GenericRepo but, since PHP only supports single inheritance, is better (at least in my opinion) to use the composition as shown above.
Disclaimer
I didn't tested this code so it is possible that some adjustment will needed. Concepts shown btw are valid.
create an abstract repository with the deleteMultipleByIds like :
abstract class BaseCountryRepository extends ServiceEntityRepository
and extend it instead of ServiceEntityRepository in the other CountryRepositories
class CountryRepository extends BaseCountryRepository
class CountryI18nRepository extends BaseCountryRepository
you can remove the deleteMultipleByIds definition from these classes

symfony2 method undefined in EntityRepository

i want create a request from my EntityRepository so this is my code:
<?php
namespace zhr\myprojectBundle\Entity;
use Doctrine\ORM\EntityRepository;
/**
* myprojectdbEntityRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class myprojectdbEntityRepository extends EntityRepository
{
public function getAll()
{
$qb = $this->createQueryBuilder('s');
$query = $qb;
$result = $qb->getQuery()->execute();
}
}
i want use it in my controller file so this is my code:
public function searchusersAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository('myprojectBundle:User');
$utilisateur = $repository->getAll();
var_dump($utilisateur); die();
//return $this->render('mypageBundle:Admin:adminindex.html.twig', array('sheet'=>$utilisateur));
}
i get a error:
Undefined method 'getAll'. The method name must start with either findBy or findOneBy!
??? normaly all methode in repository file must be defined in my controller file no ?
thanks first guys
You have to use getResult() to return a result as follow:
public function getAll()
{
return $this->createQueryBuilder('s')->getQuery()->getResult();
}
But you really don't need this kind of function since EntityRepository class already has findAll() function to get all rows from an entity:
$em = $this->getDoctrine()->getManager();
$rows = $em->getRepository('myprojectBundle:User')->findAll();
I suggest you to check documentation for QueryBuilder and EntityRepository api.

Symfony 2.5 and own models

In Symfony I'm using default ORM Doctrine, but this tool can't give me enough methods to manipulate with different cases. I want to write my own classes and using something like DBAL, just for connections making custom SQL queries and fetch the result. Who can give me some examples? Which classes I should use to make my model layer, extend my functionality.
If you want to write your own SQL query with doctrine you can check that doc page:
http://doctrine-orm.readthedocs.org/en/latest/reference/native-sql.html
but most of the time DQL is more than enough
this is from a project I did, ignore my sql query
//Daily Alerts
$dailyAlertsQuery = $em
->createQuery("
SELECT COUNT (a) AS daily
FROM XXX\xxxBundle\Entity\Alert a
JOIN a.user u
WHERE a.mentionUpdate = '1'
AND u.isActive = '1'
")
->getResult();
$dailyAlerts = new ArrayCollection($dailyAlertsQuery);
As a good practise, you may put all your custom queries (SQL or DQL) in an EntityRepository
Here is an example of a custom EntityRepository
use Doctrine\ORM\EntityRepository;
use \Doctrine\Common\Collections\Criteria;
/**
* RequestRepository
*
*/
class RequestRepository extends EntityRepository
{
public function findByStatus($status = array(), $limit = 5, $orderBy = null)
{
$queryBuilder = $this->_em->createQueryBuilder();
$queryBuilder
->select('n')
->from('BaseBundle:Request', 'n')
->where('n.status IN (:status)')
->setParameter('status', $status)
->setMaxResults($limit);
if (!is_null($orderBy)) {
$queryBuilder->orderBy('n.' . $orderBy[0], $orderBy[1]);
}
$lines = array();
foreach ($queryBuilder->getQuery()->getResult() as $line) {
$lines[] = $line;
}
return $lines;
}
public function getByExpressions($expressions = array(), $limit = 5, $orderBy = null)
{
$criteria = Criteria::create();
if (!empty($expressions)) {
foreach ($expressions as $expression) {
$criteria->andWhere($expression);
}
}
$criteria->setMaxResults($limit);
if (!is_null($orderBy)) {
$criteria->orderBy($orderBy);
}
return $this->matching($criteria);
}
}
And in the Entity code, you define this custom repository as follows:
use Doctrine\ORM\Mapping as ORM;
/**
* Request
*
* #ORM\Table(name="request")
* #ORM\Entity(repositoryClass="BaseBundle\Repository\RequestRepository")
*/
class Request
{
//Entity code, irrelevant here
}

Method 'QueryBuilder' not found in class ObjectRepository

New to Symfony & Doctrine
I trying to fetch a selection of objects from a MySQL database via Doctrine in a Symfony project. I am doing this with createQueryBuilder;
$repository = $this->getDoctrine()
->getRepository('mcsyncBundle:DB');
$query = $repository->createQueryBuilder('p')
->select('p')
->where('(p.created=:)' . $till)
->setParameter('created', $till)
->orderBy('p.created', 'ASC')
->getQuery();
$PackDB = $query->getResult();
But I keep getting the error:
*Method 'QueryBuilder' not found in class \Doctrine\Common\Persistence\ObjectRepository*.
Anyone that can explain (fix) this problem?
EDIT: This error is coming from inside PHPStorm by the way and NOT from Symfony itself
I suppose you wrote that code in a Controler file. QueryBuilders are meant to be in Repository files. For /Entity/Plop.php, you should have /Entity/PlopRepository.php as well.
PlopRepository.php
namespace Foo\BarBundle\Entity;
use Doctrine\ORM\EntityRepository;
class PlopRepository extends EntityRepository
{
public function getCreatedP()
{
$qb = $this->createQueryBuilder('p')
->select('p')
->where('p.created = :created')
->setParameter('created', $till)
->orderBy('p.created', 'ASC');
return $qb->getQuery()
->getResults();
}
// ...
}
EDIT 1 : and there was a mistake in your ->where('...') statement I fixed ;)
EDIT 2 : to be complete, controller part :
TotoController.php
public function getPlopAction()
{
$entityManager = $this->getDoctrine()->getManager();
$plopRepository = $entityManager->getRepository('FooBarBundle:Plop');
$plops = $plopRepository->getCreatedP();
// ...
}

Categories