doing a where() after a get() - laravel

I'm trying to add a new feature to an existing Laravel codebase and in that codebase there's this:
$hasGAP = (new \App\Models\Policy)->where('leadID', $leadId)
->where('policystatus', '!=', 'Canceled')
->get()->where('product.name', 'GAP Insurance')->count() > 1;
So this is doing an SQL query on the table referenced by the \App\Models\Policy model. It's doing WHERE policystatus != 'Canceled' and then it's getting the result. And then it's doing a WHERE on the result? That doesn't make sense to me.
Also, product.name isn't a column in the table. Indeed, it seems like the period (.) operator would be an illegal character..
Does this code actually work and if so what is it actually doing?

The ->get() ends the query and returns the results in a collection.
The subsequent ->where(..) and ->count() are then calls on the collection.
The dot notation is widely used in Laravel for getting sub fields of arrays, objects and similar data structures (example: array_get()) and works in ->where() (on a collection) as well.
So the posted code should work. I assume the Policy belongsTo (or hasOne) a product and the dot notation is used to search by the related product name.

Related

Laravel 5: Grabbing a single record from a query using `take`

Right now, I am using Eloquent like this: MobileAppUsers::where('store', '=', 'store_name')->first(); It turns out I don't need to check the store_name because the entire table is within that store's database, so I just want the single record that will be there (there will only be 1).
Without changing any other logic, would the equivalent of MobileAppUsers::where('store', '=', 'store_name')->first(); (ignoring the store_name) be :
MobileAppUsers::take(1)->get(); ?
you can use this
MobileAppUsers::first();
Regards, I hope this help you

Eloquent ORM get latest items query

I am trying to do get all the latest items and sort by id descending (i.e. get all items that were just added with a limit and offset).
So I did this:
$products = Product::all()
->slice($request->get('offset'))
->take($request->get('limit'))
->sortByDesc('id')
->toBase();
However it seems when I have more that that limit, then I dont have the right order. It gets me say 10 products but not sorted corrected. Any idea how to do this with Eloquent ORM?
You are probably intending to have the database handle the offset and skipping and ordering instead of pulling all the possible records then taking only what you want, then sorting them ... if you were going to do it your way you would need to sort before you skip and take, by the way.
Using the database to the the filtering and ordering:
$products = Product::skip($request->input('offset'))
->take($request->input('limit'))
->orderBy('id', 'desc')
->get();
I think the issue is you're using ::all() first, which returns all Product instances in a Collection, then using collection methods. Since these methods act in order of usage, you're slicing and offsetting before sorting, so you'll get the same products each time. Use proper Builder syntax to handle this properly and more efficiently:
$products = Product::offset($request->input("offset"))
->limit($request->input("limit"))
->orderBy("id", "DESC")
->get();
Because this is a Builder instance, the query will be compiled and executed according to your Database's grammar logic, and in a single query. There's nothing wrong in using Collection logic, you'd simply have to use the correct order of methods (sortByDesc() first, then slice(), then take()), but this is incredibly inefficient as you have to handle every Product in your database.

Magento collection complex condition or union approach

