symfony3 doctrine. Make condition for related records - symfony

i have entity ShopGoods with relation manyToMany to entity ShopCategory. That's my method for get list of goods:
public function getListByFilter(ShopGoodsFilter $filter) {
$qb = $this->createQueryBuilder('goods')
->select('goods')
->leftJoin('goods.categories', 'categories')
->where('categories.id = :category_id')
->andWhere('goods.status=1')
->andWhere('categories.status != 0') // it's my try...
->setParameter('category_id', $filter->getCategory()->getId())
->setMaxResults($filter->getLimit())
->setFirstResult($filter->getOffset());
return [
'data' => $qb->getQuery()->getResult(),
'total' => $this->getTotalByFilter($filter)
];
}
I can't understand how make condition to get goods with categories.status = 1 for all related categories.

You could use something like:
$qb = $this->createQueryBuilder('goods')
->select('goods')
->leftJoin('goods.categories', 'categories', 'WITH', 'categories.status = 1')

Related

Symfony Doctrine findOneBy with not equal to

I need to find a phone number in a recording where the contract ID is not equal to current contract ID.
It is easy to find in a specific contract ID. $value is the entity instance in my custom validator.
$existingPhone = $this->contractRepository->findOneBy(['phone' => $value->getPhone(), 'contractId' => $value->getContractId()]);
but how to find in other than the current contract ID?
You need to create a method in your contractRepository, and use the Doctrine QB.
$qb = $this->createQueryBuilder('c');
$qb
->where('c.phone = :phone')
->andWhere(
$qb->expr()->neq('c.contractId', 'contractId')
)
->setParameters([
'phone' => $phone,
'contractId' => $contractId,
])
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();

Doctrine Query with Multiple Joins only populating Main Entity

So far I have tried the following but I keep only getting the main Entity information joined entities do not make it to the result:
Option 1(Using ResultSetMapping Builder):
$rsm = new ResultSetMappingBuilder(
$this->_em,
ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT
);
$rsm->addRootEntityFromClassMetadata(
'CountryApp\StoreBundle\Entity\Product', 'p'
);
$rsm->addJoinedEntityFromClassMetadata(
'CountryApp\StoreBundle\Entity\Category', 'c', 'p', 'category'
);
$rsm->addJoinedEntityFromClassMetadata(
'CountryApp\StoreBundle\Entity\CustomerProductPrice', 'cpp', 'p', 'customerPrices'
);
$result = $this->_em
->createNativeQuery(
'
SELECT
p.id,
p.code,
p.name,
p.cost,
p.rrp,
p.status,
p.notes,
p.out_of_stock_since,
p.available_in,
c.id,
c.name,
c.code,
cpp.id,
cpp.price
FROM product as p
JOIN category as c ON c.id = p.category_id AND p.status != "DELETED"
LEFT JOIN customer_product_price as cpp ON cpp.product_id = p.id AND cpp.customer_id = :customer
', $rsm
)
->setParameter('customer', $customerId)
->getResult(Query::HYDRATE_ARRAY)
;
Option 2:(using QueryBuild and FetchMode)
$qb = $this->createQueryBuilder('p');
$result = $qb
->select('p')
->addSelect('c')
->addSelect('cpp')
->join(
'CountryApp\StoreBundle\Entity\Category',
'c',
Join::WITH,
$qb->expr()
->eq('c', 'p.category')
)
->leftJoin(
'CountryApp\StoreBundle\Entity\CustomerProductPrice',
'cpp',
Join::WITH,
$qb->expr()
->andX(
$qb->expr()
->eq('p', 'cpp.product'),
$qb->expr()
->eq('cpp.customer', ':customer')
)
)
->setParameter('customer', $customerId)
->getQuery()
->setFetchMode(
'CountryApp\StoreBundle\Entity\Category', 'product', ClassMetadata::FETCH_EAGER
)
->setFetchMode(
'CountryApp\StoreBundle\Entity\CustomerProductPrice', 'product', ClassMetadata::FETCH_EAGER
)
->getResult(Query::HYDRATE_ARRAY)
;
Please advise your thoughts as to what could make this work. I want to obtain the following structure:
[
0 => [
Product[
..
]
Category[
..
]
CustomerProductPrice[
..
]
],
1 => [
Product[
..
]
Category[
..
]
CustomerProductPrice[
..
]
],
..
.
]
While using Doctrine you define your relationships inside of your entity.
You can read more here https://symfony.com/doc/current/doctrine/associations.html always read the documentation and best practices. I dunno if you are using Symfony or not, but this is a great example and more understandable than Doctrine docs.
/**
* #ORM\Entity()
*/
class Product
{
// ...
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="products")
*/
private $category;
public function getCategory(): ?Category
{
return $this->category;
}
public function setCategory(?Category $category): self
{
$this->category = $category;
return $this;
}
}
As you see here you define an entity that holds all associations and properties.
Normally association will be lazy loaded by default if you call $product->getCategory() you category will be lazy loaded. If you do not like lazy loading you can always eager fetch with
/**
* #ManyToOne(targetEntity="Category", cascade={"all"}, fetch="EAGER")
*/
And you will receive an array of products where each product will have a property named category and will contain Category entity inside it.
This is the main difference between CakePHP because in CakePHP you get all associations separately and in Symfony, you get a tree of associations.
And your queries seems too complex and in most cases, you do not have to modify queries like that at all. But be careful with lazy loads if you lazy load data on huge lists you will end up with poor performance.

