How to define a relationship that touches grandparents in eloquent - laravel

In Laravel, I would like to define a grandparent type eloquent relationship so that I can have the grandchild touch the grandparent when it is added or updated (for instance if it brings the grandparent a very nice birthday card). Say I have a hierarchy like Collection > post > comment.
--Comment--
protected $touches = array('post', 'collection');
public function post()
{
return $this->belongsTo('Post');
}
public function order()
{
// not working
// return $this->post->collection();
// not working
// return $this->belongsTo('Post')->belongsTo('Collection');
}
--Post--
public function collection()
{
return $this->belongsTo('Collection');
}
I want the update function in the Collection to run when a Comment is added. How can I set that up?
I'm using Laravel 4.1

I want the update function in the Collection to run when a Comment is
added.
I'm a bit confused about the update function but...
You can register an event handler for that, for example, add this in your Comment model:
class Comment extends Eloquent {
public static function boot()
{
parent::boot();
static::created(function($comment)
{
// $comment is the current Comment instance
});
}
}
So, whenever any Comment will be created the event handler (anonymous function) will run so you may do something in that function. Read more on Laravel Website.

Related

Laravel call controller action from other controller

I have a post model, a user model, and a point model.
Whenever a user posts a new post, they get a point added (new row in points table).
I have controllers setup for each of those models.
Should/how can I call the PointsController#store from the PostsController#store after adding the post?
Or is this the wrong way to go about it?
Full code on GitHub
Consider using a modal.
For instance, in your PointsController:
public function store() {
// your code
User::addPoint(); // this is responsible for adding the point
}
And in your PostsController
public function store() {
// your code
User::addPoint(); // call this again
}
This way, you have a model responsible for updating the post.
Option1: using models
In PostController
public function store(Request $request){
\Auth::user()->post()->create($request->all());
\Auth::user()->points()->create['point'=> 1]; // add your columns here
}
In Point model
protected $fillable = ['point']; //add here list of columns you want to use on \Auth::user()->points->create['point'=> 1];
Option2: use an observer
create an observer class like here. I would make an Observers folder in app/
https://laravel.com/docs/5.5/eloquent#observers
Call it PostObserver
in PostObserver in create method:
public function created(Post $post)
{
$post->user()->points()->create['point'=> 1]; // [column1=>value1, column2=>value2]
}
In Point model
protected $fillable = ['point']; //add here list of columns you want to use on \Auth::user()->points->create['point'=> 1];
In AppServiceProvider in method boot add
public function boot()
{
Post::observe(PostObserver::class);
}
Don't forget to add the correct namespaces and then add use Observers\PostObserver in AppServiceProvider

How to create custom model events laravel 5.1?

I want to create a custom model event in laravel 5.1.
For e.x. when an Articles category is updated i want to make an event and listen to it.
$article = Article::find($id);
$article->category_id = $request->input('category_id');
// fire an event here
You should use Eloquent Events (do not confuse with Laravel Events).
public function boot()
{
Article::updated(function ($user) {
// do some stuff here
});
}
You would want to look into Observers to make this more reusable and single-responsible, though a starting point would be something alike:
public function boot()
{
self::updated(function ($model) {
if (array_key_exists('category_id', $model->getDirty())) {
// Log things here
}
});
}
Laravel will populate a 'dirty' array which contains modified fields. You can detect when a certain field has changed using this.
You also have:
$model->getOriginal('field_name') // for this field value (originally)
$model->getOriginal() // for all original field values
You can use Attribute Events to fire an event when the category_id attribute changes:
class Article extends Model
{
protected $dispatchesEvents = [
'category_id:*' => ArticleCategoryChanged::class,
];
}

Laravel 4.1 eloquent model set appends dynamically