Long story short, I need to get a collection that should be filtered using the following condition: where (A and (B or C) or (D and (E or F)).
I can do them separately using:
$collection = Mage::getModel('customModule/customModel')
->getCollection()
->addFieldToFilter('fieldA', valA)
->addFieldToFilter(
array('fieldB', 'fieldC'),
array(
array('eq' => valB),
array('eq' => valC)
)
);
But i don't know if I can make a single collection which would combine the two. I haven't found a "Magento" way of doing this. I also though of performing a union between them using
$collection1->getSelect()->union(array($collection2->getSelect()));
but it doesn't work. After the above statement, printing the $collection1->getSelect()->__toString() displays:
SELECT `main_table`.*SELECT `main_table`.* FROM `customModel` AS `main_table` WHERE..
which throws an error. For some reasons it concatenates the two select clauses rather then the two queries.
Can anyone suggest a fix in either direction? If I can either use Magento's native functions to perform the complex condition or how to fix the union?
I've also searched far & wide for this but unfortunately it is not implemented.
There are some problems with using UNION without DISTINCT, because the entity_id in a collection has to be unique otherwise you'll get an exception from some code layers bellow yours.
The Magento / Zend way:
It is possible by getting the underlying Zend_Select object and adding a union join in the Zend way. Meaning that the resulting query object(s) will be a Zend_Query which you will have to execute and fetch results on (the Zend way).
The pitfall of this is that the result is not a Magento Collection and the objects inside are not Magento model objects but Zend_Db_Table_Row.
The way I solved it:
I created multiple separate collections for which I called getAllIds() (this triggers a separate query which only fetches the ids) and then I merged the resulting arrays of ids and created one "master collection" WHERE entity_id IN(/*...*/).

Laravel - Really struggling to understand eloquent

I'm fairly new to Laravel having come over from Codeigniter and for the most part I really like it, but I really can't get my head around Eloquent.
If I want to do a simple query like this:
SELECT * FROM site INNER JOIN tweeter ON tweeter.id = site.tweeter_id
I try doing something like this (with a "belongs to"):
$site = Site::with('tweeter')->find($site_id);
But now I have two queries and an IN() which isn't really needed, like so:
SELECT * FROM `site` WHERE `id` = '12' LIMIT 1
SELECT * FROM `tweeter` WHERE `id` IN ('3')
So I try and force a join like so:
$site = Site::join('tweeter', 'tweeter.id', '=', 'site.tweeter_id')->find($site_id);
And now I get an error like so:
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'id' in where clause is ambiguous
SQL: SELECT * FROM `site` INNER JOIN `tweeter` ON `tweeter`.`id` = `site.tweeter_id` WHERE `id` = ? LIMIT 1
Bindings: array (
0 => 12,
)
It's obvious where the error is, the where needs to use something like "site.id = ?". But I can't see anyway to make this happen?
So i'm just stuck going back to fluent and using:
DB::table('site')->join('tweeter', 'tweeter.id', '=', 'site.tweeter_id')->where('site.id','=',$site_id)->first()
I guess it's not a massive problem. I would just really like to understand eloquent. I can't help but feel that i'm getting it massively wrong and misunderstanding how it works. Am I missing something? Or does it really have to be used in a very specific way?
I guess my real question is: Is there anyway to make the query I want to make using Eloquent?
I actually find this behaviour advantageous. Consider this (I'll modify your example). So we have many sites and each has many tweeters. Each site has a lot of info in the DB: many columns, some of them text columns with lots of text / data.
You do the query your way:
SELECT * FROM site INNER JOIN tweeter ON tweeter.id = site.tweeter_id
There are two downsides:
You get lots of redundant data. Each row you get for a tweeter of the same site will have the same site data that you only need once so the communication between PHP and your DB takes longer.
How do you do foreach (tweeter_of_this_site)? I'm guessing you display all the sites in some kind of list and then inside each site you display all of it's tweeters. You'll have to program some custom logic to do that.
Using the ORM approach solves both these issues: it only gets the site data once and it allows you to do this:
foreach ($sites as $site) {
foreach($site->tweeters as $tweeter) {}
}
What I'm also saying is: don't fight it! I used to be the one that said: why would I ever use an ORM, I can code my own SQL, thank you. Now I'm using it in Laravel and it's great!
You can always think of Eloquent as an extension of Fluent.
The problem you're running into is caused by the find() command. It uses id without a table name, which becomes ambiguous.
It's a documented issue: https://github.com/laravel/laravel/issues/1050
To create the command you are seeking, you can do this:
$site = Site::join('tweeter', 'tweeter.id', '=', 'site.tweeter_id')->where('site.id', '=', $site_id)->first($fields);
Of course, your syntax with join()->find() is correct once that issue fix is adopted.

Shaping EF LINQ Query Results Using Multi-Table Includes

I have a simple LINQ EF query below using the method syntax. I'm using my Include statement to join four tables: Event and Doc are the two main tables, EventDoc is a many-to-many link table, and DocUsage is a lookup table.
My challenge is that I'd like to shape my results by only selecting specific columns from each of the four tables. But, the compiler is giving a compiler is giving me the following error:
'System.Data.Objects.DataClasses.EntityCollection does not contain a definition for "Doc' and no extension method 'Doc' accepting a first argument of type 'System.Data.Objects.DataClasses.EntityCollection' could be found.
I'm sure this is something easy but I'm not figuring it out. I haven't been able to find an example of someone using the multi-table include but also shaping the projection.
Thx,Mark
var qry= context.Event
.Include("EventDoc.Doc.DocUsage")
.Select(n => new
{
n.EventDate,
n.EventDoc.Doc.Filename, //<=COMPILER ERROR HERE
n.EventDoc.Doc.DocUsage.Usage
})
.ToList();
EventDoc ed;
Doc d = ed.Doc; //<=NO COMPILER ERROR SO I KNOW MY MODEL'S CORRECT
DocUsage du = d.DocUsage;
Very difficult to know what is going on without a screencap of your model, including the navigational properties on each entity.
But if your saying it's a many-to-many between Event and Doc (with EventDoc being the join table), and assuming your join table has nothing but the FK's and therefore doesn't need to be mapped, then shouldn't a single Event have many Doc's?
This query:
var query = ctx.Event.Include("EventDoc.Doc");
Would imply (based on the lack of pluralization): a single Event has a single EventDoc which has a single Doc.
But shouldn't that be: a single Event has a single EventDoc which has many Doc's.
Therefore your projection doesn't really make sense. Your trying to project to an anonymous type, with EventDate and Filename for a single Doc, but an Event has many Docs.
Maybe a projection like this would be more suitable:
var query = ctx.Event.Include("EventDoc.Docs.DocUsage")
.Select(x => new
{
EventDate = x.EventDate,
DocsForEvent = x.EventDocs.Docs
}).ToList();
And for that you work you need to fix up your model. Im surprised it even validates/compiles.
Either your model is wrong or your description of the database cardinalities in your question is. :)
Of course, i could be completely misunderstanding your database and/or model - so if i am let me know and i'll remove this answer.

Resources