Polymorphic Eloquent relationships with namespaces - laravel

I've tried to implement polymorphic relationships. They work perfectly... However, I'm trying to reduce my database size as much as possible so... I've this
Table action
| id | realatable_type | relatable_id
| 1 | Lion\People | 65
| 2 | Lion\Company | 13
Obviously I've this
<?php namespace Lion;
class Company extends \Eloquent { ... }
class People extends \Eloquent { ... }
Is there any way to store only "People" or "Company" assuming that the namespace is always going to be "Lion"?

Since Laravel 4.1, inside your model (in this case Company and People) you can set the protected property $morphClass to whatever you want.
<?php namespace Lion;
class Company extends \Eloquent {
protected $morphClass = 'Company';
}
Now in your table you can store the type without the namespace:
| id | realatable_type | relatable_id
| 2 | Company | 13

I believe the best solution here (for database size at least) would be to simply change readable_type to ENUM('Lion\Company', 'Lion\People').
That being said, if you really want to handle this on Laravel side, you'll have to create new classes extending from Illuminate\Database\Eloquent\Relations\Morph* ¹ and overwrite their constructors ² as to get only the last value after a dash, on $morphClass. Something like this:
<?php
use \Illuminate\Database\Eloquent\Model;
use \Illuminate\Database\Eloquent\Builder;
class MyMorphOne extends \Illuminate\Database\Eloquent\Relations\MorphOne {
public function __construct(Builder $query, Model $parent, $type, $id) {
parent::__construct($query, $parent, $type, $id);
$this->morphClass = substr($this->morphClass, strrpos($this->morphClass, '\\') + 1);
}
}
Then, extend your own model or base model, to overwrite morphOne, morphMany and morphToMany methods as to make use of your new extended classes. Something like this:
<?php
class People extends Eloquent {
// ...
public function morphOne($related, $name, $type = null, $id = null) {
$instance = new $related;
list($type, $id) = $this->getMorphs($name, $type, $id);
$table = $instance->getTable();
return new MyMorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id);
}
}
* = One, Many and ToMany
Which are actually inherited from MorphOneOrMany on MorphOne and MorphMany.

Related

Eloquent's fillable not working with mutators

I have a following model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
class PaymentOption extends Model
{
protected $table = 'payment_option';
public $timestamps = false;
protected $fillable = ['payment_option_code', 'payment_option_name'];
public function setCodeAttribute($value)
{
$this->attributes['payment_option_code'] = $value;
}
public function getCodeAttribute()
{
return $this->payment_option_code;
}
public function setNameAttribute($value)
{
$this->attributes['payment_option_name'] = $value;
}
public function getNameAttribute($value)
{
$this->payment_option_name;
}
}
As you can see, I have weird column names. I need mutators cause I will be accepting variables without the prefix payment_option.
Now, I do it in my controller like so:
<?php
namespace App\Http\Controllers;
use App\PaymentOption;
use App\Http\Requests\PaymentOptionRequest;
class PaymentOptionController extends Controller
{
private $paymentOption;
public function __construct(PaymentOption $paymentOption)
{
$this->paymentOption = $paymentOption;
}
public function create(PaymentOptionRequest $request)
{
$paymentOption = $this->paymentOption->fill($request->validated());
dump($paymentOption);
return response()->json([]);
}
}
When I tried to use the fill() it doesn't call the mutators. But when I tried to set it 1 by 1 it worked like so:
$paymentOption->code = $validated['code'];
Why is that so?
Thanks!
fill does loop on the input data that are in your $fillable array
public function fill(array $attributes)
{
$totallyGuarded = $this->totallyGuarded();
foreach ($this->fillableFromArray($attributes) as $key => $value) {
$key = $this->removeTableFromKey($key);
// The developers may choose to place some attributes in the "fillable" array
// which means only those attributes may be set through mass assignment to
// the model, and all others will just get ignored for security reasons.
if ($this->isFillable($key)) {
$this->setAttribute($key, $value);
} elseif ($totallyGuarded) {
throw new MassAssignmentException(sprintf(
'Add [%s] to fillable property to allow mass assignment on [%s].',
$key, get_class($this)
));
}
}
return $this;
}
hence, you need to add 'code' and 'name' to the fillable array to have them assigned by fill
With latest realese of laravel 8 there is a change in implementation of fill. In laravel older version fill method does work for mutator and database column both. But in laravel 8 implementation of fill method is changed now fill also check if the key given in an array to fill method is a database column or not.
For example if you have database column name user_id and mutator name is user it will not work with laravel 8. It does work for the older version of laravel.
If the $guarded array contains at least one column/key, then that Eloquent Model goes into a strict mode where it can only be filled with values for valid database columns. here is the test is written in the official codebase for that(link to test).
To solve the problem use forceFill. That will solve your problem.