Doctrine findBy with multiple conditional fields

I have some bad data in the table but not all fields have null values and I want to get all bad rows with one iteration by implementing something like this, if possible.
$contactObj = $this->em->getRepository("ImporterBundle:Contact")
->findBy([$field1 => null OR $field2 => null OR $field3 => null ...]);
Are there is a solution to do something like above example without using repositoryClass?
You can use the matching(Criteria $criteria) function of Doctrine\ORM\EntityRepository:
use Doctrine\Common\Collections\Criteria;
$criteria = Criteria::create()
->where(Criteria::expr()->isNull('functions'))
->orWhere(Criteria::expr()->isNull('salutation'))
->orWhere(Criteria::expr()->isNull('category'))
->orWhere(Criteria::expr()->isNull('address'))
->orWhere(Criteria::expr()->isNull('city'))
->orWhere(Criteria::expr()->isNull('company'));
$contactObj = $this->em->getRepository("ImporterBundle:Contact")
->matching($criteria);
Doctrine expression builder
So I just need to use repository class
And use expressions.
public function getBadData() {
$db = $this->createQueryBuilder('c');
return $db->select('c')
->where($db->expr()->isNull('c.functions'))
->orWhere($db->expr()->isNull('c.salutation'))
->orWhere($db->expr()->isNull('c.category'))
->orWhere($db->expr()->isNull('c.address'))
->orWhere($db->expr()->isNull('c.city'))
->orWhere($db->expr()->isNull('c.company'))
->getQuery()
->getResult();
}
As JimL suggested, I had change repository method by using orX()
public function getBadData() {
$db = $this->createQueryBuilder('c');
return $db->select('c')
->where($db->expr()->orX()->addMultiple(
[
'c.functions is null',
'c.salutation is null',
'c.category is null',
'c.address is null',
'c.city is null',
'c.company is null'
]
))
->getQuery()
->getResult();
}
Which now should be more readable.

Doctrine / Symfony: convert custom type to database value before using QueryBuilder

I have defined a custom Doctrine data type for Uuid. When I search for an object using find($uuid), it works correctly, i.e. the attribute is converted using convertToDatabaseValue() before executing the query, and converted back with convertToPhpValue() when value is retrieved.
The conversion doesn't work if I use the QueryBuilder. Example:
$qb = $this->createQueryBuilder('s');
$qb = $qb->where( //some conditions...
$qb->expr()->eq( 's.uuid', ':uuid' ))->setParameter( 'uuid', $uuid );
I found two similar unanswered questions:
Symfony Doctrine datatype only works in findBy not querybuilder
Doctrine 2 Custom Types
It looks like that the conversion is in fact ignored.
How can I force the conversion of the parameter before executing the query? Is there a way to access the convertToDatabaseValue() function of the custom data type from the repository?
Thanks
Yes setParameter() has third parameter, but the type of third param as string is worked for me not the object.
You can do it in following way.
$qb = $this->createQueryBuilder('s');
$qb = $qb->where( //some conditions...
$qb->expr()->eq( 's.uuid', ':uuid' ))->setParameter( 'uuid', $uuid, 'uuid' );
If you dont know what exactly key is for datatype 'uuid' is.
Then use print_r(Type::getTypesMap()); to get list of all dataypes added.
In my case it was
Array
(
[array] => Doctrine\DBAL\Types\ArrayType
[simple_array] => Doctrine\DBAL\Types\SimpleArrayType
[json_array] => Doctrine\DBAL\Types\JsonArrayType
[object] => Doctrine\DBAL\Types\ObjectType
[boolean] => Doctrine\DBAL\Types\BooleanType
[integer] => Doctrine\DBAL\Types\IntegerType
[smallint] => Doctrine\DBAL\Types\SmallIntType
[bigint] => Doctrine\DBAL\Types\BigIntType
[string] => Doctrine\DBAL\Types\StringType
[text] => Doctrine\DBAL\Types\TextType
[datetime] => Doctrine\DBAL\Types\DateTimeType
[datetimetz] => Doctrine\DBAL\Types\DateTimeTzType
[date] => Doctrine\DBAL\Types\DateType
[time] => Doctrine\DBAL\Types\TimeType
[decimal] => Doctrine\DBAL\Types\DecimalType
[float] => Doctrine\DBAL\Types\FloatType
[binary] => Doctrine\DBAL\Types\BinaryType
[blob] => Doctrine\DBAL\Types\BlobType
[guid] => Doctrine\DBAL\Types\GuidType
[geometry] => CrEOF\Spatial\DBAL\Types\GeometryType
[point] => CrEOF\Spatial\DBAL\Types\Geometry\PointType
[polygon] => CrEOF\Spatial\DBAL\Types\Geometry\PolygonType
[linestring] => CrEOF\Spatial\DBAL\Types\Geometry\LineStringType
)
And my doctrine code was something like this.
$queryBuilder = $this->createQueryBuilder('c');
$queryBuilder
->where('st_contains(:polygon, point(c.latitude, c.longitude) ) = 1')
->setParameter('polygon', $city->getPolygon(), 'polygon');
Here's the solution: the function setParameter() has a third argument $type which is used to declare the typology of the parameter. The custom declared type can be retrieved with the getType() function of the Doctrine Type class:
$qb = $this->createQueryBuilder('s');
$qb = $qb->where( //some conditions...
$qb->expr()->eq( 's.uuid', ':uuid' ))->setParameter( 'uuid', $uuid, Type::getType('uuid') );

