Laravel created_by/modified_by relations - laravel-4

I was trying to get this working in a typical belongsTo relation. However it keeps saying that the column is not set in the model, even if looking in the actual database it is there.
I have tried to look at the source code as well as try many approaches to bypass this issue, however nothing seems to do anything.
public function modifiedBy()
{
return $this->belongsTo('\Modules\Users\Model\User', 'modified_by');
}
public function createdBy()
{
return $this->belongsTo('\Modules\Users\Model\User', 'created_by');
}
This is the code inside the model, I use PSR-0 to define modules, better splitting up logic (no issues with that) but using this it would give an error of
Undefined property: \Modules\Module\Model\CurrentModel::$modified_by
This is coming from a seed to push some initial info into the database.
$user = Sentinel::findById(1);
$model = new CurrentModel;
$model->modifiedBy()->associate($user);
$model->save();
This is basically how it goes together, I have tried for some time to figure out what is wrong but I am calling blanks. Any ideas?

Found out a solution. Not a fix though but I would consider this an issue with laravel so I may look into adding it as a bug report (although this could be fixed in laravel 5?).
Basically with modified_by I need to define the column it is using and not let laravel automatically generate it (in order to do this "cleanly"). However the "bug" (only calling it a bug as currently I can only see this as an unintended problem) makes it so you cannot define the column it will be using, you have to let laravel decide it for you.
So I changed the functions to look like this:
public function modifiedby()
{
return $this->belongsTo('\Modules\Users\Model\User');
}
This makes laravel assume the column is modifiedby_id, but by changing my migrations to reflect that there was no more error.

Related

Two models, two fields, return preferred if present

Been struggling with how to do this the most optimized way possible...
I have two models: Catalog and Application.
Catalog has a field called name.
Application has a field called name.
Both have a relationship with each other.
I am struggling to find a way to create a function i could use across my Laravel application which i would pass application.id to it and it would return a $app->name value based on the following logic:
if $application->name exists, use this value as the $app->name for the $application object
otherwise, get the $catalog->name value and use it as the $app->name
Note that I would like to create a component #application() where i can simply pass the $application->id and build the display logic (theming/styling) into it.
Since i display this $app->name in many places, i would like to make it as lightweight as possible to avoid unnecessary queries.
I hope this makes sense! There are probably so many ways to go with it, i am lost at figuring out the way way to do this :(
I'm not completely sure to understand your model/DB design, but you could use a custom Helper to use that function through the whole app.
For that, you can create a simple PHP class Helper.php file in app/Http/Helpers folder or whatever location you want. Something like:
<?php
use App\Catalog;
use App\Application;
if (! function_exists('getAppName')) {
function getAppName($id){
// Do your logic here to return the name
$catalog = Catalog::find($id);
return $catalog->name;
}
}
?>
Then in any controller or view, you just do
getAppName($application->id)
Do no forget to add your helpers file to the composer autoload. So in composer.json in Laravel's root folder, add the helper path to the autoload array:
"files": [
"app/Http/Helpers/helpers.php"
],
Last but not least, run the following command:
composer dump-autoload
Please note that function logic is just for sample purposes since I don't know your model structure.
In my opinion, I care about the database cost.
Use ternary expression will be elegant. But it took two times IO costs from database if application name is empty.
$app_name = Application::find($id)->name;
$app_name = empty($app_name) ? Catalog::where('application_id', $id)->first()->name;
And this will more complicated, but the catalog_query only execute when application.name is empty, it execute in database and the result is taken out only once;
And Database will only find the name from one table or two table.
Something like this:
$catalog_query = Catalog::where('catalogs.application_id', $id)->select('catalogs.name')->groupBy('catalogs.name');
// if catalogs and applications relationship is 1:1, use ->limit(1) or remove groupBy('name') is better.
Application::where("applications.id", $id)
->selectRaw("IF(application.name IS NULL OR application.name = '', (" . $catalog_query->toSql() ."), applications.name ) AS app_name")
->mergeBindings($catalog_query->getQuery())
->first()
->app_name;
Hope this will help you.

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 Gate method not being called

In my Laravel 5.4 application users can create Projects and then Posts inside those projects.
I'm trying to prevent users from creating or editing posts inside a project they don't have access to.
To do this I implemented a Gate as explained here: https://laravel.com/docs/5.4/authorization#gates
The gate checks if a user is the owner of the project.
Gate::define('create-post', function ($user, $project) {
Log::info($project) // !!! Never gets called
return $project->owner_id == $user->id;
});
On the PostController I call Gate::denies passing the project as an argument
if (Gate::denies('create-post', $project)) {
abort(403);
}
The problem is the code I defined for the gate never gets called. Instead it always returns false and goes to the 403 error.
However, the code does get called if I don't pass the project as an argument but that makes it useless.
I also want to add that in this case I cannot use a Policy because the create method only takes one argument ($user) and if I try to pass the $project it fails the same way it does with the Gate.
Is this a bug? Is there another, better way to implement this funcionality? Thanks.
I have the same issue. It seems something wrong happens when the second parameter in Gate::allows() is an eloquent model.
If you pass in denies() any other variable (even object, but not eloquent model), your Log::info() will work.
I wasted the whole day with it and switched to $user->can()
I was able to fix it by using a policy. I created the following method in ProjectPolicy:
public function createOrEditPosts(User $user, Project $project)
{
return $project->owner_id == $user->id;
}
Then from PostController I call:
$this->authorize('createOrEditPosts', $project));
I still don't know why the gate approach doesn't work.
I had the same issue.
I replaced Gate::denies() with Gate::allows().
I'm not sure why but this worked for me. The policy framework is a little bit tricky to be honest
first, check to log in to your project then gate work because of the gate when work that you log in to your project.