Eloquent one to many relationship not working

I'm stuck for hours with one of those issues where a fresh set of eyes might help. I just can't understand what's missing.
I'm connecting a model called User_ativo and defining two one-to-many relations to models Instituicao and Tipo_Ativo.
My database is simple.
Table user_ativo has columns "tipo_ativo_id" and "instituicao_id". I have a test row where both are set to 1. Both my tables instituicoes and tipo_ativos have only "id" and a string field "nome" (name). Both have a record with id == 1.
User_ativo.php:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User_ativo extends Model
{
public function tipo(){
return $this->belongsTo('App\Tipo_ativo');
}
public function instituicao(){
return $this->belongsTo('App\Instituicao');
}
}
Instituicao.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Instituicao extends Model
{
protected $table = 'instituicoes';
public function user_ativos(){
return $this->hasMany('App\User_ativo');
}
}
Tipo_ativo.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tipo_ativo extends Model
{
protected $table = 'tipo_ativos';
public function user_ativos(){
return $this->hasMany('App\User_ativo');
}
}
My controller method that fetches the date goes as follow:
public function index()
{
$ativos = User_ativo::with('tipo', 'instituicao')->get();
return view('ativos.index', compact('ativos'));
}
Now here's where it gets interesting, for some reason I can't figure out, when I echo the $ativos variable in my view I get this:
[{"id":1,"user_id":1,"instituicao_id":1,"tipo_ativo_id":1,"tipo":null,"instituicao":{"id":1,"nome":"Banco do Brasil"}}]
So, weirdly my relationship with the Instituicao model works, but the one with Tipo_ativo returns null.
I'm pretty confident someone will point out some dumb and obvious mistake in all of this, but I can't for the life of me understand why one works and the other doesn't since they're pretty much the same thing.
Your relationships names are not according to laravel convention.
Read below function and provide foreign_key and local_key/owner_key to your relationships then it will work
public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null){}
If we do not follow laravel convention while creating relationships then we have to tell it that these are the foreign and local keys that should be used.
Read more here
class User_ativo extends Model{
public function tipo(){
return $this->belongsTo('App\Tipo_ativo','user_ativo_id'); //second parameter is foreign_key_of_User_avito_table_here
}
public function instituicao(){
return $this->belongsTo('App\Instituicao','user_ativo_id'); //second parameter is foreign_key_of_User_avito_table_here
}
}
class Instituicao extends Model
{
protected $table = 'instituicoes';
public function user_ativos(){
return $this->hasMany('App\User_ativo','instituicao_id'); //second parameter is foreign key of Instituicao model
}
}
class Tipo_ativo extends Model
{
protected $table = 'tipo_ativos';
public function user_ativos(){
return $this->hasMany('App\User_ativo','tipo_ativo_id'); //second parameter is foreign key of Tipo_ativo model.
}
}

How to translate pivot values with laravel-translatable?

