Run a method or command when a row is expired Laravel - laravel

I have a table which has user publications, when the user publish a publications he choose a date that this publication will remains visible in the platform, i want a way to flip the state of a publication that time is come to now be shown anymore (date of publication is equal or greater then date system), i know about the cronjob way but i wanted to ask here if there's a better way to do this because i think the cronjob way is not good for this i will have to run a command every minute which i prefer not to do, i only need ideas not code.
Thank you.

Do not store an active flag on the database. In your case active is a derived attribute and derived attributes should not really be stored as columns, not to mention that since active will be based on a date it is not functionally dependant on the primary key directly so is also in violation of the 2nd normal form. These two reasons will generally lead to data anomalies and what you're trying to do with a scheduled task is basically hack your way around those anomalies.
My suggested approach is to use an eloquent model attribute and attribute mutators like below:
class Publication extends Model {
protected $appends = [ 'active' ];
public function getActiveAttribute() {
return $this->activeUntil < Carbon::now(); //Still active
}
}
This way your model includes the active attribute which is computed by eloquent and not stored in the database.

Related

LatestOfMany() of BelongsToMany() relationship

I've been using latestOfmany() for my hasMany() relation to define them as hasOne() for quite a while now. Lately I've been in need of the similar application but for belongsToMany() relationships. Laravel doesn't have this feature unfortunately.
My codebase as follows:
Document
id
upload_date
identifier_code
Person
id
name
DocumentPerson (pivot)
id
person_id
person_id
token
My objective is: define relationship for fetching the first document (according to upload_date) of Person. As you can see it's a many-to-many relationship.
What I have tried so far:
public function firstDocument()
{
return $this->hasOne(DocumentPerson::class)->oldestOfMany('document.upload_date');
//this was my safe bet but oldestOfMany() and ofMany() doesn't allow aggregating on relationship column.
}
public function firstDocument()
{
return $this->belongToMany(Document::class)->oldestOfMany('upload_date')
}
public function firstDocument()
{
return $this->belongToMany(Document::class)->oldest()->limit(1);
}
public function firstDocument()
{
return $this->hasOneThrough(Document::class, DocumentPerson::class, 'id', 'document_id', 'id', 'person_id')->latestOfMany('upload_date');
}
At this point I'm almost positive current relationship base doesn't support something like this, so I'm elaborating alternative methods to solve this. My two choices:
Add a column called first_document_id on Person table, go through that with belongsTo() simple and fast performance-wise. But downside is I'll have to implement so many event-listeners to make sure it is always consistent with actual relationships. What if Document's upload_date is updates etc. (basically database inconsistency)
Add a order column on pivot (document_person) table, which will hold order of related Documents by upload_date. This way I can do hasOne(DocumentPerson::class)->oldestOfMany('order');//or just ofMany() and be done with it. This one also poses the risk of database inconsistency.
It's fair to say I'm at a crossroads here. Any idea and suggestion is welcomed and appreciated. Thank you. Please read the restrictions to prevent suggesting things that are not feasible for my situation.
Restrictions:
(Please)
It should strictly be a relationship. I'll be using it on various places, it definitely has to be relationship so I can eager load and query it. My next objective involves querying by this relationship so it is imperative.
Don't suggest accessors, it won't do well with my case.
Don't suggest collection methods, it needs to be done in query.
Don't suggest ->limit() or ->take() or ->first(), those are prone to cause inconsistent results with eager loading.
Update 1
Q: Why first document of a person has to be a relationship ?
A: Because further down the line I'll be querying it in various different instances. Example queries where it'll be utilized:
Get all the users whose first document (according to upload_date) upload_date between 2022-01-01 and 2022-06-08. (along with 10 other scopes and filters)
Get all the users whose first document (according to upload_date) identifier_code starts with "Lorem" and id bigger than 100.
These are just to name a few, there are many cases where I really gotta query it in various fashions. This is the reason that I desperately need it to be a relationship, so I can query it with ease using Person::whereHas('firstDocument',function($subQuery){ return $subQuery->someScope1()->anotherScope2()->where(...); }
If I only needed to display it, yeah sure eager loading with closure would do well, or even collection methods, or accessors would suffice. But since ability to query it is the need, relationship is of the essence. Keep in mind Person table has around 500k record, hence the need for querying it on the database layer.
Alright here's the solution I've elected to go with (among my choices, explained in the question). I implemented the "adding order column on pivot" table. Because it scales better and is rather flexible compared to other options. It allows for querying the last document, first document, third document etc. Whilst it doesn't even require any aggregate functions (Max, min like ->latestOfMany() applies) which is a performance boost. Given these constraints this solution was the way to go. Here's how I applied it in case someone else is thinking about something similar.
Currently the only noticeable downside to this approach is inability to access any additional pivot data.
Added new column for order:
//migration
$table->unsignedTinyInteger('document_upload_date_order')->nullable()->after('token');
$table->index('document_upload_date_order');//for performance
Person.php (Model)
//... other stuff
public function personalDocuments()
{//my old relationship, which I'll still keep for display/index purposes.
return $this->belongsToMany(Document::class)->withPivot('token')->where('type_slug','personal');
}
//NEW RELATIONSHIP
public function firstDocument()
{//Eloquent relationship, allows for querying and eager loading
return $this->hasOneThrough(
Document::class,
DocumentPerson::class,//pivot class for the pivot table
'person_id',
'id',
'id',
'document_id')
->where('document_upload_date_order',1);//magic here
SomeService.php
public function determineDocumentUploadDateOrders(Person $person){
$sortLogic=[
['upload_date', 'asc'],
['created_at', 'asc'],
];
$documentsOrdered=$person->documents->sortBy($sortLogic)->values();//values() is for re-indexing the array keys
foreach ($documentsOrdered as $index=>$document){
//updating through pivot tables ORM model
DocumentPerson::where('id',$document->pivot->id)->update([
'document_upload_date_order'=>$index+1,
'document_id'=>$document->id,
'person_id'=>$document->pivot->person_id,
]);
}
}
I hooked determineDocumentUploadDateOrders() into various event-listeners and model events so whenever association/disassociation occurs, or upload_date of a document changes I simply call determineDocumentUploadDateOrders() with corresponding Person and this way it is always kept in sync with actual.
Implemented it fully and it is providing consistent results with great performance. Of course it brought a bit of an overhead with keeping it in sync. But nonetheless, It did the job whilst meeting the requirements. Honestly I found this approach far more reliable than some in-official eloquent relationships and similar alternatives.
I have encountered a similar situation years back.
the best workaround on a situation like this is to use #staudenmeir package eager limit
Load the trait use \Staudenmeir\EloquentEagerLimit\HasEagerLimit; on both model (parent and related model)
then try the code below
public function firstDocument() {
return $this->documents()->latest()->limit(1);
}
public function documents() {
return $this->belongsToMany(Document::class);
}
just to add, Eager loading with limit does not work with built laravel eloquent, you would have to build your own raw queries to achieve it which can turn into a nightmare. that eager limit package from staudenmeir should have been merge with laravel source code šŸ˜†

Handle model dependencies in Laravel Repository Pattern

I'm discovering the Repository Pattern for my Laravel project but I have to say that I'm a bit lost once a model has several dependencies and the examples on the web are always basic and don't answer the question for more complex use cases.
Let's imagine a user on my app. He can have badges, he has different things on the app that will be slightly modified when he first performs the action (when he first sees the comments, I tell him once the different things he can do, etc), he has several "counters" to record the number of comments he made, the number of friends he invited, without having to count each entry each time.
My database looks like this:
users table:
id
pseudo
name
password
...
badges table:
user_id
badge1_xxxxxx
badge2_xxxxxx
...
I have a very limited number of badges so I decided to create a column for each of them and as soon as a user wins a badge, I get his entry (in OneToOne relationship) and I indicate that the badge in question has been won.
onboarding table:
user_id
seen_comments (boolean)
seen_results (boolean)
...
As you can see, I store each action I'd like the user to do in different columns and as soon as he has done one and I've been able to modify my app accordingly (by showing him an arrow, etc), I put the column in question to true.
user_counters table:
user_id
count_comments
count_invited_friends
...
I don't consider a user to be a user if he doesn't have an entry in each of the tables (I could have done everything in one table but the users table seemed to me to become huge). The only relationship used is OneToOne between the user and the table in question.
Should I do this ?
class UserRepository {
public function register($data) {
// Create the user
$user = User::create($data);
// Create all its dependencies which are required if I want to consider the user as fully registered in my DB
$user->badges()->create();
$user->onboarding()->create();
$user->counter()->create();
// Return the user
return $user;
}
}
Or should I create a Repository for each of these elements and create the entire user in a UserService ?
How far should I separate things and when does it become overkill?
Is there something that I don't understand in concept of Repository ? If so, could you give me some links that you found useful because I feel like I ran out of ideas for search keywords.
Thanks

Laravel,end of migrations relationship at some time

hi I have the following question. In laravel I created 2 tables, apartments and sponsors with many to many relationships. Depending on the plan chosen, the sponsors have a variable duration that I valued in the pivot table(start_time and end_time). I would need the relationship between the apartment and the sponsor to end automatically after the sponsorship period has ended(when the date and day coincide with the end_time of the pivot table). How could I do such a thing? I thought about using detach but I'm not sure how.
It is always a good idea to look at these problems from different perspectives. Like the two comments mention, you could use Job Scheduling In windows or cron jobs in linux systems.
The next option is to create another field in your pivot which is something like ā€œactiveā€. So this will be a boolean and will always return a true or false. Now you can combine task scheduling and using a custom artisan command, you can keep checking if the sponsor expired and if it did expire, set the active field to false.
And to top it all, when you get your data, simply filter your query. I do not know how your code looks like but here is a sample code of a pivot and how I would have filtered it (not tested):
//Here active null assumes it is false value. Active 1 assumes it is true. So the active field will be nullable
$myQuery = Sponsor::with([ā€˜apartmentsā€™ => function($q) {
$q->where(ā€˜activeā€™, null)->get();
}])->get();
//The apartments is assumed to be the pivot relationship in your sponspor model
This is just a simple example of how you could chain and query a relationship, whether be a pivot or any other table, you can do custom queries within like mentioned above. Something along the lines should work in your case

Limit to 1 on hasMany-relationship

I have two models that are related to each other. One model contains users, and the other contains all courses and related timestamp of class start. Now the "related key" between them are the 'user_id' which are in both tables. I manage to get out data when having:
return $this->hasMany(ClassInfo::class,'user_id','user_id');
This works just fine. However, since I use the model in a with clause I need to the only one of the classes that starts a given time if start time crashes with another course for the user. I have tried with both:
return $this->hasMany(ClassInfo::class,'user_id','user_id')->take(1);
return $this->hasMany(ClassInfo::class,'user_id','user_id')->limit(1);
But both just give me empty collections, I don't see why that happends?
Is there any way that I can make it return for example the one with the biggest id value from the Class table (id is auto incremental for each course registered on a user).
Thanks for any tips and guidance!

Laravel : Delete all records that are not in array

I have a datepicker calendar in my views, and basically I need to synchronize the selected dates (that I send in ajax) with a BoxDeliveryDate model (which only has one column named "date").
So in my Controller I was able to write a pretty nice method to only create a new record if one the selected dates is not yet stored in the database, like this :
foreach (Request::get('dates') as $date) {
$date_formated = date('Y-m-d', strtotime($date));
BoxDeliveryDate::firstOrCreate(['date'=>$date_formated]);
}
Now, if the user de-select one the dates in the datepicker, later, and synchronize, I need to delete it from the database.
Is there a nice way to do that in Laravel ? In other words, to delete every record of the table that are NOT in my Request::get('dates') ?
Also, I searched for a simple way to synchronize everything with only one method, but couldn't find anything.
Thanks for helping !
You can use whereNotIn() for that:
BoxDeliveryDate::whereNotIn('date', Request::get('dates'))->delete();
Note that this will not trigger any model events nor will it work with soft delete. Also, depending on the format of dates you might have to format the array before passing it to whereNotIn().
Also I believe it should be Request::input('dates') but it's possible that both actually works...
I would highly recommend using the soft delete trait (built into Laravel) if your doing mass deletes!
http://laravel.com/docs/4.2/eloquent#soft-deleting
$this->model->whereNotIn('id', $ids)->delete();

Resources