I have to pass the secret author_id of the user when he edit for example an Article and memorize that into the Database in Backpack-Laravel.
How can do that?
I be able to do just this, the value appears in the $request array (I use dd($request) for know that) but isn't stored on the database.
AuthorCrudController.php
public function update(UpdateArticleRequest $request)
{
//dd($request); <-- author_id = Auth::id()
return parent::updateCrud();
}
UpdateArticleRequest.php
public function rules()
{
$this->request->add(['author_id'=> Auth::id()]);
return [
'title' => 'required|min:5|max:255',
'author_id' => 'numeric'
];
}
99 times out of 100, when the value isn't stored it's because that column hasn't been mentioned in your model's $fillable property. Is this it?
Sidenote: Adding the author_id like this works, but if you're using this approach for multiple models, I recommend coding it once for all your models. I use a trait for this. That way, any time an entry is created, the author is saved and you have the have all the methods for getting it in one place, the trait ($this->creator(), this->updator).
My approach to this is this:
1) I have two new columns in my database created_by and updated_by
2) I use a trait like this:
<?php namespace App\Models\Traits;
use Illuminate\Database\Eloquent\Model;
trait CreatedByTrait {
/**
* Stores the user id at each create & update.
*/
public function save(array $options = [])
{
if (\Auth::check())
{
if (!isset($this->created_by) || $this->created_by=='') {
$this->created_by = \Auth::user()->id;
}
$this->updated_by = \Auth::user()->id;
}
parent::save();
}
/*
|--------------------------------------------------------------------------
| RELATIONS
|--------------------------------------------------------------------------
*/
public function creator()
{
return $this->belongsTo('App\User', 'created_by');
}
public function updator()
{
return $this->belongsTo('App\User', 'updated_by');
}
}
3) Whenever I want a model to have this feature, i just need to:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\CrudTrait;
class Car extends Model
{
use CrudTrait;
use CreatedByTrait; // <---- add this line
Hope it helps.
The update function in my backpack setup has $request passed in updateCrud function. The one that you have mentioned does not have the request passed into the parent function.
public function update(UpdateRequest $request)
{
// your additional operations before save here
$redirect_location = parent::updateCrud($request);
return $redirect_location;
}
Related
I am using Policys and want to be sure that I am prevent data to be shown of other users.
In every Table I have the column 'user_id' and check if the current logged in user with his id the same with the data and his user_id.
In this specific case I have a table of Objects and Objektverwaltung where the objekt_id is given as foreign key.
I want to use my policy to be sure that just the data for the given object was shown in objektverwaltung where the foreign key 'objekt_id' is given.
ObjektVerwaltung Controller with the show method:
public function show($objektvwId) {
$objektId = ObjektVerwaltung::with('Objekt')->find($objektvwId);
$this->authorize('view', $objektId);
$objekte = ObjektVerwaltung::where('objekt_id',$objektvwId)->get();
return view('objekte.verwaltung', compact('objekte'));
}
Policy:
public function view(User $user, ObjektVerwaltung $objektVerwaltung)
{
return $objektVerwaltung->user_id === $user->id;
}
Models:
class ObjektVerwaltung extends Model
{
use HasFactory;
protected $table = 'objekte_verwaltungens';
protected $fillable = ['user_id','objekt_id','key', 'value'];
public function Objekt() {
return $this->belongsTo(Objekt::class);
}
}
class Objekt extends Model
{
use HasFactory;
protected $table = 'objekts';
protected $fillable = ['name','strasse', 'hausnummer', 'plz', 'ort', 'user_id'];
public function Mieter() {
return $this->hasMany(Mieter::class);
}
public function Company() {
return $this->belongTo(Company::class);
}
public function Objektverwaltung() {
return $this->hasMany(ObjektVerwaltung::class);
}
}
I learned that I can easily use find() as method for the Models to validate data. But in this specific case I have to check for the objekt_id (foreign key in objektverwaltung) and not for the ID and because of that I cant use find(). But if I use where or another method I cant use my policy and always getting unauthorized.
I tried to use the with method on the model but maybe there is a better way to my problem. I strongly believe.
Thanks!
This could be solution, but I am getting always "Unauthorized" and do not get to the policy: $objekt= ObjektVerwaltung::where('objekt_id', $objektId)->get(); $this->authorize('view', $objekt);
I solved this issue. I had to use my ObjektPolicy, because I am using the objekt_id Key.
So I have Laravel Notifications setup and it's working perfectly fine.
However, I've extend the migration to include an additional id field:
$table->integer('project_id')->unsigned()->nullable()->index();
Thing is, I don't see how I can actually set that project_id field. My notification looks like this:
<?php
namespace App\Notifications\Project;
use App\Models\Project;
use App\Notifications\Notification;
class ReadyNotification extends Notification
{
protected $project;
public function __construct(Project $project)
{
$this->project = $project;
}
public function toArray($notifiable)
{
return [
'project_id' => $this->project->id,
'name' => $this->project->full_name,
'updated_at' => $this->project->updated_at,
'action' => 'project-ready'
];
}
}
So ya, I can store it in the data, but what if I want to clear the notification specifically by "project" instead of by "user" or by "notification".
For instance if they delete the project, I want the notifications for it cleared, but there is no way to access that unless I do some wild card search on the data column.
So is there anyway to insert that project_id in the notification ?
You could create an Observer to update the field automatically.
NotificationObserver.php
namespace App\Observers;
class NotificationObserver
{
public function creating($notification)
{
$notification->project_id = $notification->data['project_id'] ?? 0;
}
}
EventServiceProvider.php
use App\Observers\NotificationObserver;
use Illuminate\Notifications\DatabaseNotification;
class EventServiceProvider extends ServiceProvider
{
public function boot()
{
parent::boot();
DatabaseNotification::observe(NotificationObserver::class);
}
}
And you should be able to access the table using the default model to perform actions.
DatabaseNotification::where('project_id', 11)->delete();
I have a model named 'Poll'. Inside Poll model I defined a boot method like follows:
public static function boot()
{
parent::boot();
self::created(function($model){
// dd($model);
$speakers = $model->speakers()->get();
// dd($speakers);
// What I want to do here is: create poll options relation from speakers as follows
// $poll->poll_options()->create([
// 'option' => $speaker->name,
// ]);
}
}
I am adding the speakers relation and it is working perfect.
But inside this boot method, inside self::created if I tried to get the speakers relation, it is always empty (dd($speakers) line). Is it because of the boot method runs just after the model is saved into DB and the relations not at all saved?
I am getting newly created model in the line: dd($model) mentioned in the code.
UPDATE
I tried with events also.
My Poll Model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\CrudTrait;
use Cookie;
use App\Events\PollCreated;
class Poll extends Model
{
........
protected $events = [
'created' => PollCreated::class,
];
.......
public function speakers()
{
return $this->belongsToMany('App\Models\Speaker','poll_speaker','poll_id','speaker_id');
}
}
app/Events/PollCreated.php:
namespace App\Events;
use App\Models\Poll;
use Illuminate\Queue\SerializesModels;
class PollCreated
{
use SerializesModels;
public $poll;
/**
* Create a new event instance.
*
* #param Poll $poll
* #return void
*/
public function __construct(Poll $poll)
{
// $this->poll = $poll;
$event = $poll->event()->first();
// dd($event);
// dd($poll->speakers()->get());
// dd($poll->load('speakers'));
}
}
Here also I am not getting speakers, in the line: dd($poll->speakers()->get());
my Speaker model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\CrudTrait;
class Speaker extends Model
{
use CrudTrait;
……..
public function polls()
{
return $this->belongsToMany('App\Models\Poll');
}
……..
}
The problem is with timing as models must always be created before they can be set in a many-to-many relationship. So there is no possible way that in a many-to-many relationship during the created event the relationship is already set as the created events are always raised before the relationships.
Anyone looking for a solution can probably experiment with the chelout/laravel-relationship-events package as this adds relationship events to models.
To be sure, I tested this out with a simple application of users and computers.
User.php
class User extends Model
{
use HasBelongsToManyEvents;
public static function boot() {
parent::boot();
self::created(function($model){
Log::info('user::created');
});
static::belongsToManyAttaching(function ($relation, $parent, $ids) {
$ids = implode(' & ', $ids);
Log::info("Attaching {$relation} {$ids} to user.");
});
static::belongsToManyAttached(function ($relation, $parent, $ids) {
$ids = implode(' & ', $ids);
Log::info("Computers {$ids} have been attached to user.");
});
}
public function computers() {
return $this->belongsToMany(Computer::class, 'user_computers');
}
}
Computer class is the same in reverse. And for the following code:
$user = User::create();
$user->computers()->attach([
Computer::create()->id,
Computer::create()->id
]);
This was the outcome:
user::created
computer::created
computer::created
Attaching computers 69 & 70 to user.
Computers 69 & 70 have been attached to user.
Here's my edit function in the controller
public function edit($id)
{
$game = Game::find($id);
// build list of team names and ids
$allTeams = Team::all();
$team = [];
foreach ($allTeams as $t)
$team[$t->id] = $t->name();
// build a list of competitions
$allCompetitions = Competition::all();
$competition = [];
foreach ($allCompetitions as $c)
$competition[$c->id] = $c->fullname();
return View::make('games.edit', compact('game', 'team', 'competition'));
}
I am sending data in order to display in a select list. I know about Eloquent ORM method Lists, but the problem is as far as I know it can only take property names as an argument, and not methods (like name() and fullname()).
How can I optimize this, can I still use Eloquent?
I would look into attributes and appends. You can do what you would like by adjusting your models.
Competition
<?php namespace App;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
class Competition extends Model
{
protected $appends = ['fullname'];
...
public function getFullnameAttribute()
{
return $this->name.' '.$this->venue;
}
}
Team
<?php namespace App;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
class Team extends Model
{
protected $appends = ['name'];
...
public function getNameAttribute()
{
return $this->city.' '.$this->teamName;
}
}
Controller
public function edit($id)
{
$game = Game::find($id);
$team = Team::get()->lists('id','name');
$competition = Competition::get()->lists('id','fullname');
return View::make('games.edit', compact('game', 'team', 'competition'));
}
The only thing I can think of (aside from using the map functionality of Eloquent collections) is to overwrite the toArray method in your model to add some custom attributes.
Eg.
public function toArray()
{
return array_merge(parent::toArray(), [
'fullname' => $this->fullname(),
]);
}
This will allow you to use something like:
$competition = $allCompetitions->fetch('fullname');
Although:
In saying all this I think the more elegant solution is to just provide the whole competition objects to the view and let the loop where you render them (or whatever) call the method itself.
You can call model method in view file if they are not related with other models. So if name() & fullname() returns result related to this model then you can use this model methods in view
#foreach (($allTeams as $t)
{{ $t->name() }}
#endforeach
ofcourse you have to pass the $allteams collection from controller to view
I'm trying to get an array of all of my model's associations. I have the following model:
class Article extends Eloquent
{
protected $guarded = array();
public static $rules = array();
public function author()
{
return $this->belongsTo('Author');
}
public function category()
{
return $this->belongsTo('Category');
}
}
From this model, I'm trying to get the following array of its relations:
array(
'author',
'category'
)
I'm looking for a way to pull this array out from the model automatically.
I've found this definition of a relationsToArray method on an Eloquent model, which appears to return an array of the model's relations. It seems to use the $this->relations attribute of the Eloquent model. However, this method returns an empty array, and the relations attribute is an empty array, despite having my relations set up correctly.
What is $this->relations used for if not to store model relations? Is there any way that I can get an array of my model's relations automatically?
It's not possible because relationships are loaded only when requested either by using with (for eager loading) or using relationship public method defined in the model, for example, if a Author model is created with following relationship
public function articles() {
return $this->hasMany('Article');
}
When you call this method like:
$author = Author::find(1);
$author->articles; // <-- this will load related article models as a collection
Also, as I said with, when you use something like this:
$article = Article::with('author')->get(1);
In this case, the first article (with id 1) will be loaded with it's related model Author and you can use
$article->author->name; // to access the name field from related/loaded author model
So, it's not possible to get the relations magically without using appropriate method for loading of relationships but once you load the relationship (related models) then you may use something like this to get the relations:
$article = Article::with(['category', 'author'])->first();
$article->getRelations(); // get all the related models
$article->getRelation('author'); // to get only related author model
To convert them to an array you may use toArray() method like:
dd($article->getRelations()->toArray()); // dump and die as array
The relationsToArray() method works on a model which is loaded with it's related models. This method converts related models to array form where toArray() method converts all the data of a model (with relationship) to array, here is the source code:
public function toArray()
{
$attributes = $this->attributesToArray();
return array_merge($attributes, $this->relationsToArray());
}
It merges model attributes and it's related model's attributes after converting to array then returns it.
use this:
class Article extends Eloquent
{
protected $guarded = array();
public static $rules = array();
public $relationships = array('Author', 'Category');
public function author() {
return $this->belongsTo('Author');
}
public function category() {
return $this->belongsTo('Category');
}
}
So outside the class you can do something like this:
public function articleWithAllRelationships()
{
$article = new Article;
$relationships = $article->relationships;
$article = $article->with($relationships)->first();
}
GruBhub, thank you very much for your comments. I have corrected the typo that you mentioned.
You are right, it is dangerous to run unknown methods, hence I added a rollback after such execution.
Many thanks also to phildawson from laracasts, https://laracasts.com/discuss/channels/eloquent/get-all-model-relationships
You can use the following trait:
<?php
namespace App\Traits;
use Illuminate\Database\Eloquent\Relations\Relation;
trait EloquentRelationshipTrait
{
/**
* Get eloquent relationships
*
* #return array
*/
public static function getRelationships()
{
$instance = new static;
// Get public methods declared without parameters and non inherited
$class = get_class($instance);
$allMethods = (new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC);
$methods = array_filter(
$allMethods,
function ($method) use ($class) {
return $method->class === $class
&& !$method->getParameters() // relationships have no parameters
&& $method->getName() !== 'getRelationships'; // prevent infinite recursion
}
);
\DB::beginTransaction();
$relations = [];
foreach ($methods as $method) {
try {
$methodName = $method->getName();
$methodReturn = $instance->$methodName();
if (!$methodReturn instanceof Relation) {
continue;
}
} catch (\Throwable $th) {
continue;
}
$type = (new \ReflectionClass($methodReturn))->getShortName();
$model = get_class($methodReturn->getRelated());
$relations[$methodName] = [$type, $model];
}
\DB::rollBack();
return $relations;
}
}
Then you can implement it in any model.
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
use App\Traits\EloquentRelationshipTrait;
class User extends Authenticatable
{
use Notifiable, HasApiTokens, EloquentRelationshipTrait;
Finally with (new User)->getRelationships() or User::getRelationships() you will get:
[
"notifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"readNotifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"unreadNotifications" => [
"MorphMany",
"Illuminate\Notifications\DatabaseNotification",
],
"clients" => [
"HasMany",
"Laravel\Passport\Client",
],
"tokens" => [
"HasMany",
"Laravel\Passport\Token",
],
]
I have published a package in order to get all eloquent relationships from a model. Such package contains the helper "rel" to do so.
Just run (Composer 2.x is required!):
require pablo-merener/eloquent-relationships
If you are on laravel 9, you are able to run artisan command model:show