Search object by slug and not by id

I'm a relative beginner with Laravel (using version 5.2.3) and have been working through tutorials on Laracasts and then doing a bit of my own experimenting.
I successfully set up a route that fetches an item from a table by its ID, as shown below
Route::get('/wiseweasel/{id}', 'WiseweaselController#singleArticle');
For simplicity, the controller simply dd's the article
public function singleArticle($id)
{
$article = ww_articles::find($id);
dd($article);
}
This works absolutely fine - I visit eg /wiseweasel/2 and get the contents of the record with id2.
So, I then wanted to use the slug field from the record instead of the id. Since I know the ID method was working, I've tried just modifying this route and controller (also tried creating anew, neither worked) So I now have:
Route::get('/wiseweasel/{slug}', 'WiseweaselController#singleArticle');
and
public function singleArticle($slug)
{
$article = ww_articles::find($slug);
dd($article);
}
The slug for the second record is "secondarticle". So, visiting the url /wiseweasel/secondarticle, I would expect to see the same record as previously dd'd out. Instead, I end up with null.
Even more oddly, using the original id route (/wiseweasel/2) still returns the record... when I have removed all trace of this from the routes and controller, so I would expect this to fail...
This is making me wonder if this could be some odd caching issue? I've tried
php artisan route:clear
in case the route was being cached. I've also tried restarting both Apache and MySql (I'm using XAMMP for both).
Still no luck though... not sure if I've misunderstood how something works or what's going on... so if anyone has any suggestions as to what I might have done wrong, or anything to try, I would be very grateful! :)
You also have the option of using Route Model Binding to take care of this and inject the resolved instance into your methods.
With the new implicit Route Model Binding you can tell the model what key it should use for route binding.
// routes
Route::get('/wiseweasel/{article}', 'WiseweaselController#singleArticle');
// Article model
public function getRouteKeyName()
{
return 'slug';
}
// controller
public function singleArticle(Article $article)
{
dd($article);
}
Laravel Docs - Route Model Binding
Laravel won't automatically know that for slug it should search record in different way.
When you are using:
$article = ww_articles::find($slug);
you are telling Laravel - find record of www_articles by ID. (no matter you call this id $slug).
To achieve what you want change:
$article = ww_articles::find($slug);
into
$article = ww_articles::where('slug', $slug)->first();
This will do the trick (for slug put the name of column in table in database). Of course remember that in this case slug should be unique in all records or you won't be able to get all the slugs.
Maybe it's a bit late for the answer but there is another way to keep using find method and use slug as your table identifier. You have to set the protected $primaryKey property to 'slug' in your model.
class ww_articles extends Model
{
protected $primaryKey = 'slug';
...
}
This will work because find method internally uses the getQualifiedKeyName method from Model class which uses the $primaryKey property.
If you have both routes like this
Route::get('/wiseweasel/{id}', 'WiseweaselController#singleArticle');
Route::get('/wiseweasel/{slug}', 'WiseweaselController#singleArticle');
it will always use the first one. Obviously, there is no id 'secondarticle', so it returns null (although in this case it doesn't matter, they both point to the same method).
The reason is route will search through possible routes till it finds a matching, which is always the one with {id}. Why? You're not telling Route that {id} must match an integer!
You can make sure {id} is understood as an integer, however I suggest using urls like this is a better option
/wiseweasel/{id}/{slug?}
Another suggestion. Do not use names such as xx_articles for a model, but Article instead. This way you can use the new implicit route binding. So using implicit route binding your url would look like this (assuming your model is called Article)
Route::get('/wiseweasel/{article}', 'WiseweaselController#singleArticle');

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();

Resources