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();
Related
In laravel API Resources:
I need a dynamic way to generalize a code for all resources to be used in all controllers instead of using resources in all methods for each controller .. for more clarification, I have a trait that includes generalized functions which return json responses with data and status code, lets take a "sample function" suppose it is showAll(Collection $collection) which is used for returning a collection of data of the specified model for example it is used for returning all users data ..
so I need to build a function that call what ever resource of the specified model, knowing that I have many models...
a) trait that include showAll method:
namespace App\Traits;
use Illuminate\Support\Collection;
trait ApiResponser
{
private function successResponse($data, $code) {
return response()->json($data, $code);
}
protected function showAll(Collection $collection, $code = 200) {
$collection = $this->resourceData($collection);
$collection = $this->filterData($collection);
$collection = $this->sortData($collection);
$collection = $this->paginate($collection);
$collection = $this->cacheResponse($collection);
return $this->successResponse([$collection, 'code' => $code], $code);
}
protected function resourceData(Collection $collection) {
return $collection;
}
}
b) usercontroller as a sample
namespace App\Http\Controllers\User;
use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\ApiController;
class UserController extends ApiController
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$users = User::all();
// Here the showAll(Collection $collection) is used
return $this->showAll($users);
}
}
c) UserResource:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'identity' => $this->id,
'name' => $this->name,
'email' => $this->email,
'isVerified' => $this->verified,
'isAdmin' => $this->admin,
'createDate' => $this->created_at,
'updateDate' => $this->updated_at,
'deleteDate' => $this->deleted_at,
];
}
}
generalize: means used everywhere without code redundancy
What about providers, you may load data there and make that data reachable at places where user data can be reachable ?
laravel docs
I found a simple solution.. by adding the following method
protected function resourceData($collection) {
$collection = get_class($collection[0]);
$resource = 'App\Http\Resources\\' . str_replace('App\\', '', $collection) .
'Resource';
return $resource;
}
The $collection[0] in the first line of this method will get the
model you are currently using.
get_class will get the model name ex: App\User
'App\Http\Resources\\' . str_replace('App\\', '', $collection):
This will get the path of the resource by adding 'App\Http\Resources\' before the
model
str_replace('App\\', '', $collection): will remove App\ path from the collection
name so App\User should be User
then 'Resource' would be concatenated with the previous results and the whole
string should be like that: App\Http\Resources\UserResource
So at the end you should return the whole string App\Http\Resources\UserResource
,finally you should call the resourceData() in
the showAll() method:
protected function showAll(Collection $collection, $code = 200) {
$collection = $this->resourceData($collection);
$collection = $this->filterData($collection);
$collection = $this->sortData($collection);
$collection = $this->paginate($collection);
//Calling resourceData() method
$resource = $this->resourceData($collection);
$collection = $this->cacheResponse($collection);
return $this->successResponse([$resource::collection($collection), 'code' => $code], $code);
}
I am using model factories in NewsTableSeeder, but I get this error when I entered db:seed.
I want to know why I can't use create() in my seeder.
Here is my News model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class News extends Model
{
protected $table = 'news';
protected $primaryKey = 'id';
public function home_news_lists() {
return $this->select('id', 'news_title', 'news_update')
->orderBy('news_update', 'DESC')
->limit(5)
->get();
}
public function lists() {
return News::all();
}
}
Model Factories:
$factory->define(App\Models\News::class, function (Faker\Generator $faker)
{
static $password;
$faker = $faker->create('zh_TW');
return [
'news_title' => $faker->sentence(),
'news_content' => $faker->paragraph(),
'news_author' => $faker->name(),
'news_pageviews' => $faker->numberBetween(1, 100),
'news_file' => ' ',
'news_img' => $faker->imageUrl($width, $height, 'business'),
'created_at' => $faker->dateTimeBetween('2012', 'now', 'zh_TW'),
'updated_at' => $faker->dateTimeBetween('2015', 'now', 'zh_TW')
];
});
NewsTableSeeder :
<?php
use Illuminate\Database\Seeder;
class NewsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
factory(App\Models\News::class, 50)->create();
}
}
I can't tell 100% without seeing exactly the error you got, but I do believe there is no create() method on the $faker object.
I believe what you mean to do is:
$factory->define(App\Models\News::class, function (Faker\Generator $faker)
{
static $password;
$faker = \Faker\Factory::create('zh_TW'); // change to this
return [
...
];
}
I would just create a new faker generator (\Faker\Generator) that gets returned from calling \Faker\Factory::create($locale) and use that instead. Otherwise, I believe your next best option is to override wherever Laravel instantiates the \Faker\Generator $faker object that gets passed into the callback, but that may get hacky if Laravel doesn't provide a clean way to do it.
The create() method is a static call on the \Faker\Factory method. It accepts a locale as the parameter and uses en_US as the default locale.
$faker = $faker->create('zh_TW');
The error message said this code is wrong.
What is your purpose to use this code?
I've developed an API with Laravel 5 and Dingo/API.
Following the documentation, i used pagination and my code look like that
$users = User::paginate(50);
return $this->response->paginator($users, new UserTransformer);
Unfortunately, the response root key is "data"
{
"data": [
{
"id": 1,
"username": "superuser",
......
I'd like to change the "data" key to a custom one, because in my case, emberjs get this response and try to make a link with a "datum" model which doesn't exist, the key need to be set with the same name as the ember model in case of a RESTAdapter.
I already tried some parameters in the response but nothing change
return $this->response->paginator($users, new UserTransformer, ['key' => 'users']);
or
return $this->response->paginator($users, new UserTransformer, ['identifier' => 'users']);
Nothing work, i'm stuck with the "data" key.
Is someone have a solution ?
Thank you in advance.
I managed to fix my problem.
I don't modify the api.php configuration, transformer stay the same
'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class),
Firstly i create a new serializer
// app/Api/V1/Serializers/CustomJsonSerializer.php
<?php namespace App\Api\V1\Serializers;
use League\Fractal\Pagination\CursorInterface;
use League\Fractal\Pagination\PaginatorInterface;
use League\Fractal\Serializer\SerializerAbstract;
/**
* Create a new Serializer in your project
*/
use League\Fractal\Serializer\ArraySerializer;
class CustomJsonSerializer extends ArraySerializer
{
public function collection($resourceKey, array $data)
{
if ($resourceKey === false) {
return $data;
}
return array($resourceKey ?: 'data' => $data);
}
public function item($resourceKey, array $data)
{
if ($resourceKey === false) {
return $data;
}
return array($resourceKey ?: 'data' => $data);
}
}
And i set my new custom serializer inside the AppServiceProviders
// app\Providers\AppServiceProviders.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Dingo\Api\Transformer\Adapter\Fractal;
use League\Fractal\Manager;
use App\Api\V1\Serializers\CustomJsonSerializer;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->app['Dingo\Api\Transformer\Factory']->setAdapter(function ($app) {
$fractal = new Manager();
$fractal->setSerializer(new CustomJsonSerializer());
return new Fractal($fractal);
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
I hope it'll help ppl :)
or you can use something like this
Answer::where('question_id', '=', $questionId)
->join('articles', 'answers.article_id', '=', 'articles.id')
->orderBy('count_thanks', 'desc')
->limit($perPage)
->offset($offset)
->get();
I have been trying to learn how event works in Yii from the Yii Guide. I have found there are most important things: Event Handlers, Attaching Event Handlers and Triggering Events. I have read entire the article properly. But I don't understand how to implement these three things properly. How to see the effect of it's implementation. I have extended component class as:
namespace app\components;
use yii\base\Component;
use yii\base\Event;
use yii\web\View;
class Foo extends Component{
const EVENT_HELLO = 'hello';
public function bar()
{
$this->trigger(self::EVENT_HELLO);
}
}
I do not understand what is the next part to do. Where I should write the Attaching Event Handlers. Can you help me someone, at least I can see a simple output using event.
You may create init() method in model:
public function init()
{
$this->on(Event::ACTION_ADD, ['app\models\Event', 'sendInLog']);
$this->on(Event::ACTION_DELETE, ['app\models\Event', 'sendInLog']);
$this->on(Event::ACTION_UPDATE, ['app\models\Event', 'sendInLog']);
}
In initialize events in second parameter you may use current model or set other model. If you want use current model set like that:
[$this, 'sendInLog']
sendInLog it is method in model. In method sendInLog one parameter it is $event. This is object \yii\base\Event. In property $event->name - it is event name. In property $event->sender - it is model class from trigger event.
In my class app\models\Event like that:
namespace app\models;
class Event extends Component
{
const ACTION_ADD = 1;
const ACTION_DELETE = 2;
const ACTION_UPDATE = 3;
const TYPE_PROJECT = 10;
const TYPE_BIDS = 20;
const TYPE_BIDS_DATA = 30;
/**
* #param $event
*/
public static function sendInLog($event)
{
/** #var \yii\base\Event $event */
/** #var ActiveRecord $event->sender */
$userId = Yii::$app->user->id;
$model = new Logs([
'type' => $event->sender->getType(),
'action' => $event->name,
'id_user' => $userId,
'old_data' => Json::encode($event->sender->attributes),
'new_data' => Json::encode($event->sender->oldAttributes),
]);
$model->save();
}
}
Run trigger like that:
public function afterDelete()
{
$this->trigger(Event::ACTION_DELETE);
parent::afterDelete();
}
Or
public function actionView()
{
$this->trigger(Event::ACTION_VIEW);
$this->render(...);
}
EDIT:
For example. If you want run trigger after delete, insert, update. You may use trigger in afterDelete, afterSave in model. If you want run trigger in controller run trigger like that:
public function actionCreate()
{
$model = new Bids();
$model->id_project = Yii::$app->request->get('projectId');
$fieldsDefaults = BidsFieldsDefaults::find()->orderBy(['order' => SORT_ASC])->all();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
$model->trigger(Event::ACTION_ADD);
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
'fieldsDefaults' => $fieldsDefaults
]);
}
}
I show you two different ways to run trigger. Which one to use is up to you :)
I am trying to implement a custom transformer using dingo api (https://github.com/dingo/api/wiki/Transformers#custom-transformation-layer) for my Post model and I am getting this exception:
Missing argument 2 for PostTransformer::transform(), called in /home/.../vendor/league/fractal/src/Scope.php on line 298 and defined
My controller:
$post = Post::findOrFail(2);
return $this->item($post, new PostTransformer);
My PostTransformer class:
<?php
use Illuminate\Http\Request;
use Dingo\Api\Transformer\Binding;
use Dingo\Api\Transformer\TransformerInterface;
class PostTransformer implements TransformerInterface
{
public function transform($response, $transformer, Binding $binding, Request $request)
{
// Make a call to your transformation layer to transformer the given response.
return [
'kkk' => 'val'
];
}
}
What is wrong?
Your PostTransformer isn't a Transformer. What you specified there is an TransformerLayer (https://github.com/dingo/api/wiki/Transformers#custom-transformation-layer).
However a Transformer in Dingo looks like this:
<?php
use League\Fractal\TransformerAbstract;
class PostTransformer extends TransformerAbstract
{
public function transform(Post $post) {
return [
'id' => $post->id
// ...
];
}
}