Behavior Difference between find('count') and find('all') cakePHP - php

I am having an odd problem in CakePHP where
$this->something->find('count');
works perfectly, yet
$this->something->find('all');
returns nothing (not even an empty array, any errors, or anything).
edit: turns out I am getting an sql error: "SQL Error: 1054: Unknown column" - for a column that does indeed exist. (users.display_name in the sql query below):
SELECT item.id, item.name, item.description, item.user_id, users.display_name FROM item LEFT JOIN users ON (item.user_id = users.id);
I also tried using findAllBy as well as paginate (paginate is actually what I am trying to do - although from what I've gathered, paginate and find('all') are pretty similar in functionality).
The odd thing is that find('all') works everywhere else - it's just in this specific controller that it is acting odd. I am not getting any errors, simply an empty result.
I'm thinking that I may be overlooking something quite simple, but any help is appreciated. Thanks!

So, as per our discussion, the problem you're having is with the virtual fields. Have a look at the documentation, more specifically at the virtual fields and model aliases and limitation of virtualFields sections.
From your description above, it looks like you have a join specified your virtual field which would be causing the error you're seeing because it'll add the JOIN before the FROM. If you insist on using the virtual field, I'd suggest you rewrite it to use a subquery. Make sure your subquery only returns 1 column.
Example: (http://web-development-blog.co.uk/2011/03/08/cakephp-virtual-field-count-another-modeltable/)
public $virtualFields = array(
'count' => 'SELECT COUNT(*) FROM stacks Stack'
);
Alternatively, you can use the Model::beforeFind to bind the necessary models (if necessary) and change the query parameters.
If you can't figure it out, please post your model and I'll help you.

The specific problem you're having with the difference in behaviour is that find('count') will run a basic COUNT(*) query on your database to determine the number of rows.
find('all'), however, runs a different query, and if the SQL you've provided is what it's trying to use, it's invalid:
SELECT item.id, item.name, item.description, item.user_id, users.display_name LEFT JOIN users ON (item.user_id = users.id);
There's no FROM declaration (SELECT from what, exactly?), and if you've not customised your Item or User or App models, (setting $useTable = false maybe?) you're dealing with an unusual error.
One thing to look out for, if these models work fine in other controllers, is any alteration to the properties of each model in each controller. They won't act differently on a per-controller basis unless you have code in each controller that tells it to.

Related

UpdateExistingPivot for multiple ids in loop = n+1 problem. Is there another way?

There's another very similar issue with an answer that was very helpful (and I'm currently using) but causes the n+1 query problem.
I'll outline my use case. Polymorphic many to many relationship
I have:
Location model
Vendor model
User Model
Contactable (custom pivot model - still fleshing this out)
Users can be marked as contacts (aka contactables) for both Locations and Vendors.
I need to not detach contactables when dissociating them (I need a record of the fact that a User was once a contact for a location or vendor) so I don't want to detach them, I need to mark them inactive.
I'll limit the scope of this scenario to the following fields in the contactables table:
active
user_ID
contactable_type
contactable_ID
So I'm executing:
$collectionOfLocationIds = $contactDetails->locations()->getRelatedIds(); //changed to 'allRelatedIds()' in 5.4+
foreach ($collectionOfLocationIds as $locationID)
{
$contactDetails->locations()->updateExistingPivot($locationID, ['active' => 0]);
}
This runs great for most of my vendors, but some have 5k+ locations, so then I'm executing 5k+ update operations for what should really be one query. DB lives on a different server, so a few extra milliseconds add up pretty quickly...
I tried passing an array of ids to the updateExistingPivot function (it says it will take a mixed type for the id parameter) it doesn't produce an error, but it only seems to update the first id in the array. I'm not sure if this is a new bug, #Wallace Maxters mentioned that he could pass an array in 4.2, and I am still working in 5.3, but I'm wondering if anyone else has had this problem.
(Updated for clarity)
Use raw query instead of relationship.
I don't exactly understand which rows you want to inactive.
So lets think you want to inactive contactable for a particualar user.
If its not, change it to whatever where().
DB::table('contactable')->where('user_id', $user_id)
->update(['active' => 0]);
this will only execute one query.

Laravel ManyToMany Join On Condition

Greetings to everyone.
How can I add condition to "join on" for belongsToMany (many to many) relation in Laravel 5.3?
For example, let's say here are Products and Parameters of those Products with many to many relation. When I want to get all Parameters for specific Product, it is just fine. I do $product->parameters and it gives me what I need with query like this:
SELECT
parameters.*,
product_parameters.product_id AS pivot_product_id,
product_parameters.parameter_id AS pivot_parameter_id
FROM parameters
INNER JOIN product_parameters ON parameters.id = product_parameters.parameter_id
WHERE product_parameters.product_id = 1;
Problem appears when I want to define some "Global" flag on Parameter which defines that Parameter should be bound to every Product. With plain SQL, all I have to do is to add condition to JOIN, OR parameters.global = 1, like this:
SELECT
parameters.*,
product_parameters.product_id AS pivot_product_id,
product_parameters.parameter_id AS pivot_parameter_id
FROM parameters
INNER JOIN product_parameters ON parameters.id = product_parameters.parameter_id OR parameters.global = 1
WHERE product_parameters.product_id = 1;
But I didn't find if there is correct and clean way to do it with Laravel to be able to get Eloquent\Relation object back from function with additional data I require.
I have found "bad" way, which is to "fetch" join which I need from query builder object and add another "where" to it, but it is very ugly and I'm not sure that it will work in future without problems.
This is what I have tried to do and it almost worked (have problem with incorrect number of PDO parameters, but it generates SQL which I need):
$relation = $this->belongsToMany(Parameter::class, 'product_parameters');
$relation->getQuery()->getQuery()->joins[0]->where('parameters.definition', '=', 0, 'or');
Any help or advice is appreciated. Thank you.
I have left original idea to place everything in queries, tried to do several requests and combine resulting Eloquent Collections. Then I did some research and in the end I decided that what I was going wrong way, and it is better to use patterns like Repository and Specification to achieve required logic.

Correct way to handle loading doctrine entities with multiple associations

I'm currently building an eCommerce site using Symfony 3 that supports multiple languages, and have realised they way I've designed the Product entity will require joining multiple other entities on using DQL/the query builder to load up things like the translations, product reviews and discounts/special offers. but this means I am going to have a block of joins that are going to be the same in multiple repositories which seems wrong as that leads to having to hunt out all these blocks if we ever need to add or change a join to load in extra product data.
For example in my CartRepository's loadCart() function I have a DQL query like this:
SELECT c,i,p,pd,pt,ps FROM
AppBundle:Cart c
join c.items i
join i.product p
left join p.productDiscount pd
join p.productTranslation pt
left join p.productSpecial ps
where c.id = :id
I will end up with something similar in the SectionRepository when I'm showing the list of products on that page, what is the correct way to deal with this? Is there some place I can centrally define the list of entities needed to be loaded for the joined entity (Product in this case) to be complete. I realise I could just use lazy loading, but that would lead to a large amount of queries being run on pages like the section page (a section showing 40 products would need to run 121 queries with the above example instead of 1 if I use a properly joined query).
One approach (this is just off the top of my head, someone may have a better approach). You could reasonably easily have a centralised querybuilder function/service that would do that. The querybuilder is very nice for programattically building queries. The key difference would be the root entity and the filtering entity.
E.g. something like this. Note of course these would not all be in the same place (they might be across a few services, repositories etc), it's just an example of an approach to consider.
public function getCartBaseQuery($cartId, $joinAlias = 'o') {
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select($joinAlias)
->from('AppBundle:Cart', 'c')
->join('c.items', $joinAlias)
->where($qb->expr()->eq('c.id', ':cartId'))
->setParameter('cartId', $cartId);
return $qb;
}
public function addProductQueryToItem($qb, $alias) {
/** #var QueryBuilder $query */
$qb
->addSelect('p, pd, pt, ps')
->join($alias.'product', 'p')
->leftJoin('p.productDiscount', 'pd')
->join('p.productTranslation', 'pt')
->join('p.productSpecial', 'ps')
;
return $qb;
}
public function loadCart($cartId) {
$qbcart = $someServiceOrRepository->getCartBaseQuery($cartId);
$qbcart = $someServiceOrRepository->addProductQueryToItem($qbcart);
return $qbcart->getQuery()->getResult();
}
Like I said, just one possible approach, but hopefully it gives you some ideas and a start at solving the issue.
Note: If you religiously use the same join alias for the entity you attach your product data to you would not even have to specify it in the calls (but I would make it configurable myself).
There is no single correct answer to your question.
But if I have to make a suggestion, I'd say to take a look at CQRS (http://martinfowler.com/bliki/CQRS.html) which basically means you have a separated read model.
To make this as simple as possibile, let's say that you build a separate "extended_product" table where all data are already joined and de-normalized. This table may be populated at regular intervals with a background task, or by a command that gets triggered each time you update a product or related entity.
When you need to read products data, you query this table instead of the original one. Of course, nothing prevents you from having many different extended table with your data arranged in a separate way.
In some way it's a concept very similar to database "views", except that:
it is faster, because you query an actual table
since you create that table via code, you are not limited to a single SQL query to process data (think filters, aggregations, and so on)
I am aware this is not exactly an "answer", but hopefully it may give you some good ideas on how to fix your problem.

PHP Postgres JOIN with LIKE query failing

I am trying to figure out a query with postgres, and Im not sure its cause I am used to mysql and postgres doesn't have a LIKE query. Or what my deal is. Either way I know its not working and I have no idea why not. Can someone help me out point me in the right direction? I mean ultimately I am trying to make a Zend Database version of this query but, this is the core query I am working with to try and make before I attempt to use zend db class to build it.
SELECT
org.orgid,
org.roleid,
users.userid,
users.email,
users.first_name,
users.last_name,
users.contact_id,
users.state,
users.ts,
users.altemail,
users.unboundid,
users.blocked
FROM mapping AS org
INNER JOIN my_users AS users ON org.userid = users.userid
WHERE (org.orgid = 'generated-id')
AND (org.roleid LIKE 'partner-%');
ERROR: operator does not exist: roles ~~ unknown
LINE 17: AND (org.roleid LIKE 'partner-%');
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Change
AND (org.roleid LIKE 'partner-%');
to
AND (org.roleid::text LIKE 'partner-%');
Evidently you are working on the textual representation of the roleid. this seems like it should work fine except your data types do not match. By casting to text you ensure that they do.
Note that one important ramification is that indexes on roleid will be useless for such a comparison because they operation is not directly compatible with the type. This shouldn't be a problem since orgid can still use an index but it is something to think about. If you want to fix that, you may want to use table methods or other functions to break this down for you. You can then index function output.
For example you could use a table method as like so:
CREATE FUNCTION is_partner(mapping) returns bool language sql immutable as $$
SELECT $1.roleid::text LIKE 'partner-%';
$$;
You could then index it with:
CREATE INDEX mapping_is_partner_idx ON mapping (is_partner(mapping));
You could then change that join condition from
AND (org.roleid LIKE 'partner-%');
to
AND org.is_partner;
Note in this case, org is necessary and cannot be added implicitly since it changes org.is_oartner to is_partner(org) using class.method notation.
Hope this helps.

Doctrine 2.1: Getting and assigning COUNT(t.id) from a subquery?

I have two entities in Doctrine 2.1: Category and Site each category has many sites and each site has a parent category.
I would like to make a single update query (in DQL) which will update a field called count of the Category entity with the number of related sites.
So in SQL I would do something like this:
UPDATE categories c SET c.count = (SELECT COUNT(s.id) FROM sites s WHERE s.category_id = c.id);
This would work beautifuly, in DQL it might something like this:
UPDATE PackageNameBundle:Category c SET c.count = (SELECT COUNT(s.id) FROM PackageNameBundle:Site s WHERE s.category = c)
Such attempt raises [Syntax Error] line 0, col 61: Error: Expected Literal, got 'SELECT'.
Subqueries DO work in DQL, but the problem here (as far as I see it) is that Doctrine cannot assign the returned value from the subquery, to the c.count. This is understandable since I might fetch more than 1 field in the subquery and even more than one row. It magicaly works in MySQL since it sees one row, one field and for convenience returns a single integer value. Doctrine on the other hand has to be object oriented and has to work with different engines where such convertions might not be supported.
Finally, my question is:
What is the best way to do this in Doctrine, should I go with Native SQL or it can be done with DQL and how?
Thanks in advance!
EDIT: I just found this quote in the DQL Docs:
References to related entities are only possible in the WHERE clause and using sub-selects.
So, I guess assigning anything but a scalar value is impossible?
The main question remains though..
You can use native sql queries in Doctrine also, for that kind of specific queries. DQL is powerful in its own way, but it's also limited due to performance constraints. Using native sql queries and mapping the results will achieve the same thing, and there is no disadvantage in doing that.
The documentation explains it in detail.

Categories