I am using Fractal Transformers in Laravel 5. I have:
namespace App\Transformers;
use App\Models\Cake;
use League\Fractal\TransformerAbstract;
class CakeTransformer extends TransformerAbstract
{
protected $availableIncludes = [
'user',
'description'
];
public function transform(Cake $cake)
{
$ar = [
'name' => $cake->name,
'url_name' => $cake->url_name,
'user' => $cake->user->screenname,
'date_created' => $cake->created_at
];
return $ar;
}
public function includeUser(Cake $cake)
{
return $this->item($cake->user, new UserTransformer());
}
public function includeDescription(Cake $cake) {
return $cake->description;
}
}
The above doesn't work because includeDescription doesn't return the right kind of object, but from the above you can see what I'm trying to do.
For instance in my search I want to bring back much less data than if I were to load a whole page about the search item. E.g. for search I don't want to load the description, but for the page that contains details about the product I would want to.
How can I achieve this?
Related
I've been trying to send data from controller to view in CodeIgniter 4, let me just...
Model:
class KomikModel extends Model
{
protected $table = 'komik';
protected $useTimestamps = true;
protected $allowedFields = ['judul', 'slug', 'penulis', 'penerbit', 'sampul'];
public function getKomik($slug = false)
{
if ($slug == false) {
return $this->findAll();
}
return $this->where(['slug' => $slug])->first();
}
}
Controller:
public function edit($slug)
{
$data = [
'title' => 'Form Ubah Data',
'validation' => \Config\Services::validation(),
'komik' => $this->komikModel->getKomik($slug)
];
return view('komik/edit', $data);
}
View:
<?= dd($komik); ?>
After all that, $komik that arrived in the view comes out as null. Why, what did I do wrong? Thanks in advance.
PS: Somehow, it works fine with other methods. currently there are 3 other methods using the model to send data from controller towards the view. but only at this edit method the problem occurs.
The issue is with this particular line.
return $this->where(['slug' => $slug])->first();
Model Class doesn't have any "where" method defined in it. It borrows it from Builder Class. This is called object composition. You can certainly try to have your own method with a similar implementation as in the Model class but I would recommend against it since it is an antipattern.
A better approach would be to have a single class that is responsible for all the querying or filtering as in Repository Pattern. You can do a bit of research on that.
I want to return a selection from my database whenever a user specifies an id.
I have a laravel 5.5 API with in the api.php the following route.
Route::get('companytasks/{id}','TaskController#showOfCompany');
In my TaskController i have the following function:
public function showOfCompany($id)
{
// Get tasks with the specifiec company id
$companytasks = Task::findOrFail($id);
// Return single task as a resource
return new TaskResource($companytasks);
}
And my resource looks like this:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Task extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'TaskCompanyId' => $this->TaskCompanyId,
'TaskName' => $this->TaskName,
'TaskDescription' => $this->TaskDescription,
'Branche' => $this->Branche,
'Budget' => $this->Budget,
'TaskFinishDate' => $this->TaskFinishDate,
'PaydFor' => $this->PaydFor,
'PublicTask' => $this->PublicTask,
'created_at' => $this->created_at
];
}
}
I want to make a selection from the database where the 'TaskCompanyId' equals the $id.
I can do this with the following line in my controller:
$companytasks = Task::where('TaskCompanyId','=',$id)->get();
But this does not work for me, I can however add ->first() to it so it only returns the first occurrance but I want all ocurrances to be returned. How do I achieve this?
You are using 'CompanyTaskId' instead of 'TaskCompanyId' in where clause
Look OK but try with this.
$companytasks = App\Task::where( 'TaskCompanyId' , $id )->get();
If you want you can order or paginate
$companytasks = App\Task::where( 'TaskCompanyId' , $id )
->orderBy('CompanyTaskId', 'desc')
->paginate(15);
I managed to fix it by changing the resource code to:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
class Task extends ResourceCollection
{
public function toArray($request)
{
return [
'data' => $this->collection
];
}
}
Making a selection returns a collection instead of an object and therefore needs a ResourceCollection instead of a JsonResource.
To change the JSON output send by a Laravel 5.4 RESTful API, I make use of the Fractal package by thephpleague. Because pagination will be added in the future, it is important that collections make use of the default DataArraySerializer and single items use the ArraySerializer. It is also needed that deeper nested objects are given the same structure. How can I achieve this (globally or not)?
class TreeTransformer extends TransformerAbstract {
protected $defaultIncludes = [
'type',
'branches'
];
public function transform(Tree $tree) {
return [
'id' => (int)$tree->id,
'name' => (string)$tree->name
];
}
public function includeType(Tree $tree) {
return $this->item($tree->type, new TypeTransformer()); // Should be ArraySerializer
}
public function includeBranches(Tree $tree) {
return $this->collection($tree->branches, new BranchTransformer()); // Should stay DataArraySerializer
}
}
Unfortunately, I think what you are trying to do is not possible yet. More information here: https://github.com/thephpleague/fractal/issues/315
You can still change the serializer for an entire output like this: https://github.com/spatie/fractalistic#using-a-serializer. But it is not what you want to achieve.
Actually you can.
It may look quite more verbose, but the trick is just not using $defaultIncludes. Use the fractal helper instead.
class TreeTransformer extends TransformerAbstract {
public function transform(Tree $tree) {
return [
'id' => (int)$tree->id,
'name' => (string)$tree->name,
'type' => $this->serializeType($tree)
'branches' => $this->serializeBranches($tree)
];
}
private function serializeType(Tree $tree) {
return fractal()
->serializeWith(new ArraySerializer())
->collection($tree->type)
->transformWith(TypeTransformer::class)
->toArray(); // ArraySerializer
}
private function serializeBranches(Tree $tree) {
return fractal()
->serializeWith(new DataArraySerializer())
->collection($tree->branches)
->transformWith(BranchesTransformer::class)
->toArray(); // DataArraySerializer
}
}
It's working for me with ArraySerializer. Didn't try DataArraySerializer.
Struggling using parseIncludes in https://github.com/thephpleague/fractal.
I have two tables, Property and Weeks. Each property has many weeks. Using Fractal I can return my property item with a collection of weeks. What I want to do is use parseIncludes, so that the return of weeks is optional.
PropertyTransformer.php
<?php
namespace App\Transformer;
use App\Models\Property;
use League\Fractal\TransformerAbstract;
class PropertyTransformer extends TransformerAbstract
{
protected $availableIncludes = [
'week'
];
public function transform(Property $property)
{
return [
'id' => (int) $property['PropertyID'],
'PropertyName' => $property['PropertyName'],
'ExactBeds' => (int) $property['ExactBeds'],
'weeks' => $property->week
];
}
/**
* Include Week
*
* #return League\Fractal\ItemResource
*/
public function includeWeek( Property $property )
{
$week = $property->week;
return $this->item($week, new WeekTransformer);
}
}
WeekTransformer.php
<?php
namespace App\Transformer;
use App\Models\Week;
use League\Fractal;
class WeekTransformer extends Fractal\TransformerAbstract
{
public function transform(Week $week)
{
return [
'Week' => $week['week'],
'Available' => $week['available'],
'Price' => (int) $week['price'],
];
}
}
My PropertyController.php
<?php namespace App\Http\Controllers\Api\v1;
use App\Http\Requests;
use App\Models\Week;
use Illuminate\Support\Facades\Response;
use App\Models\Property;
use League\Fractal;
use League\Fractal\Manager;
use League\Fractal\Resource\Collection as Collection;
use League\Fractal\Resource\Item as Item;
use App\Transformer\PropertyTransformer;
class PropertyController extends \App\Http\Controllers\Controller {
public function show($id)
{
$property = Property::with('bedroom')->with('week')->find($id);
$fractal = new Fractal\Manager();
if (isset($_GET['include'])) {
$fractal->parseIncludes($_GET['include']);
}
$resource = new Fractal\Resource\Item($property, new PropertyTransformer);
//$resource = new Fractal\Resource\Collection($properies, new PropertyTransformer);
return $fractal->createData( $resource )->parseIncludes('weeks')->toJson();
}
I get the following error on the parseIncludes:-
Method 'parseIncludes' not found in class \League\Fractal\Scope
I'm following the guide here on transformers - http://fractal.thephpleague.com/transformers/
I think I am going wrong somewhere here where it says:-
These includes will be available but can never be requested unless the Manager::parseIncludes() method is called:
<?php
use League\Fractal;
$fractal = new Fractal\Manager();
if (isset($_GET['include'])) {
$fractal->parseIncludes($_GET['include']);
}
If I remove the parseIncludes, I don't get an error, I also get my property data with my collection of weeks, but ?include=week doesn't work to optionally get it.
Your problem is in this line:
return $fractal->createData( $resource )->parseIncludes('weeks')->toJson();
createData() returns \League\Fractal\Scope and it has no parseInlcudes method.
You've already called parseIncludes here:
if (isset($_GET['include'])) {
$fractal->parseIncludes($_GET['include']);
}
So just remove the second call to it in the return statement:
return $fractal->createData($resource)->toJson();
I went through offical guide and found a way to envelop JSON data like this.
use yii\rest\ActiveController;
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
public $serializer = [
'class' => 'yii\rest\Serializer',
'collectionEnvelope' => 'items',
];
}
This works perfect when I have a collection and then I have a response like this.
{
products:....
}
But what I want to do is that i have a envelope for single data. For example if I do products/10 GET request to get.
{
product:
}
Hope somebody figured it out.
Single Data Envelope is not supported by \yii\rest\Serializer. At least until Yii 2.0.6 only collections get enveloped in order to add _links and _meta data objects to the response.
To envelope single data resource objects you'll need to override ActiveController's default view action within your Controller :
public function actions()
{
$actions = parent::actions();
unset($actions['view']);
return $actions;
}
public function actionView($id)
{
$model = Product::findOne($id);
return ['product' => $model];
}
Old, but I just bumped into here with the same problem.
And found a better (I think) solution: create your own serializer class extending \yii\rest\Serializer:
class Serializer extends \yii\rest\Serializer
{
public $itemEnvelope;
public function serializeModel($model)
{
$data = parent::serializeModel($model);
if($this->itemEnvelope)return [$this->itemEnvelope=>$data];
return $data;
}
}
And then use it like this:
public $serializer = [
'class' => '[your-namespace]\Serializer',
'collectionEnvelope' => 'list',
'itemEnvelope' => 'item'
];