Laravel polymorphic with custom foreign key - php

I have problem with custom shipmentable_type. My structure looks like this:
transfers:
id - integer
name - string
shipment:
id - integer
name - string
shipmentale:
shipment_id - integer
shipmentable_id - integer
shipmentabl_type - enum ( transfer, order, complaint, internet )
Now I have in my Transfer model realtion like this:
public function shipments()
{
return $this->morphToMany(Shipment::class, 'shipmentable');
}
The problem is, that to table shipmentable, to column shipmentable_type is going sth like this now: App/Models/Transfer, but I would like to force to be there 'transfer' in this case. Is it possible?

From the docs
By default, Laravel will use the fully qualified class name to store the type of the related model. However, you may wish to decouple your database from your application's internal structure. In that case, you may define a relationship "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([
'transfer' => 'App/Models/Transfer'
]);
You may register the morphMap in the boot function of your AppServiceProvider or create a separate service provider if you wish.

To set a value other than the model's fully qualified name in shipmentable_type, there is the Relation::morphMap() function.
Relation::morphMap([
'transfer' => 'App/Models/Transfer'
]);
This should be set in the AppServiceProvider or similar.

I've had this issue but unfortunately I had multiple models pointing at transfer with multiple locations. You can resolve them dynamically on your model:
class Transfers extends Model
{
/**
* Declare the class to get connect polymorphic relationships.
*
* #var string|null
*/
protected $morphClass = null;
/**
* Get the associated morph class.
*
* #return string|null
*/
public function getMorphClass()
{
return $this->morphClass ?: static::class;
}
public function shipments()
{
$this->morphClass = 'transfer';
return $this->morphToMany(Shipment::class, 'shipmentable');
}
// ...
}

Related

Can't set value for Non-relational column in pivot table with Laravel factory

hey everybody is it technically possible to set data in an extra column in the pivot table?
my database is like
products
sellers
id
id
title
name
brand_id
and...
prodduct_sellrs
id
product_id
seller_id
price
and I want to Seed my database with factory Faker data and here is my code in my seeder (I've already made the ProductFactory and.... )
Product::factory()
->foruser()
->hasCategories()
->hasTags()
->hassellers()
->forBrand()
->forAttributeSet()
->create();
but when I run it I get this error
General error: 1364 Field 'price' doesn't have a default value
which is truly right but anybody can help how I can define value for price in the pivot table?
You can use factory callbacks
Factory callbacks - Laravel
After create your Product, you can create data for your relational tabble
namespace Database\Factories;
use App\Models\Product;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class ProductFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Product::class;
/**
* Configure the model factory.
*
* #return $this
*/
public function configure()
{
return $this->afterCreating(function (Product $product) {
$product->sellers()->save(factory(App\ProductSeller::class)->make());
});
}
// ...
}
I realized my answer If anyone wanted to use
Product::factory()
->foruser()
->hasCategories()
->hasAttached(
Seller::factory()->count(10),
['price'=>423432432]
)
and it solved, here it is

Populating parameters with default value using relationships in Laravel 5.2

Okay, so I have a question. I'm programming a really complex report and the interface uses Laravel 5.2. Now the thing is that, depending on certain conditions, the user does not always need all parameters to be filled. However, for simplicity purposes, I made it so that the report always receives the complete set of parameters no matter what. So I have three tables:
tblReportParam
ID
ParamName
DefaultValue
tblReportParamValue
ParamID
ReportID
Value
tblReport
ID
UserName
Now, I have a solution that works, but for some reason, it just feels like I should be able to make better use of models and relationships. I basically have just my models and controllers and solved the whole thing using SQL.
It feels somewhat close to this but not quite. So basically, you need to always load/save all parameters. If parameter x is actually defined by the user then you use his definition otherwise you go with the default defined in tblReportParam. Anyone has any idea how to do this?
EDIT:
Okay, so I checked Eddy's answer and tried to work it in our system, but another colleague of mine started implementing a many-to-many relationship between the tblReport and the tblReportParam table with the tblReportParamValue acting as the pivot so I'm having some difficulty adapting this solution for our system. Here's the two models:
class ReportParam extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'tblReportParam';
protected $primaryKey = 'ID';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['ID', 'NomParam', 'DefaultValue'];
public function renourapports()
{
return $this->belongsToMany('App\Report');
}
}
class Report extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'tblReport';
protected $primaryKey = 'ID';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['ID', 'NoEmploye', 'NoClient', 'NoPolice', 'DateCreation', 'DateModification', 'runable', 'DernierEditeur'];
public $timestamps = false;
public function params()
{
return $this->belongsToMany('App\ReportParam ', 'tblReportParamValue', 'ReportID', 'ParamID')->withPivot('Valeur');
}
}
Now this actually is a pretty neat solution, but it only works if the parameter is actually in the pivot table (i.e. the relationship actually exists). What we want is that for the parameters that aren't in the pivot table, we simply want their default value. Can Eddy's solution work in this case?
Using Eloquent models
class ReportParam extends Model
{
public function paramValue() {
return $this->hasOne('App\ReportParamValue', 'ParamID');
}
public function getDefaultValueAttribute($value) {
if ( $this->paramValue ) return $this->paramValue->Value; //relationship exists
return $this->DefaultValue;
}
}
$reportParam->value; // return the relationship value or the default value;
UPDATE
Now that tblReportParamValue is a pivot table you should redefine your relationships. In ReportParam model add
public function reports() {
return $this->belongsToMany('App\Report', 'tblReportParamValue', 'ParamID', 'ReportID')->withPivot('Value');
}
And in Report model, defined the opposite
public function params() {
return $this->belongsToMany('App\ReportParam', 'tblReportParamValue', 'ReportID', 'ParamID')->withPivot('Value');
}
Now getting the default value from ReportParam becomes too complicated because it will one ReportParam has Many Reports. So doing $reportParam->reports() will bring back every single report that uses that paramID in the pivot table. Therefore looking for a value would mean going through all the reports. We could avoid that by changind the function definition.
public function getDefaultValue($reportID) {
$reportValue = $this->reports()->wherePivot('ReportID', $reportID)->first();
return $reportValue ? $this->reportValue->Value : $this->DefaultValue;
}
//In Controller
$report = Report::find(1);
$reportParam = ReportParam::find(1);
$reportParam->getDefaultValue($report->ID);
Ok I think this might work. If it doesnt, I am really sorry, I don't know any better.