Symfony2 - Best practice to refactor code/methods?

I'm using a search code and pagination code in my controller(s), it goes without saying it's bad coding habits by repeating code. That being said what is the best practice in Symfony2, to avoid repeating code in all my controllers?
And how do I access the code once it's been re-factored?
Controller
// Search code
$results = null;
$query = $request->query->get('q');
if (!empty($query)) {
$em = $this->getDoctrine()->getManager();
$results = $em->createQueryBuilder()
->from('AcmeDemoBundle:Blog', 'b')
->select('b')
->where('b.title LIKE :search')
->setParameter(':search', "%${query}%")
->getQuery()
->getResult();
}
// Pagination code
$page = $request->get('page');
$count_per_page = 5;
$total_count = $this->getTotalBlogs();
$total_pages = ceil($total_count/$count_per_page);
if (!is_numeric($page)) {
$page = 1;
} else {
$page = floor($page);
}
if ($total_count <= $count_per_page) {
$page = 1;
}
if (($page * $count_per_page) > $total_count) {
$page = $total_pages;
}
$offset = 0;
if ($page > 1) {
$offset = $count_per_page * ($page - 1);
}
$em = $this->getDoctrine()->getManager();
$blogQuery = $em->createQueryBuilder()
->select('b')
->from('AcmeDemoBundle:Blog', 'b')
->addOrderBy('b.created', 'DESC')
->setFirstResult($offset)
->setMaxResults($count_per_page);
$blogFinalQuery = $blogQuery->getQuery();
$blogPage = $blogFinalQuery->getArrayResult();
foreach ($blogPage as $blog) {
$blog_id = $blog['id'];
$commentRepository = $this->getDoctrine()
->getRepository('AcmeDemoBundle:Comment');
$comments[] = $commentRepository->findByBlog($blog_id);
}
// exit(\Doctrine\Common\Util\Debug::dump($comments));
return $this->render('AcmeDemoBundlBundle:Default:index.html.twig', array(
'blogPage' => $blogPage,
'total_pages' => $total_pages,
'current_page' => $page,
'comments' => $comments,
'query' => $query,
'results' => $results,
));
For a start, you can put all your custom queries in custom repository classes. I suspect that would cover all the re-use you need in this case.
For example, create a BlogRepository class in AcmeDemoBundle:Repository and annotate the Blog entity class as follows to define it's repository class:
/**
* #ORM\Entity(repositoryClass="Acme\DemoBundle\Repository\BlogRepository")
*/
Then add methods to the repository for each custom query that's needed, bearing in mind the way that repository methods are typically named. It looks as though the controller method getTotalBlogs() could also be a method on BogRepository e.g.:
public function findFiltered($page, $count_per_page = 5, $filterText = '')
{
$total_count = $this->findTotal();
// Code to initialise page and offset here
$queryBuilder = $this->createQueryBuilder('blog');
$queryBuilder->...
...
}
Note that the above method could be used to get all the blogs if no $filterText is passed in. You would just need something like this:
if (!empty($filterText))
{
queryBuilder->where('b.title LIKE :search')
->setParameter(':search', "%${query}%")
}
Then, a CommentRepository could be created with a method to find all the comments for a given set of blog(id)s. Note you could use a sql 'IN' clause to get all the comments using a single query:
$commentQuery = $em->createQueryBuilder()
->select('comment')
->from('AcmeDemoBundle:Comment', 'comment')
->where('comment.blog IN (:ids)')
->setParameter('ids', $blogIds);
In addition to custom repository classes I use manager services (e.g. BlogManager) to encapsulate business processes. My controllers mainly use the managers rather than using the repositories directly but it depends on the functionality.
I'm a little confused that you have an overall results query which only returns Blogs where the title is like the search text whilst the paged query returns (a page of) all blogs. That may just be a because your code is in progress?

Resources