I just started using laravel-translatable package from dimsav which enables to set and retrieve translated strings from the database using Eloquent models. It works great but I still have trouble translating pivot values from belongsToMany relationships.
I have a model called Combination defined as shown below:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Combination extends Model {
public function product() {
return $this->belongsTo('App\Product');
}
public function product_attributes() {
return $this->belongsToMany('App\Attribute')->withPivot('value');
}
}
Here is my Attribute model:
namespace App;
use Illuminate\Database\Eloquent\Model;
use Dimsav\Translatable\Translatable;
class Attribute extends Model {
use Translatable;
public $translatedAttributes = ['name'];
}
class AttributeTranslation extends Model {
public $timestamps = false;
protected $fillable = ['name'];
}
For instance, I have a Product called "T-Shirt" with two Attributes:
Color
Size
This Product has two combinations:
Black / M
Black / L
These values are stored as pivot values in an intermediate table called attribute_combination like this:
+--------------+----------------+-------+
| attribute_id | combination_id | value |
|--------------+----------------+-------|
| 1 | 1 | Black |
| 2 | 1 | M |
| 1 | 2 | Black |
| 2 | 2 | L |
+--------------+----------------+-------+
I have no problem retrieving the translated attribute's name using $attribute->name but I can't find a way to retrieve the translated pivot values. I tried to create a table called attribute_combination_translations and its AttributeCombination model as shown below but it does not work.
namespace App;
use Illuminate\Database\Eloquent\Model;
use Dimsav\Translatable\Translatable;
class AttributeCombination extends Model {
use Translatable;
protected $table = 'attribute_combination';
public $translatedAttributes = ['value'];
}
class AttributeCombinationTranslation extends Model {
public $timestamps = false;
protected $fillable = ['value'];
}
Maybe anyone using this package could help me get through this?

Laravel 5 returns string primary key as integer

I have a settings table in my database that looks like this:
| name | value | validation |
| site-name | Sample Site | max:255 |
| site-title | Sample Site | max:255 |
This is how my Setting model looks like:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model
{
protected $primaryKey = 'name';
protected $fillable = ['name', 'value'];
}
and the Controller.php file used for sharing the settings variable across all views:
<?php
namespace App\Http\Controllers;
use App\Setting;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesResources;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
class Controller extends BaseController
{
use AuthorizesRequests, AuthorizesResources, DispatchesJobs, ValidatesRequests;
protected $usr;
protected $settings;
public function __construct()
{
$this->usr = Auth::user();
view()->share('usr', $this->usr);
$this->settings = array();
$allSettings = Setting::all();
foreach ($allSettings as $setting)
$this->settings[$setting->name] = $setting->value;
print_r($this->settings);
view()->share('settings', $this->settings);
}
}
the print_r is used for debugging.
For some reason, the print_r outputs:
Array ( [0] => Sample Site )
instead of:
Array ( [site_name] => Sample Site )
It looks like $setting->name returns an integer instead of a string (the name column on the database is set as varchar).
Any ideas why it happens?
if its not integer use in model
public $incrementing = false;
Ok, I got this.
All I needed to do is to add this line to my Setting model:
public $incrementing = false;

Laravel 5.2 eloquent returns soft deleted records

I am using Laravel 5.2.
I have a model as below:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
class ZoomMeeting extends BaseModel {
public $timestamps=true;
protected $table = 'zoom_meetings';
use SoftDeletes;
protected $dates = ['deleted_at'];
protected $fillable = ['user_id', 'uuid', 'meeting_id', 'host_id', 'topic', 'status', 'type', 'start_url', 'join_url', 'created_at'];
public function users() {
return $this->belongsTo('App\Models\User');
}
}
And the base model is as below:
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Auth;
use Carbon\Carbon;
class BaseModel extends Model {
public $timestamps = false;
protected static function boot()
{
//parent::boot();
static::creating(function($model) {
if(empty($model->created_at))
{
$model->created_at = date('Y-m-d H:i:s');
}
return true;
});
static::updating(function($model) {
$model->updated_at = date('Y-m-d H:i:s');
return true;
});
}
}
I am using softdeletetrait in ZoomMeeting model, and soft deleting is working fine.
However, if I fetch records from the same model using eloquent, it returns the soft deleted records too. I am using code below to get the records:
$record = ZoomMeeting::where("user_id", $user_id)->where("meeting_id", $meeting_id)->orderBy("id", "DESC")->first();
The eloquent is building the query as:
select * from `zoom_meetings` where `user_id` = 3 and `meeting_id` = 707070707 order by `id` desc limit 1
See, there is no deleted at is null set in where statement. It is not preventing the deleted records.
I am not sure where am I making mistake?
It looks like you are overriding the boot method, but you aren't ever actually calling the parent boot method (it's commented out), so the trait is never getting initialized correctly. I believe that also means the data you have been deleting is actually being deleted from the database.
Is there a reason you need to override the boot method? What you are adding is already done handled by the framework, so it doesn't appear to be necessary.

Resources