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.
Related
In my Laravel Nova project, I have a Page and a PageTranslation (model and resource). When adding a hasMany to my Resource fields, upon visiting the detail of the Page, I get a 404 error. This is my code
This is my Page Resource
<?php
namespace App\Pages\Resources;
use Illuminate\Http\Request;
use Laravel\Nova\Resource;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\HasMany;
class Page extends Resource
{
/**
* The model the resource corresponds to.
*
* #var string
*/
public static $model = 'App\Pages\Models\Page';
/**
* The single value that should be used to represent the resource when being displayed.
*
* #var string
*/
public static $title = 'working_title';
/**
* #var string
*/
public static $group = 'Pages';
/**
* The columns that should be searched.
*
* #var array
*/
public static $search = [
'id', 'working_title'
];
/**
* Eager load translations
*/
public static $with = ['translations'];
/**
* Get the fields displayed by the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title', 'working_title')
->sortable()
->rules('required', 'max:256'),
HasMany::make('Translations', 'translations', \App\Pages\Resources\PageTranslation::class)
];
}
}
This is my PageTranslation Resource
<?php
namespace Codedor\Pages\Resources;
use Illuminate\Http\Request;
use Laravel\Nova\Resource;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
class PageTranslation extends Resource
{
/**
* The model the resource corresponds to.
*
* #var string
*/
public static $model = 'Codedor\Pages\Models\PageTranslation';
/**
* Hide resource from Nova's standard menu.
* #var bool
*/
public static $displayInNavigation = false;
/**
* Get the fields displayed by the resource.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Locale')
->sortable()
->rules('required', 'max:256')
];
}
}
I'm a little bit late, but if anyone comes across this issue while using Nova::resources instead of the resources path inside resources method in NovaServiceProvider, make sure you add the related resource to the list.
If you wish to hide a resource from the sidebar navirgation, just use public static $displayInNavigation = false; inside the resource file
It's not related to relationships at all. Make sure you've included the resources in your NovaServiceProvider
Also, to restrict from viewing in the sidebar based on user role, you can do something like:
public static function availableForNavigation(Request $request)
{
return $request->user()->isAdmin();
}
I've seen many examples of how to set up a OneToMany association between Entities. However, I have not seen anything on how to output the data from an association. (such as converting to JSON or just having a clean array)
So, here is some sample code:
declare(strict_types=1);
namespace Banks\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
/**
* https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/basic-mapping.html
*
* #ORM\Entity
* #ORM\Table(name="bank")
**/
class Banks implements \JsonSerializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer", name="id", nullable=false)
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* A Bank could have Many Branches
*
* #ORM\OneToMany(targetEntity="Branches\Entity\Branches", mappedBy="bank")
*
*/
protected $branches;
/**
* #ORM\Column(type="string", nullable=true)
*/
protected $name;
/**
*
* #return array|mixed
*/
public function jsonSerialize()
{
return [
'id' => $this->id,
'name' => $this->name,
'branches' => $this->getBranches()
];
}
public function __construct()
{
$this->branches = new ArrayCollection();
}
public function getBranches(): Collection
{
return $this->branches;
}
// ... Other getter/setters removed
}
Then we also have the Branches Entity:
declare(strict_types=1);
namespace Branches\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/basic-mapping.html
*
* #ORM\Entity
* #ORM\Table(name="branches")
**/
class Branches implements \JsonSerializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer", nullable=false)
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* A Branch has one Bank
*
* #ORM\ManyToOne(targetEntity="Banks\Entity\Banks", inversedBy="branches")
* #ORM\JoinColumn(name="bank_id", referencedColumnName="id")
*/
protected $bank;
/**
* #ORM\Column(type="integer", nullable=false)
*/
protected $bank_id;
/**
* #ORM\Column(type="string", nullable=true)
*/
protected $name;
/**
*
* #return array|mixed
*/
public function jsonSerialize()
{
return [
'id' => $this->id,
'bank_id' => $this->bank_id,
'name' => $this->name,
'bank' => $this->getBank()
];
}
public function getBank()
{
return $this->bank;
}
// ... Other getter/setters removed
}
Querying both Entities work fine overall, with calls to $result->jsonSerialize(), then returning with return new JsonResponse($result) to get a JSON object. Though querying a Branch has the expected result, where I receive the Branch along with the associated Bank as part of the output, the query to Bank is not returning the associated Branches and instead only displays as "branches": {}
I know this is because $branches is a Collection, but how to output it in a way to be part of the resulting JSON object?
I've tried $this->branches->toArray(), but that results in an array of Objects that cannot be encoded to JSON, therefore, ending in an error.
NOTE: The contents (Object) of $this->getBranches() does contain the Branches as expected, which can be seen by $this->branches->count(). But how to reach them in such a way to allow JsonSerializable to create the JSON?
As requested, here is middleware code leaving up to Entity usage:
A factory is used to create what is needed by the Handler:
class BanksViewHandlerFactory
{
public function __invoke(ContainerInterface $container) : BanksViewHandler
{
$entityManager = $container->get(EntityManager::class);
$entityManager->getConfiguration()->addEntityNamespace('Banks', 'Banks\Entity');
$entityRepository = $entityManager->getRepository('Banks:Banks');
return new BanksViewHandler($entityManager, $entityRepository);
}
}
The Factory calls the Handler:
class BanksViewHandler implements RequestHandlerInterface
{
protected $entityManager;
protected $entityRepository;
public function __construct(
EntityManager $entityManager,
EntityRepository $entityRepository,
) {
$this->entityManager = $entityManager;
$this->entityRepository = $entityRepository;
}
public function handle(ServerRequestInterface $request) : ResponseInterface
{
$return = $this->entityRepository->find($request->getAttribute('id'));
$result['Result']['Banks'] = $return->jsonSerialize();
return new JsonResponse($result);
}
}
The handler returns the JSON.
It's important to note that, when implementing the \JsonSerializable interface, calling jsonSerialize() directly does not return JSON, and you do not call this method explicitly.
As stated in the docs:
Objects implementing JsonSerializable can customize their JSON representation when encoded with json_encode().
The intent of implementing this interface is to enforce the jsonSerialize() method, which is called internally when passing the object(s) to json_encode(); e.g:
$result = $banksRepository->find($id);
$json = json_encode($result);
Additionally, if you want to serialize the child Branch entities as well you need to:
Implement \JsonSerializable for this entity (which you have done)
Doctrine will return these Branches as an ArrayCollection object, containing all child Branch objects. In order to ensure that json_encode() encodes these to JSON properly you need to convert the ArrayCollection to an array using toArray().
To illustrate - (as you pointed out you also implemented this):
public function jsonSerialize()
{
return [
'id' => $this->id,
'name' => $this->name,
'branches' => $this->getBranches()->toArray(), // <--
];
}
This should serialise your Bank and associated Branch entities as expected. Hope this helps :)
I'm trying to create a link in between 2 objects using NeoEloquent. Unfortunately i get the following error.
Class 'Permission' not found
I tried pretty much everything but i can't get it to work unfortunately.
I submit the permission objects I want to link to as an integer representing the id of the label.
The relationship between the labels is a Many to Many relation. As far as i can see i've done everything correctly. I've checked with the GitHub page, it looks good to me.
Thanks in advance!
Controller method:
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param Role $role
* #return \Illuminate\Http\Response
*/
public function update(Request $request, Role $role)
{
//dd($request);
$this->validate($request, [
'title' => 'required',
]);
foreach($request['permission'] as $perm){
$role->permissions()->attach($perm);
}
$role->title = request('title');
$role->save();
return redirect("/roles");
}
Role Model:
<?php
namespace App;
use Vinelab\NeoEloquent\Eloquent\Model as NeoEloquent;
class Role extends NeoEloquent
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'title',
];
protected $label = "Role";
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
];
public function permissions(){
return $this->hasMany('Permission', 'Has_Permission');
}
}
Permission Model:
<?php
namespace App;
use Vinelab\NeoEloquent\Eloquent\Model as NeoEloquent;
class Permission extends NeoEloquent
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'title',
];
protected $label = "Permission";
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
];
}
I have a php laravel projekt where I need to add a field to one/more models (Eloquent). I don't have much experience in php and never tried laravel before.
The class looks like this now
class Player extends Eloquent
{
use GenderTrait;
use VisibilityTrait;
use PlayerPhotoTrait;
use PlayerActionTrait;
const GENDER_MALE = 2;
const GENDER_FEMALE = 1;
/**
* The database table used by model.
*
* #var string
*/
protected $table = 'players';
/**
* Parameters for `actions` relation.
*
* #see PlayerActionTrait::actions()
* #var array
*/
protected $actionModel = [
'name' => 'PlayerAction',
'foreignKey' => 'player_id',
];
/**
* The list of mass-assignable attributes.
*
* #var array
*/
protected $fillable = [
'name',
'start_value',
'gender',
'is_visible',
'nation',
];
/**
* The list of validation rules.
*
* #var array
*/
public static $rules = [
'name' => 'required',
'nation' => 'required|numeric',
'start_value' => 'required|numeric',
];
/**
* #inheritdoc
*/
protected static function boot()
{
parent::boot();
}
/**
* Players country.
*
* #return Country
*/
public function country()
{
return $this->belongsTo('Country', 'nation');
}
/**
* Player videos.
*
* #return mixed
*/
public function videos()
{
return $this->morphMany('YoutubeLink', 'owner');
}
}
I would like to add a string field called "level" but I have no idea how to go about it. If I create the field in MySQL first and then the models get updated, if I update the models and then Laravel update MySQL for me?
Im looking forward to hearing what I can do :)
You need to add migration:
php artisan make:migration add_fields_to_players_table --table=players
Open in /database/migrations new migration and write
Schema::table('players', function ($table) {
$table->string('new_string_field');
});
Now you need to run migrations
php artisan migrate
More info and available column types here
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