Laravel 5 eloquent load model properties after create - php

When creating an eloquent model:
Model::create(['prop1' => 1, 'prop2' => 2]);
the returned model will only have prop1 & prop2 as properties, what can I do to eager load all others properties that I haven't inserted in database because they are optional ?
EDIT: Why do I need this ? to rename my database fields:
database
CREATE TABLE `tblCustomer` (
`pkCustomerID` INT(11) NOT NULL AUTO_INCREMENT,
`baccount` VARCHAR(400) NULL DEFAULT NULL,
`fldName` VARCHAR(400) NULL DEFAULT NULL,
`fldNumRue` VARCHAR(10) NULL DEFAULT NULL,
....
PRIMARY KEY (`pkCustomerID`)
);
customer model
<?php namespace App\Models;
/**
* Class Customer
* #package App\Models
* #property int code
* #property string name
* #property string addressno
*/
class Customer extends Model
{
protected $table = 'tblCustomer';
protected $primaryKey = 'pkCustomerID';
public $timestamps = false;
/**
* The model's attributes.
* This is needed as all `visible fields` are mutators, so on insert
* if a field is omitted, the mutator won't find it and raise an error.
* #var array
*/
protected $attributes = [
'baccount' => null,
'fldName' => null,
'fldNumRue' => null,
];
/**
* The accessors to append to the model's array form.
* #var array
*/
protected $appends = [
'id',
'code',
'name',
'addressno'
];
public function __construct(array $attributes = [])
{
// show ONLY mutators
$this->setVisible($this->appends);
parent::__construct($attributes);
}
public function setAddressnoAttribute($value)
{
$this->attributes['fldNumRue'] = $value;
return $this;
}
public function getAddressnoAttribute()
{
return $this->attributes['fldNumRue'];
}
}
The problem is that when Laravel converts everything to JSON, he will parse all my mutators:
public function getAddressnoAttribute()
{
return $this->attributes['fldNumRue'];
}
and raise an error as $this->attributes['fldNumRue'] is not defined ErrorException: Undefined index... So I need a way to initialize all attributes with their default values.

You can call fresh() method on your model. It will reload the model from the database and return it. Keep in mind that it returns a reloaded object - it doesn't update existing one. You can also pass an array of relations that should be reloaded:
$model = $model->fresh($relations);
You could consider removing default values from the database and in your model. This way you won't need to reload model to get the defaults.
You can do it by overriding $attributes property in your model and seting defaults there:
class MyModel extends Model {
protected $attributes = [
'key' => 'default value'
];
}

Related

How to save entities across two databases in Laravel (Backpack)

I am new with Laravel and of course also with backpack.
I am using laravel 7.x and backpack 4.1 with both MySql and MongoDb.
The situation I am facing is that I have a company model with some attributes which are in MySql (both save and update working great with for the attributes stored in MySql) and other attributes that should be stored in MongoDb.
I have a CompanyPropertyCollection model for the attributes which I want to be stored in MongoDb
All these company will have a variable number of other arbitrary properties, which I want to save in mongo.
These properties may be simple scalar values or more complex values too (think arrays of objects), hence the idea to save them in mongo.
MySql Company table:
My question is the following:
What is the best practice to save attributes of an entity in two distinct databases from BackPack? I override the CreateOperation, UpdateOperation with the store() and update() functions something like this:
Company model:
class Company extends Model
{
use \Backpack\CRUD\app\Models\Traits\CrudTrait;
use SoftDeletes;
use HybridRelations;
protected $connection = 'mysql';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name',
'company_type',
'is_active',
'package_id',
'certification_id',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'id' => 'integer',
'is_active' => 'boolean',
'package_id' => 'integer',
'certification_id' => 'integer',
];
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function users()
{
return $this->belongsToMany(\App\Models\User::class);
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function package()
{
return $this->belongsTo(\App\Models\Package::class);
}
public function certification()
{
return $this->hasMany(\App\Models\Certification::class);
}
public function properties()
{
return $this->hasOne(\App\Models\CompanyPropertyCollection::class);
}
}
CompanyPropertyCollection model:
class CompanyPropertyCollection extends Model
{
use SoftDeletes;
protected $connection = 'mongodb';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
//'company_id',
'email',
'big_news_id',
'phone.number',
'phone.country_prefix',
'phone.area_prefix',
'phone.postfix',
'year_of_foundation',
'nr_of_employees',
'nr_of_branches',
'company_size',
'subtitle',
'homepage',
'country_code',
'city',
'street',
'post_code',
'uid_nr',
'registration_nr',
'total_sales_area',
'total_annual_bisuness_volume',
'short_portrait',
'long_portrait',
'embedded_video',
'certificates',
'gallery',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'id' => 'integer',
'gallery' => 'array',
];
public function company()
{
return $this->belongsTo(\App\Models\Company::class);
}
}
CompanyCrudController :
public function update()
{
//$this->update( $company->properties);
$response = $this->traitUpdate();
// do something after save
//use registered observer
return $response;
}
Currently I am trying to use a CompanyObserver and on saving to store the data for mongo from the Request.
class CompanyObserver {
public function saving(Company $company)
{
//dd(request()->request);
$request = request()->request;
dd($company->properties());
//save to MongoDb
dd('saving methond on the observer');
}
}
If you need to perform some action after saving a model, like saving some user data in another db with a different type. You can override the model's save method.
Inside your model, add a method like the below
public function save(array $options = array())
{
if (parent::save($options)) {
// Model has been saved in mysql, now save in mongoDB
}
}