I am using Laravel 4.2.
I have two models: User and Video, both of these models are having one-to-many relationship i.e. User -> HasMany-> Video.
Recently, I got a requirement to display the list of users along with sum of file-size of total videos uploaded by each user and allow users to be order by the sum of file size ascending or descending.
I've made following changes in User model:
class User extends Eloquent {
protected $hidden = array('videosSum');
protected $appends = array('videos_size');
public function videosSum() {
return $this->hasOne('Video')
->selectRaw('sum(file_size) as sum, user_id')
->groupBy('user_id');
}
public function getVideosSizeAttribute()
{
// if relation is not loaded already, let's do it first
if ( ! array_key_exists('videos_size', $this->relations)){
$this->load('videosSum');
}
$related = $this->getRelation('videosSum');
return $this->attributes['videos_size'] = isset($related->sum) ? (int) $related->sum : 0;
}
}
And using like:
User::where('id', '!=', Auth::user()->id);
I am getting the desired result.
But the problem is, I don't want the videos_size attribute everywhere, where the User model gets called. I want to set it dynamically.
I tried User::$appends = ['videos_size'] but it gives protected property cannot be set outsize of class error.
I also tried to make a method in User model which set the $appends if called, but it is also not working.
Can anybody help me how to enable the appends property dynamically?
Laravel doesn't support this off the bat.
my friend and I wrote this extention:
Dynamically hide certain columns when returning an Eloquent object as JSON?
basically you have to override your models.php toArray() method as appended attributes get calculated when you ask for the model in json or array form.
you can add to the trait that's in that link and use it or just put these methods in your respective model class.
public static function getStaticAppends() {
return self::$_appends;
}
public static function setStaticAppends(array $value) {
self::$_appends = $value;
return self::$_appends;
}
public static function getDefaultAppends() {
return with(new static)->getAppends();
}
public function getAppends(){
return $this->appends;
}
public function toArray() {
if (self::getStaticAppends()) {
$this->appends = self::getStaticAppends();
}
return parent::toArray();
}

How to catch model events in pivot tables

I want to track (record) the changes made to each database row. This means, saving a log of each action (insert, update, delete) made to each record of each table.
This issue is solved for models, as they extend from a BaseModel and I'm using model events. However, I cannot seem to find a way to record the changed from pivot tables.
Given the following tables users, profiles and profile_user(profile_id, user_id), I have the following code:
class User extends BaseModel {
public function profiles() {
return $this->belongsToMany('Profile');
}
}
class Profile extends BaseModel {
public function users() {
return $this->belongsToMany('User');
}
}
abstract class BaseModel extends Model {
public static function boot() {
parent::boot();
static::created(function($model) {
return LogTracker::saveRowCreatedOrUpdated($model, true);
});
static::updated(function($model) {
return LogTracker::saveRowCreatedOrUpdated($model, false);
});
static::deleted(function($model) {
return LogTracker::saveRowDeleted($model);
});
}
}
This allows me to record the changes from user and profile but not from profile_user.
I've tried to create a ProfileUser model that extends from Pivot (Illuminate\Database\Eloquent\Relations\Pivot) where I defined the model events but that didn't work.
I'm guessing that's because I never create a new instance of this model. So, I've added the following to my User model (and similar code to Profile):
class User extends BaseModel {
// (...)
public function newPivot(Eloquent $parent, array $attributes, $table, $exists) {
if ($parent instanceof Profile) {
return new ProfileUser($parent, $attributes, $table, $exists);
}
return parent::newPivot($parent, $attributes, $table, $exists);
}
// (...)
}
Still, the events are never fired (actually this method is never executed).
I am updating the relationship through sync():
$user->profiles()->sync(explode(',', $values["profiles"]));
I'm looking for a solution that doesn't involve firing a custom event (as this means that I would have to do this for each pivot table in database).
How can I use model events in pivot tables?
I am aware you don't want a custom event situation but I cannot find any non-custom solution.
You can however do a very simple custom one (pretty much pulled right from Laravel docs):
DB::listen(function($sql, $bindings, $time)
{
//regex matching for tables in SQL query
//as well as firing your event handling code
});
http://laravel.com/docs/4.2/database#running-queries

Eloquent construction returning too many results

I have the following code in my DesignsController:
public function show($id)
{
return Design::find($id)->with('variables')->get();
}
When I GET /designs/1 I should get back json of just the design with id=1, but I get back all the current designs.
In the design model:
/* Define relationship between designs and variables */
public function variables()
{
return $this->hasMany('Variable');
}
The routes.php:
Route::resource('designs', 'DesignsController');
What am I doing wrong here?
Edit: a bit more information. I get all the results back as long as I hit an id of an actual design, so it seems to be finding the result according to the id, but then returning all results.
If I remove ->with('variables')->get(); then this works, but I need the variables too. Here's the model for Design:
class Design extends Eloquent {
/* Define relationship between designs and variables */
public function variables()
{
return $this->hasMany('Variable');
}
/* Define relationship between designs and variables */
public function user()
{
return $this->belongsTo('User');
}
}
Variable model:
class Variable extends Eloquent {
public $timestamps = false;
}
You're doing your "with" statement incorrectly:
Eager load:
public function show($id)
{
return Design::with('variables')->find($id);
}
Actually I think you're problem was calling get() after find() since find already returns a model. Find should be called at the end of a query you build because it essentially calls get() inside of it.
Lazy-Eager alternative:
public function show($id)
{
return Design::find($id)->load('variables');
}

Resources