Can I cast one Eloquent model to another? - php

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

Related

Can a related pair of Models be extended without redefining their relationship?

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

How can I mock a different model to retrieve a polymorphic relationship?

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.

Laravel - Single Model for Multiple Tables

I want to use a single Model file for multiple tables.
Why???
The Table structure of all the tables is same
I have few columns to be stored as JSON Arrays and I would like to use Laravel's built in Json Serialization rather than manually serializing Arrays.
I have already read on laracast blog that it's not possible in Laravel but is there any other way to make it possible.
Thanks in advance!!!
You can just create a base model that has the logic that is common to all the models, and then create your individual models that inherit from the base model.
class Auto extends Model
{
protected $casts = [
'details' => 'json',
];
public function getWheelsAttribute()
{
return $this->details->wheels;
}
}
class Car extends Auto
{
// models your "cars" table
}
class Truck extends Auto
{
// models your "trucks" table
}
class Bus extends Auto
{
// models your "buses" table
}
Or, you could create a trait with the common functionality and use the trait in all your child models.
trait HasJsonDetails
{
protected $casts = [
'details' => 'json',
];
public function getWheelsAttribute()
{
return $this->details->wheels;
}
}
class Car extends Model
{
// models your "cars" table
use HasJsonDetails;
}
class Truck extends Model
{
// models your "trucks" table
use HasJsonDetails;
}
class Bus extends Model
{
// models your "buses" table
use HasJsonDetails;
}
Or, another option, if the table structure truly is and will always be the same, would be to combine all your tables into one table and use single table inheritance to have multiple models all use the same table.
With this method, you would add a type field to your table to tell you which class to use to model the individual row. It also requires some customization, but you can find an STI package to use, or follow this forum thread for more information:
https://laravel.io/forum/02-17-2014-eloquent-single-table-inheritance
This, of course, would still need to be combined with one of the methods mentioned above to share implementation logic across the multiple models.

Laravel on some kind of Model Ready method

Well i don't know how to format the title of this post in very clear way, but here's my question:
Say i have
Posts::find('1);
Photos:find('1');
... and so on, every mode db request
now by default i can access db columns, for instance the id: through model->id
$Photos = Photos::find('1')->first();
echo $Photos->id; // will return 1
what i want is that i need all those kind of requests to add a custom field automatically like hashed_id, which is not in the database, which in return will make all models have a hashed_id as well, i know i can add that field to database and then grab it but i need it for different reasons/implementations
i did create a BaseModel and every Model will extend that BaseModel, so Photos extends BaseModel, BaseModel extends Model... and all that etc etc.
but i need some kind of constructor, upon retrieving data to process the data automatically without having to add -let's say- a hash_id() after retrieving the data.
something like, onAfterGet(), onReady()....sort of commands.
i hope my question is clear.
Thanks.
What you're looking for is an Accessor. Accesors can be used to add custom attributes to the model. Combine this with the $appends property and you have exactly what you need. The $appends property adds the custom accessor in every result.
You can do this by creating a base model like you've stated in the question or by using traits. I'll show you an example on how to achieve this using a base model.
Let's create base model called BaseModel. All other models that need this custom attribute will extend this.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
protected $appends = ['hashed_id'];
public function getHashedIdAttribute()
{
return some_hash_function($this->id);
}
}
We have a Image model which extends our BaseModel.
<?php
namespace App;
class Image extends BaseModel
{
}
Now every result from the Image model will have the hashed_id field added by default.
Accesor documenation https://laravel.com/docs/5.4/eloquent-mutators#defining-an-accessor
If I understand you right, all you need to do is to define mutator, for example:
<?php
class Photo extends Model
{
/* ... model implementation ... */
public function getHashedIdAttribute()
{
return md5($this->id);
}
}
Then you can access property like it was in database:
echo Photo::find(5)->hashed_id;

Modeling an Object with one-to-many relationships (PHP)

I am trying to force myself to be consistent when modeling objects, but I'm just not sure what the best way is to create a class that has one-to-many relationships.
For example, lets say I have two tables in a database called Projects & Notes. A note can only belong to one project and a project can have many notes. Would the following be a good (best) way to model my project class? Or should the notes just be set to a separate variable in the controller and never be a property of project?
class Project extends BaseModel{
$id //string
$description //string
$notes //array of note objects
}
class Note extends BaseModel{
$id //string
$text//string
}
//In a Controller Class
$project = new Project();
$noteArray = new Note();
//Set the note property of project equal to an array of note objects
$project->setNotes($noteArray->findNotes($project->id));
I think there should be one more property in Note model that will reference to the Project model. Identificators of model MUST be an integer type
class Note extends BaseModel{
$id //string
$text//string
$project_id //int
}
So when you add a project, say it, with ID=5, You can add Notes with project_id = 5. And it will be one-to-many relatoionship.

Categories