Laravel Lighthouse GraphQL renamed relationship mutation

I'm having an issue updating a renamed relationship with a graphQL query.
Here's the related schema and Laravel models:
Laravel Models
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Lead extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
// protected $fillable = [
// 'lead_type_id',
// ];
protected $guarded = [];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
//
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
// 'created_at' => 'timestamp',
// 'updated_at' => 'timestamp'
];
/**
* Get the LeadActions for the Lead.
*/
public function leadActions()
{
return $this->hasMany(\App\Models\LeadAction::class);
}
/**
* Get the clients for the Lead.
*/
public function clients(): BelongsToMany
{
return $this->belongsToMany(Client::class);
}
/**
* Get the LeadType for the Lead.
*/
public function leadType(): BelongsTo
{
return $this->belongsTo(\App\Models\LeadType::class, 'lead_type_id');
}
}
?>
<?php
namespace App\Models;
use App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class LeadType extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
//
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'name' => 'string',
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
/**
* Get the Leads for the LeadType.
*/
public function leads(): HasMany
{
return $this->hasMany(Models\Lead::class);
}
}
?>
GraphQl Schema
type Lead {
id: ID!
lead_type: LeadType #belongsTo(relation: "leadType")
clients: [Client!]! #belongsToMany
created_at: DateTime!
updated_at: DateTime!
}
input UpdateLeadInput {
id: ID!
clients: UpdateClientsRelation
lead_type: UpdateLeadTypeRelation
}
input UpdateLeadTypeRelation {
create: CreateLeadTypeInput
connect: ID
update: UpdateLeadTypeInput
upsert: UpsertLeadTypeInput
delete: ID
disconnect: ID
}
Using the following graphQl query I get an SQL error for missing column lead_type:
Query
mutation UpdateLead {
updateLead(input: {id: 1, lead_type: {connect: 1}}) {
id
clients {
id
name
}
lead_type {
id
name
}
}
}
SQL Error
Column not found: 1054 Unknown column 'lead_type' in 'field list' (SQL: update `leads` set `lead_type` = {\"connect\":\"1\"}, `leads`.`updated_at` = 2020-01-14 17:11:17 where `id` = 1
I've followed the Laravel naming convention, and named the column lead_type_id on the leads table. If I remove the renaming of the lead_type relationship to leadType I can successfully run an update mutation, but I can't figure out how to get it to use the correct name for the column (lead_type_id) whilst keeping the relationship renamed.
Any help is greatly appreciated.
Many Thanks
Have you tried #rename directive? I mean you have to use it on lead_type in your UpdateLeadInput, because lighthouse looks for relation named lead_type, and as this is not defined, it assume that lead_type is an argument.
Either rename your relations in models, like:
class Lead extends Model
{
public function lead_actions()
{
return $this->hasMany(\App\Models\LeadAction::class);
}
public function lead_type(): BelongsTo
{
return $this->belongsTo(\App\Models\LeadType::class, 'lead_type_id');
}
}
OR
use #rename directive (I didn't try it, but I mean it works like this):
input UpdateLeadInput {
id: ID!
clients: UpdateClientsRelation
lead_type: UpdateLeadTypeRelation #rename(attribute: "leadType")
}

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'projects_id' in 'field list'

I am trying to submit some data to database. However when inserting the $airattributes im getting an error that projects_id (WHICH DOESNT EXIST ANYWHERE, NEITHER IN DB NOR ANY FILE)
MY CONTROLLER:
public function newProject(Request $request)
{
$data = $request->all();
$attributes = [];
$attributes['title'] = $data['title'];
$attributes['start_date'] = date("Y-m-d h:i:s", strtotime($data['start_date']));
$attributes['end_date'] = date("Y-m-d h:i:s", strtotime($data['end_date']));
$attributes['created_by'] = Auth::user()->id;
$attributes['description'] = $data['description'];
$attributes['air'] = '10';
$attributes['water'] = '19';
$attributes['lat'] = $data['lat'];
$attributes['lng'] = $data['lng'];
$airattributes['dust'] = $data['dust'];
$airattributes['noise'] = $data['noise'];
$airattributes['temperature'] = $data['temperature'];
$airattributes['radiation'] = $data['radiation'];
// var_dump($attributes);
// return;
$project = Projects::create($attributes);
$air = $project->air()->create($airattributes);
var_dump($data);
return;
MY projects model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Projects extends Model
{
protected $table = 'projects';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'title', 'start_date', 'end_date', 'created_by', 'description', 'air', 'water', 'lat', 'lng'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'id',
];
/**
* Get the phone record associated with the user.
*/
public function air()
{
return $this->hasMany('App\Air');
}
/**
* Get the phone record associated with the user.
*/
public function water()
{
return $this->hasOne('App\Water');
}
public function user()
{
return $this->hasOne('\App\User', 'id', 'created_by');
}
public function enrolls()
{
return $this->hasMany('\App\Enroll', 'project_id', 'id');
}
public function lastEdited()
{
return $this->hasOne('\App\User', 'id', 'last_edited_by');
}
}
My Air Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Air extends Model
{
protected $table = 'projects_air';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'project_id', 'temperature', 'radiation', 'dust', 'noise'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'id',
];
}
you can test it here for more info: http://188.166.166.143/projects/add
In the projects model if you don't specify the foreign key, then I believe in the Air model you should change to 'projects_id' in accordance to the name of the table.
Projects hasMany relationships with Air:
public function air()
{
return $this->hasMany('App\Air');
}
This, by default assumes that air model have foreign key projects_id, hence the error.
Since, you have project_id foreign key,
return $this->hasMany('App\Air', 'project_id');
will do.
Simply: Changing model name Projects to Project will solve the problem. Also model names are always singular.
You can solve this in two way. One you can change you model name to Project or you can specify you foreign key in your air function. So you can Change you air function to below.
public function air()
{
return $this->hasMany('App\Air', 'project_id');
}
Choose whichever you want. But in Laravel Model name is always singular so I will recommend you to follow the first rule in that way you don't need to specify the foreign key Laravel is smart enough to recognize that automatically.

Laravel One to many relation with custom primary key not working

I am trying to implement one to many relation in laravel
My tables have custom primary key name not id
I have set $primartKey attribute as well but the relations doesnot seem to work.
activities
|act_ID|act_acc_ID|.........
categories
|acc_ID|.......
Here are my Models
class Adventure extends \Eloquent {
/**
* #var string $table the name of the table
*/
protected $table = 'activities';
/**
* #var string $primaryKey the primary key of the table
*/
protected $primaryKey = 'act_ID';
/**
* #var bool timestamps updated_at and created_at columns flag
*/
public $timestamps = false;
/**
*
*/
public function category(){
$this->belongsTo('Category','act_acc_ID');
}
}
class Category extends \Eloquent {
/**
* #var string $table the name of the table
*/
protected $table = 'categories';
/**
* #var string $primaryKey the primary key of the table
*/
protected $primaryKey = 'acc_ID';
/**
* #var bool timestamps updated_at and created_at columns flag
*/
public $timestamps = false;
public function adventures(){
$this->hasMany('Adventure','act_acc_ID');
}
}
Now when ever i try to access categories from adventure or adventures from categories i get
Relationship method must return an object of type
Illuminate\Database\Eloquent\Relations\Relation
What am i doing wrong here ??
There are plenty adventures whose category is 15 so i try
I try Categories::find(15)->adventures also tried Categories::find(15)->adventures()
You didn't use return keyword, it should be something like this:
public function category(){
return $this->belongsTo('Category','act_acc_ID');
}
public function adventures(){
return $this->hasMany('Adventure','act_acc_ID');
}
Add the return keyword in both relationship methods.
you have to set this in you Category model
public $incrementing = false;

Laravel hasManyThrough relationship with non-default local key

My schema is as follows:
Clients (hasMany Accounts)
id
name
Accounts (hasMany Holdings, belongsTo Clients)
id (int)
account_id (string, unique key)
name
Holdings (belongsTo Accounts)
id
account_id (string)
value
holding_date... etc
So, Client hasMany Accounts hasMany Holdings. The caveat being that the local key for accounts is account_id, not just id as is expected. This is because there is a requirement for the accounts to have a string identifier. In the holdings table the foreign key is also account_id.
I have defined my relationships like so:
// Client.php
public function accounts()
{
return $this->hasMany('Account');
}
// Account.php
public function client()
{
return $this->belongsTo('Client');
}
public function holdings()
{
return $this->hasMany('Holding');
}
// Holding.php
public function account()
{
return $this->belongsTo('Account', 'account_id', 'account_id');
}
If I wanted to query all the holdings for a given client ID how would I do this? If I do something like
Client::find($id)->accounts->holdings;
I get this error:
Undefined property: Illuminate\Database\Eloquent\Relations\HasMany::$holdings
I also tried using the hasManyThrough relationship (having added the relationship to my model) but there seems to only be a way to define the foreign key, not the local key for the accounts. Any suggestions?
Assuming you have client_id on accounts table,
do this:
// Account model
public function holdings()
{
return $this->hasMany('Holding', 'account_id', 'account_id');
}
// then
$client = Client::with('accounts.holdings')->find($id);
$client->accounts // collection
->first() // or process the collecction in the loop
->holdings; // holdlings collection
HasManyThrough will work only if Account model has (or will have for that purpose) $primaryKey set to account_id instead of default id
Since account_id is not primary key of the Account model, you can't use hasManyThrough. So I suggest you do this:
$accountIds = $client->accounts()->lists('account_id');
// if it was many-to-many you would need select clause as well:
// $accountIds = $client->accounts()->select('accounts.account_id')->lists('account_id');
$holdings = Holding::whereIn('account_id', $accountIds)->get();
This way you get the Collection just like you wanted, donwside is 1 more query needed in comparison to eager loading.
You need to change your relation in Account model
// Account.php
public function client()
{
return $this->belongsTo('Client','account_id');
}
But, it more appropriate to change column name to client_id in Accounts table
I think you can use load method to get the corresponding resulting query for each accounts. Something like:
Client::find($id)->load('accounts.holdings');
This means that client_id is present in accounts and holdings has account_id as well.
PS: I am not super sure how this would work in this context. But I hope this can lead you to find the way to do it.
You'd have to override Eloquent a little bit. I just ran into something very similar with a BelongsToMany relationship. I was trying to perform a many-to-many query where the relevant local-key was not the primary key. So I extended Eloquent's BelongsToMany a little bit. Start by building an override class for the BelongsToMany relationship class:
namespace App\Overrides\Relations;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany as BaseBelongsToMany;
class BelongsToMany extends BaseBelongsToMany
{
protected $localKey;
/**
* #var array
*/
protected $customConstraints = [];
/**
* BelongsToMany constructor.
* #param Builder $query
* #param Model $parent
* #param string $table
* #param string $foreignKey
* #param string $otherKey
* #param string $relationName
* #param string $localKey
*/
public function __construct(
Builder $query,
Model $parent,
$table,
$foreignKey,
$otherKey,
$relationName = null,
$localKey = null
) {
//The local-key binding, assumed by Eloquent to be the primary key of the model, will have already been set
if ($localKey) { //If it's intended to be overridden, that value in the Query/Builder object needs updating
$this->localKey = $localKey;
$this->setLocalKey($query, $parent, $table, $foreignKey, $localKey);
}
parent::__construct($query, $parent, $table, $foreignKey, $otherKey, $relationName);
}
/**
* If a custom local-key field is defined, don't automatically assume the pivot table's foreign relationship is
* joined to the model's primary key. This method is necessary for lazy-loading.
*
* #param Builder $query
* #param Model $parent
* #param string $table
* #param string $foreignKey
* #param string $localKey
*/
public function setLocalKey(Builder $query, Model $parent, $table, $foreignKey, $localKey)
{
$qualifiedForeignKey = "$table.$foreignKey";
$bindingIndex = null;
//Search for the 'where' value currently linking the pivot table's foreign key to the model's primary key value
$query->getQuery()->wheres = collect($query->getQuery()->wheres)->map(function ($where, $index) use (
$qualifiedForeignKey,
$parent,
&$bindingIndex
) {
//Update the key value, and note the index so the corresponding binding can also be updated
if (array_get($where, 'column', '') == $qualifiedForeignKey) {
$where['value'] = $this->getKey($parent);
$bindingIndex = $index;
}
return $where;
})->toArray();
//If a binding index was discovered, updated it to reflect the value of the custom-defined local key
if (!is_null($bindingIndex)) {
$bindgings = $query->getQuery()->getBindings();
$bindgings[$bindingIndex] = $this->getKey($parent);
$query->getQuery()->setBindings($bindgings);
}
}
/**
* Get all of the primary keys for an array of models.
* Overridden so that the call to $value->getKey() is replaced with $this->getKey()
*
* #param array $models
* #param string $key
* #return array
*/
protected function getKeys(array $models, $key = null)
{
if ($key) {
return parent::getKeys($models, $key);
}
return array_unique(array_values(array_map(function ($value) use ($key) {
return $this->getKey($value);
}, $models)));
}
/**
* If a custom local-key field is defined, don't automatically assume the pivot table's foreign relationship is
* joined to the model's primary key. This method is necessary for eager-loading.
*
* #param Model $model
* #return mixed
*/
protected function getKey(Model $model)
{
return $this->localKey ? $model->getAttribute($this->localKey) : $model->getKey();
}
/**
* Set the where clause for the relation query.
* Overridden so that the call to $this->parent->getKey() is replaced with $this->getKey()
* This method is not necessary if this class is accessed through the typical flow of a Model::belongsToMany() call.
* It is necessary if it's instantiated directly.
*
* #return $this
*/
protected function setWhere()
{
$foreign = $this->getForeignKey();
$this->query->where($foreign, '=', $this->getKey($this->parent));
return $this;
}
}
Next, you'll need to make the Model class actually use it:
namespace App\Overrides\Traits;
use App\Overrides\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Relation;
/**
* Intended for use inside classes that extend Illuminate\Database\Eloquent\Model
*
* Class RelationConditions
* #package App\Overrides\Traits
*/
trait CustomConstraints
{
/**
* Intercept the Eloquent Model method and return a custom relation object instead
*
* {#inheritdoc}
*/
public function belongsToMany($related, $table = null, $foreignKey = null, $otherKey = null, $relation = null, $localKey = null)
{
//Avoid having to reproduce parent logic here by asking the returned object for its original parameter values
$base = parent::belongsToMany($related, $table, $foreignKey, $otherKey, $relation);
//The base action will have already applied the appropriate constraints, so don't re-add them here
return Relation::noConstraints(function () use ($base, $localKey) {
//These methods do the same thing, but got renamed
$foreignKeyName = version_compare(app()->version(), '5.3', '>=')
? $base->getQualifiedForeignKeyName()
: $base->getForeignKey();
$relatedKeyName = version_compare(app()->version(), '5.3', '>=')
? $base->getQualifiedRelatedKeyName()
: $base->getOtherKey();
return new BelongsToMany(
$base->getQuery(),
$base->getParent(),
$base->getTable(),
last(explode('.', $foreignKeyName)),
last(explode('.', $relatedKeyName)),
$base->getRelationName(),
$localKey
);
});
}
}
Use this trait inside your model class, and you now have the ability to add a 6th argument that specifies what local-key to use, rather than automatically assume the primary one.

Categories