I have following table:
$table->boolean('prop_enabled');
$table->boolean('prop1_enabled')->nullable();
$table->boolean('prop2_enabled')->nullable();
$table->boolean('prop3_enabled')->nullable();
My requirement is when prop_enabled is false, then prop1_enabled, prop2_enabled and prop3_enabled values shall be set to null during row insertion or update.
Is my table design is appropriate for solving the problem?
If my table is good to solve the problem, which approach is better to handle my requirement? I have options to create trigger on database, or to override Create and save method from Model class.
I believe the best approach is doing the Observer solution. Using an mutator, requires a lot of mutators to secure your data structure and / or business logic that can ignore it. The observer is using events and there for you can only circumvent it from triggering by avoiding using eloquent.
You can hook into the saving event on Models. This will look at the model before saving it, then alter it, if your case is true and thereby creating the optimal 1 query insertion. No matter how your business logic is, this will trigger before the creation or updating.
class YourModelObserver {
public function saving(YourModel $model) {
if ($model->prop_enabled === false) {
$model->prop1_enabled = false;
$model->prop2_enabled = false;
$model->prop3_enabled = false;
}
}
}
In a provider you have to register your listener.
public function boot()
{
YourModel::observe(YourModelObserver::class);
}
My biggest problem with your design is a common code smell i have seen before. Naming variables after numbers, when you have multiple just seems wrong for me. What happens if you get the 4. prop and how will you handle showing them and updating em? An alternative approach would be to have em in a more generic fashion in another table when they had a name or key instead of being prop1 or prop2 and having logic associated with creating them or updating em. If you are certain the prop1, prop2 and prop3 are not gonna change the design is good for now.
Related
When we inserting or updating the data via Eloquent relationship model, which is the best approach to use?
Example
$user->profile->update(['salary' => 5000]);
vs
$user->profile()->update(['salary' => 5000]);
I understand that
$user->profile() will return the relationship class such as Illuminate/Database/Eloquent/Relations/HasOne
$user->profile will return the actual UserProfile model class
I somehow remember I saw someone recommended to use $user->profile->update() instead of $user->profile()->update() but I couldn't find the article or reference link anymore
However, I found that if $user->profile is null, then it might caused an error such as
Call to a member function update() on null
So will it be easier to use relationship function update all the time?
$user->profile()->create()
$user->profile()->update()
$user->profile()->save()
$user->profile()->delete()
Is there any situation we should use the $user->profile->save() instead?
Or should one use it when it is in the multiple nested relationship?
$user->profile->bank()->create()
$user->profile()->bank()->create()
Update
reference link (for my own understanding)
https://github.com/laravel/framework/issues/13568
https://github.com/laravel/framework/issues/2536
Eloquent attach/detach/sync fires any event?
Conclusion
For now, will use code below in application, both will trigger events
if ($user->bank === null) {
$user->bank()->save(new UserBankAccount($input)); // trigger created event
// $user->bank()->create($input);// trigger created event
} else {
$user->bank->update($input); // trigger updated event
// $user->bank()->update($input); // will NOT trigger updated event
}
You can use forceFill()
Example:
$user->bank->forceFill($data)->save();
I am currently moving over from symfony to laravel, it's quite a bit different when it comes to the database. So i have a basic model, i'm just going to use an example:
class Test extends Model
{
use HasFactory;
}
All good, i have a migration and the table created. However, i don't like this:
$test = new Test();
$test->my_field = 'hello';
$test->save();
I don't like it because it's having to use a magic __set() to create the parameter, if i define the parameter in my model like this:
class Test extends Model
{
use HasFactory;
public ?string $my_field;
}
I get database errors when it tries to insert when i define the params like this. Why is that? It's doing the same thing as __set() but i'm actually physically defining them, which in my opinion is a better way to code it as my IDE can typehint and it's just nicer to follow the program knowing what params are there.
What's the reason for it inserting when i don't define them, and not when i do? From my actual table which is bookings , has a field booking_ref:
General error: 1364 Field 'booking_ref' doesn't have a default value (SQL: insert into booking_reviews (updated_at, created_at) values (2021-12-13 14:13:08, 2021-12-13 14:13:08))
This happens when i define the $booking_ref param on the model, but if i take it out and rely on the __set() method it works fine. Doesn't make any sense to me right now.
I think this is a reasonable enough misunderstanding to be useful to future visitors, so I want to try to explain what's going on with some pseudo-code and some references to the current source code.
You are correct that when setting a property on a Laravel model, that is a column in the DB, internally Laravel is using the PHP magic method __set.
What this does is allow you to 1) set properties directly instead of calling some kind of setter function, and 2) interact with your table columns without needing the boilerplate of column definitions in your model.
Where the assumptions go wrong is with what __set is doing. __set does not have to simply set an actual property with the same name. __set is just a method you may implement to do whatever you want. What you assumption implies is that it's doing something like this:
public function __set($key, $value)
{
$this->{$key} = $value;
}
However, you can do whatever you want with the $key and $value passed to the magic method.
What Laravel does is call another method defined in the HasAttributes trait - setAttribute.
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
setAttribute does a few extra things, but most importantly it adds the key/value pair to Model property $this->attributes[].
To hopefully help this difference make sense, here is what the two __set methods would yield with a basic example:
$model->my_column = 'value';
// 1st example
/**
* {
* public $my_column = 'value';
* }
*/
// Laravel way
/**
* {
* protected $attributes= ['my_column => 'value'];
* }
*/
I won't go through both saving and updating since they're very similar, but to show how this is used, we can look at the save method, which calls performInsert and after a few more calls makes it's way back to the attributes property to determine what to actually insert into the query.
Summary
Laravel does not use custom model properties when deciding what column/values to add to queries.
This is why when you create custom mutators, you interact with the attributes property just like Laravel does internally.
Anytime you introduce "magic" into code, you have some tradeoffs. In this case, that tradeoff is slightly less clarity with what database columns are actually available. However, like I mentioned in comments, there are other solutions to make models more IDE friendly like Laravel IDE helper.
From Laravel's docs, the model polymorphism is defined as follows:
Polymorphic relations allow a model to belong to more than one other model on a single association
Sounds like it's designed to work with belongsTo instead of hasMany side. Here's a scenario that I want to achieve:
In my system, there are many project types, each projec type will have its own invoice field layout. Let's say we have a Project model that has a type field, whose value could be contract or part-time. We have another two tables called ContractInvoice and PartTimeInvoice to define their respective field layout, both of these invoice tables have a project_id referencing a project record. What I want to do is I want a universal interface to retrieve all invoices given a project, something like $project->invoices.
Current solution
I can't figure out how to achieve this via polymorphism. So what I am currently doing is kind silly, using a switch statement in my invoice() method on Project model class:
switch ($this->type) {
case 'contract':
$model = 'App\ContractInvoice';
break;
case 'part-time':
$model = 'App\PartTimeInvoice';
break;
}
return $this->hasMany($model);
I feel like there must be a better way to do this. Can someone please shed some light?
I don't see how a polymorphic relationship would be beneficial in this case. If you had different project type models and a single invoices table, then the invoices could morphTo the projects. But as you've described it, the switch statement sounds like it is adequate. You could achieve the same means using when conditionals like:
public function invoices()
{
return $this->when($this->type === 'contract', function () {
return $this->hasMany(ContractInvoice::class);
})->when($this->type === 'part-time', function () {
return $this->hasMany(PartTimeInvoice::class);
});
}
The type attribute on the Project model and the separate invoice tables are defining a rigid relationship between them, which goes against the idea of polymorphism. Think likes for comments and posts.
after some digging I still could not find any solid way to retrieve the inverse of a many-to-many polymorphic relation that allows mixed models results.
Please consider the following:
I have several models that can be "tagged". While it is trivial to retrieve for example $item->tags, $article->tags and the inverse with $tag->articles and $tag->items I have no easy way to do something like $tag->taggables to return both articles and items in the same collection. Things get even bumpier as I need to use pagination/simple pagination to the query.
I have tried a few workarounds but the best I could put together still looks crappy and limited. Basically:
I queried the DB once per "taggable";
put all in a single big collection;
passed the collection to a phpleague/fractal transformer (my API uses it) that returns different json values depending on the parsed models.
The limits of this approach is that building a pagination is a nightmare and fractal "include" options can't be used out of the box.
Can anyone help me? I'm currently using Laravel 5.1.
There is not much magic in my current code. Faking and simplifying it to make it short:
From the api controller:
$tag = Tag::findOrDie($tid);
$articles = $tag->cms_articles()->get();
$categories = $tag->cms_categories()->get();
$items = $tag->items()->simplePaginate($itemsperpage);
$taggables = Collection::make($articles)->merge($categories);
// Push items one by one as pagination would dirt the collection struct.
foreach ($items as $item) {
$taggables->push($item);
}
return $this->respondWithCollection($taggables, new TaggableTransformer);
Note: using simplePaginate() is there only because I would like all articles and categories to be shown on first page load while the number of items are so many that need pagination.
From the Transformer class:
public function transform($taggable)
{
switch (get_class($taggable)) {
case 'App\Item':
$transformer = new ItemTransformer;
break;
case 'App\CmsArticle':
$transformer = new CmsArticleTransformer;
break;
case 'App\CmsCategory':
$transformer = new CmsCategoryTransformer;
break;
}
return $transformer->transform($taggable);
}
Please consider that the other transformers are simply returning arrays of data about the models they correlate with. If you use Fractal you would easily spot that nested "included" models would not be applied.
Nothing fancy for the Tag model:
class Tag extends Model
{
protected $morphClass = 'Tag';
protected $fillable = array('name', 'language_id');
public function cms_articles() {
return $this->morphedByMany('App\CmsArticle', 'taggable');
}
public function cms_categories() {
return $this->morphedByMany('App\CmsCategory', 'taggable');
}
public function items() {
return $this->morphedByMany('App\Item', 'taggable');
}
// Would love something like this to return inverse relation!! :'(
public function taggables() {
return $this->morphTo();
}
}
I am also considering the option to do 3 separate calls to the API to retrieve articles, categories and items in three steps. While in this particular scenario this might make sense after all, I would still need to deal with this particular inverse relation headache with another part of my project: notifications. In this particular case, notifications would have to relate to many different actions/models and I would have to retrieve them all in batches (paginated) and sorted by model creation date...
Hope this all makes sense. I wonder if a completely different approach to the whole inverse "polymorphic" matter would help.
Kind regards,
Federico
Ah yes. I was down your path not all that long ago. I had the same nightmare of dealing with resolving the inverse of the relationship of polymorphic relationships.
Unfortunately polymorphic relationships haven't been given much attention in the Laravel ecosystem. From afar they look like unicorns and rainbows but soon you're fighting things like this.
Can you post an example of a $thing->taggable for a better picture? Think it may be solvable with a dynamic trait + accessor magic.
I'd like to include some additional functions in my Doctrine 2 entities to contain code that I'm going to have to run quite frequently. For example:
User - has many Posts
Post - has a single user
I already have a function $user->getPosts(), but this returns all of my posts. I'm looking to write a $user->getActivePosts(), which would be like:
$user->getPosts()->where('active = true') //if this were possible
or:
$em->getRepository('Posts')->findBy(array('user'=>$user,'active'=>true)) //if this were more convenient
As far as I can tell, there's no way to get back to the entity manager though the Entity itself, so my only option would be
class User {
function getActivePosts() {
$all_posts = $this->getPosts();
$active_posts = new ArrayCollection();
foreach ($all_posts as $post) {
if ($post->getActive()) {
$active_posts->add($post);
}
}
return $active_posts;
}
However, this requires me to load ALL posts into my entity manager, when I really only want a small subset of them, and it requires me to do filtering in PHP, when it would be much more appropriate to do so in the SQL layer. Is there any way to accomplish what I'm looking to do inside the Entity, or do I have to create code outside of it?
I think you should implement the method on the PostRepository rather than on the entity model.
I try to keep all model related logic in the repositories behind "domain specific" methods. That way if you change the way you represent whether a post is active or not, you only have to change the implementation of a single method instead of having to find all the active = true statements scattered around in your application or making changes in an "unrelated" entity model.
Something like this
PostRepository extends EntityRepository {
public function findActiveByUser($user){
// whatever it takes to get the active posts
}
}