Laravel - Eloquent - Dynamically defined relationship

Is it possible to set a model's relationship dynamically? For example, I have model Page, and I want to add relationship banners() to it without actually changing its file? So does something like this exist:
Page::createRelationship('banners', function(){
$this->hasMany('banners');
});
Or something similar? As they are fetched using the magic methods anyway, perhaps I can add the relationship dynamically?
Thanks!
I've added a package for this i-rocky/eloquent-dynamic-relation
In case anyone still looking for a solution , here is one. If you think it's a bad idea, let me know.
trait HasDynamicRelation
{
/**
* Store the relations
*
* #var array
*/
private static $dynamic_relations = [];
/**
* Add a new relation
*
* #param $name
* #param $closure
*/
public static function addDynamicRelation($name, $closure)
{
static::$dynamic_relations[$name] = $closure;
}
/**
* Determine if a relation exists in dynamic relationships list
*
* #param $name
*
* #return bool
*/
public static function hasDynamicRelation($name)
{
return array_key_exists($name, static::$dynamic_relations);
}
/**
* If the key exists in relations then
* return call to relation or else
* return the call to the parent
*
* #param $name
*
* #return mixed
*/
public function __get($name)
{
if (static::hasDynamicRelation($name)) {
// check the cache first
if ($this->relationLoaded($name)) {
return $this->relations[$name];
}
// load the relationship
return $this->getRelationshipFromMethod($name);
}
return parent::__get($name);
}
/**
* If the method exists in relations then
* return the relation or else
* return the call to the parent
*
* #param $name
* #param $arguments
*
* #return mixed
*/
public function __call($name, $arguments)
{
if (static::hasDynamicRelation($name)) {
return call_user_func(static::$dynamic_relations[$name], $this);
}
return parent::__call($name, $arguments);
}
}
Add this trait in your model as following
class MyModel extends Model {
use HasDynamicRelation;
}
Now you can use the following method to add new relationships
MyModel::addDynamicRelation('some_relation', function(MyModel $model) {
return $model->hasMany(SomeRelatedModel::class);
});
As of laravel 7, dynamic relationship is officially supported. You can use the Model::resolveRelationUsing() method.
https://laravel.com/docs/7.x/eloquent-relationships#dynamic-relationships
you can use macro call for your dynamic relation like this:
you should write this code in your service provider boot method.
\Illuminate\Database\Eloquent\Builder::macro('yourRelation', function () {
return $this->getModel()->belongsTo('class');
});
You have to have something in mind, an Eloquent relationship is a model of a relational database relatioship (i.e. MySQL).
So, I came with two approaches.
The good
If you want to achieve a full-featured Eloquent relationship with indexes and foreing keys in the database, you probably want to alter the SQL tables dynamically.
For example, supossing you have all your models created and don't want to create them dynamically, you only have to alter the Page table, add a new field called "banner_id", index it and reference to "banner_id" field on Banner table.
Then you have to write down and support for the RDBMS you will work with.
After that, you may want to include support for migrations. If it's the case, you may store in the database these table alterations for further rollbacks.
Now, for the Eloquent support part, you may look at Eloquent Model Class.
See that, for each kind of relation, you have a subyacent model (all can be found here, which is in fact what you are returning in relatioship methods:
public function hasMany($related, $foreignKey = null, $localKey = null)
{
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$localKey = $localKey ?: $this->getKeyName();
return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey, $localKey);
}
So you have to define a method in your model that accepts the type of relation and the model, creates a new HasMany (in case hasMany was the desired relationship) instance, and then returns it.
It's little bit complicated, and so you can use:
The easy
You can create a intermediate model (i.e. PageRelationship) that stores all the relationships between Page and other Models. A possible table schema could be:
+-------------+---------+------------------+-------------+
| relation_id | page_id | foreign_model_id | model_class |
+-------------+---------+------------------+-------------+
| 1 | 2 | 225 | Banner |
| 2 | 2 | 223 | Banner |
| 3 | 2 | 12 | Button |
+-------------+---------+------------------+-------------+
Then you can retrieve all dynamically relative models to a given Page. The problem here is that you don't actually have any real RDBMS relation between Models and Pages, so you may have to make multiple and heavy queries for loading related Models, and, what's worse, you have to manage yourself database consistency (i.e., deleting or updating the "225" Banner should also remove or update the row in page_relationship_table). Reverse relationships will be a headache too.
Conclusion
If the project is big, it depends on that, and you can't make a model that implements other models via inheritance or so, you should use the good approach. Otherwise, you should rethink you app design and then decide to choose or not second approach.
Just in case anyone is looking for a Laravel 8 answer:
Let's say I define my relationships in a single method of my model:
public function relationships()
{
return [
'user' => $this->belongsTo(User::class, 'user_id'),
];
}
Now, in my app service provider, I can use the resolveRelationUsing method. I've done this by iterating through the models folder and checking all models which contain the aforementioned method:
foreach ((new Filesystem)->allFiles(app_path('Models')) as $file) {
$namespace = 'App\\Models\\' . str_replace(['/', '.php'], ['\\', ''], $file->getRelativePathname());
$class = app($namespace);
if (method_exists($class, 'relationships')) {
foreach ($class->relationships() as $key => $relationship) {
$class->resolveRelationUsing($key, function () use ($class, $key) {
return $class->relationships()[$key];
});
}
}
}

