Displaying Data from an Eloquent collection with Relations - laravel

I'm trying to get data from an Eloquent query as follows:
$submission->find($id)->first()->with('user', 'clientform')->get();
I'm sending the submission to the view and attempting to access properties from the user model like so:
{{ $submission->clientform->name }}
However, Laravel is throwing the following error:
Undefined property: Illuminate\Database\Eloquent\Collection::$clientform
What am I doing wrong with the way my query is formatted?

You're overdoing it!
Let's break it down:
$submission->find($id);
Will return an instance of the model which corresponds to the entry having the primary key of $id.
$submission->find($id)->first();
This is an unncessary repetition; the find method already gives you a single entry, no need to call first on it.
$submission->find($id)->first()->with('user', 'clientform');
This is where you start going the wrong way; when calling with on a model, Laravel will transform that model into a query builder again, even though it was already resolved by calling find.
$submission->find($id)->first()->with('user', 'clientform')->get();
Finally, the get method resolves the builder into a Illuminate\Support\Collection, regardless of the number of entries found (i.e. it could be a Collection of only one item). It's worth noting, though, that your query will most likely have been fully reset by calling with. It would be the same as instantiating a fresh Submission model and building your query with it. That means you're probably using a collection of all your submission entries, not just the one with $id.
Long story short, this is what you want:
$submission->with('user', 'clientform')->find($id);
It will fetch the matching $id, with the user and clientform relations eager-loaded.

Related

Laravel - trouble with Eloquent Collection methods such as last()

I have a variable $courses that in my debugger is shown as type App/Courses
In the model, there is a one to many relation and I retrieve these related items in the model and then access as $courses->relateditems
In the debugger, $courses->relateditems is shown as type Illuminate/Database/Eloquent/Collection
Ok, all makes sense.
I want to get last item in the $courses->relateditems collection. So I try
$courses->relateditems->last()->startdate
But this is not returning the value that I know exists. And when I evaluate the expression $courses->relateditems->last() in the debugger I get this in my laravel.log:
Symfony\Component\Debug\Exception\FatalErrorException: Cannot access self:: when no class scope is active in /app/Courses.php:68
I am just not sure what is going on. I know I can use DB queries to just get the data I need, but I have a model event triggering a function and that function receives the $courses object/model (or however we name it) and I am just trying to get this working as above.
Ideas on what I am doing wrong?
thanks,
Brian
The issue here based on the error you have at the bottom of your post is a mistake in your code.
However, I think you've misunderstood how Laravel relationships work. They essentially come in two forms on a model, that of a property and that of a method.
relateditems Is a magic property that will return the result of a simple select on the relationship if one has already been performed. If one hasn't been performed it will perform one and then return it, known as eager loading.
relateditems() Is a method that returns a query builder instance for that relationship.
By calling relateditems->last() you're loading ALL related items from the database and then getting the last, which is less than optimal.
The best thing for you to do is this:
$course->relateditems()->orderBy('id', 'desc')->first();
This will get the first item return, however, since we've ordered it by id in descending order, it'll be reversed from what could be considered its default ordering. Feel free to change the id to whatever you want.

Assert model was not made searchable

