Laravel Lighthouse GraphQL renamed relationship mutation - php

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")
}

Related

Laravel Many-To-Many - Different databases

need some light on a problem... I'm trying to get data from another database using a Many-To-Many relation.
Basically, a site can have many templates and a template can have many sites.
Site Model:
class Site extends Model
{
use HasFactory;
/**
* Database Connection Name
*/
protected $connection = 'hub';
/**
* Model Table Name
*/
protected $table = 'tbl_sites';
/**
* Model Primary Key
*/
protected $primaryKey = 'id';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'code', 'name', 'abbreviation', 'address', 'zipcode', 'town', 'geolocation_id', 'gps'
];
/**
* Returns associated SGC templates
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function sgc_templates()
{
return $this->belongsToMany('App\Models\SGC\Contracts\Templates\Template', 'sgc_contracts_templates_hasmany_sites', 'site_id', 'template_id');
}
}
Template Model:
class Template extends Model
{
use HasFactory;
/**
* Database Connection Name
*/
protected $connection = 'sgc';
/**
* Model Table Name
*/
protected $table = 'sgc_contracts_templates';
/**
* Model Primary Key
*/
protected $primaryKey = 'id';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'description', 'file_name'
];
/**
* Returns associated sites
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function sites()
{
return $this->belongsToMany('App\Models\Hub\Sites\Site', 'sgc_contracts_templates_hasmany_sites', 'template_id', 'site_id');
}
}
If I try to get templates associated to a site with: Site::with('sgc_templates')->find(1), everything works fine.
If I try to get sites associated to a template with: Template::with('sites')->find(1), I got error. Basically saying that the pivot table doesn't exists on sites database. The templates and the pivot table are on sgc connection/database.
The error is:
Illuminate\Database\QueryException
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'hub.sgc_contracts_templates_hasmany_sites' doesn't exist (SQL: select `tbl_sites`.*, `sgc_contracts_templates_hasmany_sites`.`template_id` as `pivot_template_id`, `sgc_contracts_templates_hasmany_sites`.`site_id` as `pivot_site_id` from `tbl_sites` inner join `sgc_contracts_templates_hasmany_sites` on `tbl_sites`.`id` = `sgc_contracts_templates_hasmany_sites`.`site_id` where `sgc_contracts_templates_hasmany_sites`.`template_id` in (1))
Clearlly that the Template::with('sites')->find(1) is going to the wrong database, because on the error, 'hub.sgc_contracts_templates_hasmany_sites' should be 'sgc.sgc_contracts_templates_hasmany_sites'.
Can someone help me with this? :|
Thanks
Found an workaround. Seems that Many-To-Many only works in 1 direction (?).
Github Issue
Workaround
Thanks for all the help.
You need to tell Eloquent that you want to use other db, try something like this
return $this->belongsToMany('App\Models\Hub\Sites\Site', 'sgc.sgc_contracts_templates_hasmany_sites', 'template_id', 'site_id');
and then check if it tries to query sgc db.
If it still don't help try this https://stackoverflow.com/a/60060726/7892040.

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 5.6 API resource collection - Conditional relation not fetched

I'm experiencing my first Laravel project and I implemented a resource collection API, where I fetch data via passport. Data seems to be retrieved correctly from model, except for relations. Here's the situation:
item.php (Model)
<?php
// Definizione Namespace
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* Classe Item
*/
class Item extends Model
{
use SoftDeletes;
// Dichiarazione Proprietà
protected $table = 'item';
protected $dateformat = 'Y-m-d';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'data_acquisto',
'labeled',
'estensione_garanzia',
'stato',
'data_dismissione',
'note'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'codice',
'serial',
'componente_id',
'tipologia_id',
'condizione_id',
'locazione_id',
'fornitore_id',
'parent_id'
];
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = [
'data_acquisto',
'data_dismissione',
'deleted_at'
];
/**
* All of the relationships to be touched.
*
* #var array
*/
protected $touches = [
'componenti',
'condizioni',
'fornitori',
'locazioni',
'tipologie'
];
/**
* Scope query item figli
* Getter
* #param array $query Query
* #return array Query
*/
public function scopeFigli($query)
{
return $query->where('parent_id', '!=', null);
}
/**
* Componenti Correlati
* Getter
* #return object Componenti
*/
public function componenti()
{
// Definizione relazione
return $this->belongsTo('App\Componente');
}
/**
* Condizioni Correlate
* Getter
* #return object Condizioni
*/
public function condizioni()
{
// Definizione relazione
return $this->belongsTo('App\Condizione');
}
/**
* Fornitori Correlati
* Getter
* #return object Fornitori
*/
public function fornitori()
{
// Definizione relazione
return $this->belongsTo('App\Fornitore');
}
/**
* Locazioni Correlate
* Getter
* #return object Locazioni
*/
public function locazioni()
{
// Definizione relazione
return $this->belongsTo('App\Locazione');
}
/**
* Tipologie Correlate
* Getter
* #return object Tipologie
*/
public function tipologie()
{
// Definizione relazione
return $this->belongsTo('App\Tipologia');
}
}
item.php (Resource)
<?php
// Definizione Namespace
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Componente as ComponenteResource;
use App\Http\Resources\Condizione as CondizioneResource;
use App\Http\Resources\Fornitore as FornitoreResource;
use App\Http\Resources\Locazione as LocazioneResource;
use App\Http\Resources\Tipologia as TipologiaResource;
class Item extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
parent::toArray($request);
return [
'id' => $this->id,
'codice' => $this->codice,
'data_acquisto' => $this->data_acqisto,
'serial' => $this->serial,
'labeled' => $this->labeled,
'estensione_garanzia' => $this->estensione_garanzia,
'stato' => $this->stato,
'data_dismissione' => $this->data_dismissione,
'note' => $this->note,
'parent_id' => $this->parent_id,
// Includi associazioni se caricate
'componenti' => ComponenteResource::collection($this->whenLoaded('componenti')),
'condizioni' => CondizioneResource::collection($this->whenLoaded('condizioni')),
'fornitori' => FornitoreResource::collection($this->whenLoaded('fornitori')),
'locazioni' => LocazioneResource::collection($this->whenLoaded('locazioni')),
'tipologie' => TipologiaResource::collection($this->whenLoaded('tipologie'))
];
}
}
This is the screen about an example of data fetched:
As showed above there's no trace of relations. By googling around and changing code as suggested like this:
// Resoruce - Straight including relations instead of lazy load
[...]
'componenti' => ComponenteResource::collection($this->componenti),
[...]
or by expliciting the foreign key in model:
/**
* Componenti Correlati
* Getter
* #return object Componenti
*/
public function componenti()
{
// Definizione relazione
return $this->belongsTo('App\Componente', 'componente_id');
}
I'm still not retrieving relations.
Could anyone give me a little help/tip to solve this problem?
Thanks in advance for help.
The code below will only show Tipologie when it is explicitly loaded to avoid N+1 query problems.
'tipologie' => TipologiaResource::collection($this->whenLoaded('tipologia'))
To load Tipologie for Resource to show it, you need to explicitly load it as:
$itemResource = new ItemResource($item->load('tipologia', ... other relationships...);
See Eager Loading for more information about this.
Edit
Sorry for not understanding the type of relationship, just like #luca-cattide said, collection should not be used for belongsTo, and the correct one is to use:
TipologiaResource::make($this->tipologia);
Or also:
new TipologiaResource($this->topologia);
But I advise you to use "load" method to load the information before, otherwise you perform a search in the database for "item", another by "typologie" and so on until loading all your relationships.
There's another way you load information without having to load the item, see below:
new ItemResource(App\Item::find(1)->with(['tipologie', ... other relationships ... ])->get());
See more about N+1 query problems here.
Thanks #vinicius, but googling around a bit more, as suggested from this post by #CamiloManrique, I noticed that in these relations, I'm trying to fetch data from belongs_to side (so actually from Item and not from Componente, Tipologia and so on). As is ::collection simply doesn't work except if called by hasMany relation side
So, instead using ::collection in conjunction with whenLoaded I refactored like this:
// Includi associazioni se caricate
'componente' => ComponenteResource::make($this->componente),
'condizione' => CondizioneResource::make($this->condizione),
'fornitore' => FornitoreResource::make($this->fornitore),
'locazione' => LocazioneResource::make($this->locazione),
'tipologia' => TipologiaResource::make($this->tipologia)
In this way data being fetched with no error.
Thanks again for your tips.

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 5.1 suggested relationship setup

I am building a timesheet system and have setup a model for timesheets. Timesheet can have many rows - for example when I add a timesheet, I can add many days (rows) to the timesheet.
I want to be able to sync rows when a timesheet gets saved. For example, new rows will be added to the database, missing rows from the given array will be removed from the database.
I understand I can use sync method which works like this, however, I do not think I need a belongsToMany relationship. Currently I have my row relationship setup as a hasMany. The timesheet model looks like this:
<?php
namespace App\Models\Timesheet;
use Illuminate\Database\Eloquent\Model;
class Timesheet extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'timesheet';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['user_id', 'week', 'year', 'token', 'total_hours'];
/**
* Define that we want to include timestamps.
*
* #var boolean
*/
public $timestamps = true;
/**
* Boot the model.
*
*/
public static function boot()
{
parent::boot();
static::deleting(function($timesheet)
{
$timesheet->row()->delete();
});
}
/**
* The rows that belong to the timesheet.
*
* #return Object
*/
public function row()
{
return $this->hasMany('App\Models\Timesheet\RowTimesheet');
}
}
The row_timesheet model looks like this:
namespace App\Models\Timesheet;
use Illuminate\Database\Eloquent\Model;
class RowTimesheet extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'row_timesheet';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['timesheet_id', 'activity_category', 'description', 'eri_number', 'ewn_number'];
/**
* Define that we want to include timestamps.
*
* #var boolean
*/
public $timestamps = true;
What do I need to do in order to make something like this work:
$this->timesheet->find($id)->row()->sync($data);
Thanks in advance.
I believe the 'sync' methods works with 'belongsTomany' relationship.
what you have is 'hasMany' relationship, for that you need to do something like below
use 'save' method instead of 'sync' for hasMany relationship
$data = new App\Comment(['message' => 'A new comment.']);
$this->timesheet->find($id)->row()->save($data); // saves single row sheet object for a timesheet
$this->timesheet->find($id)->row()->saveMany($multipleData); // saves multiple row sheet objects for a timesheet

Categories