how does laravel finds the connection between models and its table in database

in my model i have
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Page extends Model
{
//
}
in my controller i can say Page:all() and get all the rows from pages table
but i dont see any connection between Page model and pages table in the database
does it just guess table name based on the model name (lower case with extra s at the end ) or it's mentioned somewhere else ?
As you can see in the docs, this is the magic of Laravel :-)
https://laravel.com/docs/5.2/eloquent#defining-models (see Table Names)
If you want, you can set another name manually by user the following
protected $table = 'my_table_name';
And to go a bit further, this is how Laravel gets the table name in the base Model you can found at /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
/**
* Get the table associated with the model.
*
* #return string
*/
public function getTable()
{
if (isset($this->table)) {
return $this->table;
}
return str_replace('\\', '', Str::snake(Str::plural(class_basename($this))));
}
You can specify the table by putting the below code into your model
protected $table = 'your table name';
Otherwise, it takes a table name as a plural form of the model.
For example, if your model name Product then by default it connects the table named 'products'.
So if your table name is not different then no need to configure it. It's will connect automatically
By default, it takes the "snake case" of the class name used in the model. Also, add a final "s" to make the plural.
You can also define a custom name by adding the variable
protected $table = 'my_table_name';
in the model class.
For instance:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'my_flights';
}

Model store with different column than primaryKey

I have this Laravel/OctoberCMS Model to interface with my ExchangeModel.
It's a one sided relationship with my ExchangeModel.
Currently it stores in the Customer database table via ExchangeModel.id. But i'd like it to store as ExchangeModel.currencyISO, which is a 3 letter country code.
What do I need to setup in the relation to get laravel to store it via the other field I try to define via otherKey or key instead of the primaryKey(id)?
<?php namespace PhunTime\Client\Models;
use Model;
/**
* Customer Model
*/
class Customer extends Model {
/**
* #var string The database table used by the model.
*/
public $table = 'CUSTOMER';
public $primaryKey = 'customerNumber';
/**
* #var array Relations
*/
public $belongsTo = ['currency' => ['PhunTime\ExchangeRate\Models\ExchangeModel',
'key' => 'currencyISO',// EUR,USD,YEN
'otherKey' => 'currencyISO'
]];
}
Clarification of the structure:
- Customer
- id
- name
- currency(varchar(3)) <-- relation
- ExchangeModel
- id
- currencyISO
Currently the ExchangeModel.id gets stored in Customer.currency
I want ExchangeModel.currencyISO to be stored in Customer.currency
Customer.currency is already a varchar field suitable to accept it.
Currently if I specify type:relation in fields.yaml it stores it by ExchangeModel.id no matter what I specify how it should store it. use of otherKey, foreignKey, key, nothing helps changing octobers mind.
Currently i'm using a type: dropdown that i populate via getcurrencyOptions() but in my feeling this is a less than ideal situation.

Categories