I'm building a system to manage some articles for my company using Laravel and Laravel Scout with Algolia as the search backend.
One of the requirements states that whenever something in an article is changed, a backup is kept so we can prove that a certain information was displayed at a specific time.
I've implemented that by cloning the existing article with all its relationships before updating it. Here is the method on the Article model:
public function clone(array $relations = null, array $except = null) {
if($relations) {
$this->load($relations);
}
$replica = $this->replicate($except);
$replica->save();
$syncRelations = collect($this->relations)->only($relations);
foreach($syncRelations as $relation => $models) {
$replica->{$relation}()->sync($models);
}
return $replica;
}
The problem is the $replica->save() line. I need to save the model first, in order for it to have an ID when syncing the relationships.
But: The only thing preventing scout from indexing the model is if the model has its archived_at field set to any non-null value. But since this is a clone of the original model, this field is set to null as expected, and is only changed after the cloning procedure is done.
The problem: Scout is syncing the cloned model to Algolia, so I have duplicates there. I know how to solve this, by wrapping the clone call into the withoutSyncingToSearch (https://laravel.com/docs/5.6/scout#pausing-indexing) callback.
But since this is rather important and the bug is already out there, I want to have a unit test backing me up that it was indeed not synced to Algolia.
I don't have any idea how to test this though and searching for a way to test Scout only leads to answers that tell me not to test Scout, but rather that my model can be indexed etc.
The question: How do I create a Unittest that proves that the cloned model wasn't synced to Algolia?
At the moment I'm thinking about creating a custom Scout driver for testing, but it seems to be a total overkill for testing one single function.

Laravel 5.5 Update of Empty Date String Produces Data Missing Carbon Error

The update to Laravel 5.5 seems to be creating a few oddities for me. Probably something I've screwed up, but these issues only broke with the new version - this same code has not failed in 5.4, 5.3, etc. The bigger problem is that the error is not consistent on the same model - it fails on update, but works on store.
I have a date field called 'decom_date' on a 'prog' model with the $dates field on the model overridden to include 'decom_date'. A user can fill out a form for a new 'prog', and skip the 'decom_date' field. The model saves with no error. If the user edits the same prog model with the exact same form, and leaves the 'decom_date' field blank, the following error occurs in Laravel 5.5 only:
message "Data missing"
exception "InvalidArgumentException"
file "/var/www/ipfast/vendor/nesbot/carbon/src/Carbon/Carbon.php"
line 582
IE Carbon is now expecting a format instead of an empty string upon updates only. I can work around this with a mutator on the model like so:
public function setDecomDateAttribute($value)
{
$this->attributes['decom_date'] = $value ?: null;
}
No problem - this works, and I think will stop the new breaking 100%... but I worry when things suddenly break, especially as it doesn't seem consistent across the saves. This pattern fails consistently across every model I have with dates, and these were not broken before update.
Anyone able to shed some light on this - or maybe just something dumb I've done?
I can't confirm this behaviour. If your date is an empty string it always fails with your posted error, no matter if it is during update or create.
Normally in Laravel 5.4 and 5.5 the global middleware stack contains:
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
You can check this in app\Http\Kernel.php
With this middleware emptry strings shoul automatically be converted to a NULL value which Carbons handles properly.
What I can imagine is, that your decom_date field is not even present in the request data when you 'skip' this field during create ( Just check it with dd($request) all on top of your store method.
And if you leave the field 'blank' during your update method, the result is an empty string ( In case the middleware mentioned above is missing ... )

Why does Laravel Controller needs (integer) cast in Homestead, but not in production server

(integer) cast must be done in Homestead for Controller parameter
I am having a hard time searching for the cause of a discrepancy between my local dev environment (Homestead) and the hosted one.
I define a route like this:
Route::get('group/{id}/data', 'GroupDataController#index');
And the code in the Controller looks like this:
public function index($id)
{
return Grouping::all()->where('group_id', $id);
}
Which works fine in production (hosted env), BUT when I execute it locally it throws and empty array [] unless I modify my Controller function to look like this:
public function index($id)
{
return Grouping::all()->where('group_id', (integer)$id);
}
I have no idea of what is going on in here, and I am tired of making changes all over my Controller to make it work on both environments.
I have searched in several place, but maybe I am using incorrect tokens for my search as I have found nothing.
Any help will be really appreciated.
The problem here is that you're not using the correct set of functions.
When you call Grouping::all(), this is actually returning an Eloquent Collection object with every record in your groupings table. You are then calling the where() method on this Collection. This causes two problems: one, it is horribly inefficient; two, the where() method on the Collection works differently than the where() method on the query builder.
The where() method on the Collection takes three parameters: the name of the field, the value in that field on which to filter, and finally a boolean value to tell it whether or not to do a strict comparison (===) or a loose comparison (==). The third parameter defaults to strict. This strict comparison is why you're having the issue you are having, but I cannot explain why one environment sees $id as an integer and the other doesn't.
The where() method on a query builder object will actually add a where clause to the SQL statement being executed, which is a much more efficient way of filtering the data. It also has more flexibility as it is not limited to just equals comparisons (the second parameter is the comparison operator for the where clause, but will default to "=" if it is left out).
You have two options to fix your issue. You can either pass in false as the third parameter to your where() method in the current code (bad), or you can update the code to actually filter using the query instead of filtering on the entire Collection (good).
I would suggest updating your code to this:
public function index($id) {
return Grouping::where('group_id', '=', $id)->get();
}
In the above code, Grouping::where('group_id', '=', $id) will generate a query builder object that has the given where clause, and then get() will execute the query and return the Collection of results.
I marked #patricus (thanks you, so much!) as the correct answer, for he really pointed me in the right direction to understand that there are some keywords that work differently under different contexts (like get()), but I will also point out, how my 2 confusing points were solved in my case:
The difference in my code between production and Homestead development environments was solved by pointing my Homestead to the production database. I am not sure what was the difference (maybe collation or table format), but it gave me a quick out.
I was trying to filter a list of elements in the database but I was constructing it with the wrong logic for Laravel.
To clear what I refer to in the second point, I was using this code:
Grouping::all(['id', 'name_id', 'product_id', 'ship_id'])->where('name_id', '=', $id);
I thought this could work, because it would be selecting all items, with the selected columns, and then filter those with a where clause. But I was wrong, since, as I found out later, the correct way of writing this is:
Grouping::where('name_id', $id)->select('id', 'name_id', 'product_id', 'ship_id')->get();
This is because I forgot completely that I was assembling the query, not writing the actions I expected the program to do.
This second syntax has more logic, since I specify the filter, then put the columns over what was filtered, and finally execute the query with the get() clause.
Of course, it can also be written the other side around for clearer fluent reading:
Grouping::select('id', 'name_id', 'product_id', 'ship_id')->where('name_id', $id)->get();

How to use model events with query builder in laravel

I'm using model events such as static::saving, static::saved, etc in my models' static function boot method, and that works great when users save new posts, but when I do something like this:
$post::where('id', $post_id)->update(array('published'=>1));
Updating in this way does not run those model events. My current solution is to just not use this method of updating and instead do:
$post = Post::find($post_id);
$post->published = 1;
$post->save();
But is there any way to make the model events work with the first example using query builder?
Model events will not work with a query builder at all.
One option is to use Event listener for illuminate.query from /Illuminate/Database/Connection.php. But this will work only for saved, updated and deleted. And requires a bit of work, involving processing the queries and looking for SQL clauses, not to mention the DB portability issues this way.
Second option, which you do not want, is Eloquent. You should still consider it, because you already have the events defined. This way you can use also events ending with -ing.

Resources