How do I extend two models without having to redefine their relations to target the 'new' extended model?
Say I have two related Eloquent model: Shop and Item in a package.
Shop defines a HasMany relation to Item.
Item defines a HasOne relation to Shop.
Now say I want to extend both models in some other package. Maybe something like:
class LuxuryShop extends Shop{
protected static function booted()
{
static::addGlobalScope(new LuxuryScope);
}
}
// ...
class LuxuryItem extends Shop{
protected static function booted()
{
static::addGlobalScope(new LuxuryScope);
}
}
The problem I have now is that the relations are still pointing to the 'base' model without the new functionality (be it a scope or anything else);
$anyShops = Shop::with('items')->get();
$anyShops->first()->items; // this is a collection of Item model;
$luxuryShops = LuxuryShop::with('items')->get();
$luxuryShops->first()->items; // this is a collection of Item model;
Is it possible to get 'LuxuryItem' from 'LuxuryShop's items HasMany relation without redefining it in the extended model?
I suspect contextual bindings might have a solution but not quite sure how I'd define it.
Please note that actual use-case is many relations over many models used in different contexts (where each consuming context extends the base model). For only a one-off like this example I'd just go ahead and redefine the relation.
This takes place in Laravel 8
I wonder why don't you use two Traits in order to implements these especial features and use as required in your case, instead of extending your models.
https://www.php.net/manual/en/language.oop5.traits.php
Related
In eloquent you can have different models operating on a same table.
So, is it possible to switch an object from one Model to another in a runtime.
Assuming that we have three classes:
Product extends Model
ProductSet extends Product
SimpleProduct extends Product
The difference between ProductSet and a SimpleProduct is column complex which has 'true' in it for a ProductSet and 'false' for a SimpleProduct.
ProductSet and a SimpleProduct models have Scopes applied to them so that ProductSet::all() will get only sets, and SimpleProduct::all() will get only a simple products.
Parent model however allow us to get both types of products via Product::all()
so, is it possible to go through all instances of Product class and switch them to their higher level models at runtime based on a value of property complex ?
Since you're already extending the class which holds the same props as the parent. You can loop over any product collection and get all the attributes through getAttributes() method and pass to the parent. Because each of your model is extending Eloquent Model which accepts attribute in the constructor and fills its attribute.
Following example extends the default User class and converts back to the Parent class
<?php
namespace App;
class Admin extends User {}
Get all Admins and convert them to User
$admins = \App\Admin::all();
$users = [];
foreach ($admins as $admin) {
$users[] = new \App\User($admin->getAttributes());
}
dd(collect($users));
Yes you can do that, Based on the prop you have to create object of that specific class
I hope someone may help me with the issue I have for a long time, but only now I'm posting it.
The project I'm working on uses models with nested relationships. Just to simplify the context of the issue, let's imagine a parent with many children model relation: hasMany and belongsTo.
I usually create new instances and fill the properties then I relate them by hand using setRelation() and associate() methods (because I like to retrieve the relationships using parent-to-child and child-to-parent methods).
But infinite loops arises after I call toArray() (among many other model methods that traverse its relations).
The question is: Am I doing the right thing caling setRelation and associate for model relationship? If not, how would I retrieve $model->children()/$model->parent() relation?
I'm using Laravel Framework 7.14.1 with PHPUnit 8.5.5 and PHP 7.4.4 (cli)
Here a Unit test:
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use Illuminate\Database\Eloquent\Model;
class Team extends Model {
protected $fillable = ['name'];
public function players()
{
return $this->hasMany(Player::class);
}
}
class Player extends Model {
protected $fillable = ['name'];
public function team()
{
return $this->belongsTo(Team::class);
}
}
class CircularReferencesTest extends TestCase
{
public function testCircularReference(): void
{
// new instances
$team = app(Team::class)->fill(['name' => 'team name']);
$player = app(Player::class)->fill(['name' => 'player name']);
// set relations
$team->setRelation('players', collect([$player]));
$player->team()->associate($team);
dd($team->toArray(), $player->toArray()); // ERROR: Segmentation fault (core dumped).
// dd($team->push()); // push calls save() method recursively #see https://laravel.com/docs/7.x/eloquent-relationships#the-push-method
dd($team, $player);
}
}
I'm calling by:
./vendor/phpunit/phpunit/phpunit --stop-on-failure --colors=always ./tests/Unit/CircularReferencesTest.php
according to this
you can't create a relationship between 2 models that are not already represented in the database.
at least one of the models must be saved in advance, in order to create the relationship.
this is the first issue in your code ...
second one:
setting relation of one side is more than enough.. why you set the relation from the other side?
i mean:
one of those two line is enough:
$team->setRelation('players', collect([$player]));
$player->team()->associate($team);
i prefer using 'associate' method is more clearer ...
I have 3 data models, one of which extends the other:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Opinion extends Model
{
public function reactions()
{
return $this->morphMany('App\Models\Reaction', 'reactable');
}
...
}
namespace App\Models\Activity;
use App\Models\Opinion;
class ActivityOpinion extends Opinion
{
...
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Reaction extends Model
{
public function reactable()
{
return $this->morphTo();
}
...
}
The App\Models\Opinion model has a polymorphic relationship with the App\Models\Reaction model. I can retrieve all of the App\Models\Opinion reactions no problem, so I know the relationship works great.
My question is, how can I retrieve the same set of reactions from the App\Models\Activity\ActivityOpinion model? Because right now, it is looking for App\Models\Activity\ActivityOpinion as the relationship but I need it to look for App\Models\Opinion. Is it possible to mock another model in a polymorphic relationship?
This is because in a Polymorphic Relationship in the stored data (if leaved as default) the relationship type gets the class namespace (sort of) to specify wich model needs to be returned. That's why when you try to access to your reactions() relationship from ActivityOpinion it will look up for the App\ActivityOpinion value in the reactable_type.
You can customize the morph class to search in the model addind this:
Opinion.php
protected $morphClass = 'reaction';
This should be enough, if not, add it also in the ActivityOpinion model.
Note
This could breake some things when trying to search results using Eloquent. Check this other answer in order to address this possible inconviniance.
Update
I've just found out that you could do all this even easier with MorphMap. From the docs:
Custom Polymorphic Types
By default, Laravel will use the fully qualified class name to store
the type of the related model. For instance, given the one-to-many
example above where a Comment may belong to a Post or a Video,
the default commentable_type would be either App\Post or
App\Video, respectively. However, you may wish to decouple your
database from your application's internal structure. In that case, you
may define a "morph map" to instruct Eloquent to use a custom name for
each model instead of the class name:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'posts' => 'App\Post',
'videos' => 'App\Video',
]);
You may register the morphMap in the boot function of your
AppServiceProvider or create a separate service provider if you
wish.
I am pretty new to laravel and currently exploring its concepts. In some videos I saw a concept of models inheritance. I wonder if we can use models relationships in laravel 5.6 then why we need to inherit models. In which case we need to or should inherit models.
eg Base Model:
class User extends Authenticatable
{
}
eg Child Model:
Class UserTypeOne extends User()
{
}
eg Child Model2:
Class UserTypeTwo extends User
{
}
Thanks in advance.
You don't really need to inherit Models like class inherit. You should use Eloquent Relationship instead. Prior to development, you have to do proper database designing.
https://laravel.com/docs/5.6/eloquent-relationships
I am currently working on a web app that has been set up using the Repository/Service Layer Design Pattern, i.e. I have service layer that does any necessary business logic before running any methods within the repository. I have facades for each one of my models which access their respective service layers, and this has been fine for the most part. However, now that I am trying to set up Eloquent relationships, the facades seem to be causing a massive headache as I am not sure which direction I should be going.
Take the following code:
class Account extends Eloquent {
// Our table name
protected $table = "accounts";
// Our primary key
protected $primaryKey = "id";
/**
* Role Relationship
*
* Returns a list of roles associated with
* this account
*/
public function roles() {
return $this->hasMany('Role');
}
}
This will not work as is, because instead of using the entity class of Role, it is using the Role Facade. I have figured out a workaround for this, by setting an alias for the Entity with a slightly different name, such as RoleEntity so that
public function roles() {
return $this->hasMany('RoleEntity');
}
will work, however this doesn't seem like the most optimal solution.
My question is, is the practice ok? Or better yet, should this be happening at all? And if not, how do I fix it/where did I go wrong?
You have two classes with the same name in the same namespace. Use different namespaces so you can use the same class names.
I usually use \Models to locate my models classes.
At the top of each model file:
namespace Models;
In your controller or any part of your app:
\Models\Role::first();
Note that changing the namespace on your model will require you to add the namespaces of other classes i.e. Str, Eloquent, Url, Redirect, etc.
use Eloquent;
use URL;
In your model, you also have to pass the namespaces in the relationship functions, i.e.:
public function roles() {
return $this->hasMany('\Models\Role');
}