Giter VIP home page Giter VIP logo

laravel5-jsonapi's Introduction

Laravel 5 JSON API Server Package

Scrutinizer Code Quality SensioLabsInsight Latest Stable Version Total Downloads License Donate

Compatible with Laravel 5.0, 5.1 & 5.2

  • Package provides a full implementation of the JSON API specification, and is featured on the official site!
  • A JSON API Transformer that will allow you to convert any mapped object into a valid JSON API resource.
  • Controller boilerplate to write a fully compiliant JSON API Server using your exisiting Eloquent Models.
  • Works for Laravel 5 and Lumen frameworks.

Installation

Use Composer to install the package:

composer require nilportugues/laravel5-json-api

Now run the following artisan command:

php artisan vendor:publish

Configuration (Laravel 5 & Lumen)

For the sake of having a real life example, this configuration will guide you on how to set up 7 end-points for two resources, Employees and Orders.

Both Employees and Orders resources will be Eloquent models, being related one with the other.

Furthermore, Employeeswill be using an Eloquent feature, appended fields to demonstrate how it is possible to make the most of Eloquent and this package all together.

Configuration for Laravel 5

Step 1: Add the Service Provider

Open up config/app.php and add the following line under providers array:

'providers' => [
    //...
    NilPortugues\Laravel5\JsonApi\Laravel5JsonApiServiceProvider::class,
],

Step 2: Defining routes

We will be planning the resources ahead its implementation. All routes require to have a name.

This is how our app/Http/routes.php will look:

<?php
Route::group(['namespace' => 'Api'], function() {
    Route::resource('employees', 'EmployeesController');    
    Route::get(
        'employees/{employee_id}/orders', [
        'as' => 'employees.orders',
        'uses' => 'EmployeesController@getOrdersByEmployee'
    ]);
});
//...

Step 3: Definition

First, let's define the Models for Employees and Orders using Eloquent.

Employees (Eloquent Model)

<?php namespace App\Model\Database;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Validation\ValidatesRequests;

class Employees extends Model
{
    public $timestamps = false;
    protected $table = 'employees';    
    protected $primaryKey = 'id';
    protected $appends = ['full_name'];

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function latestOrders()
    {
        return $this->hasMany(Orders::class, 'employee_id')->limit(10);
    }

    /**
     * @return string
     */
    public function getFullNameAttribute()
    {
        return $this->first_name.' '.$this->last_name;
    }
}

Employees SQL

CREATE TABLE `employees` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `company` varchar(50) DEFAULT NULL,
  `last_name` varchar(50) DEFAULT NULL,
  `first_name` varchar(50) DEFAULT NULL,
  `email_address` varchar(50) DEFAULT NULL,
  `job_title` varchar(50) DEFAULT NULL,
  `business_phone` varchar(25) DEFAULT NULL,
  `home_phone` varchar(25) DEFAULT NULL,
  `mobile_phone` varchar(25) DEFAULT NULL,
  `fax_number` varchar(25) DEFAULT NULL,
  `address` longtext,
  `city` varchar(50) DEFAULT NULL,
  `state_province` varchar(50) DEFAULT NULL,
  `zip_postal_code` varchar(15) DEFAULT NULL,
  `country_region` varchar(50) DEFAULT NULL,
  `web_page` longtext,
  `notes` longtext,
  `attachments` longblob,
  PRIMARY KEY (`id`),
  KEY `city` (`city`),
  KEY `company` (`company`),
  KEY `first_name` (`first_name`),
  KEY `last_name` (`last_name`),
  KEY `zip_postal_code` (`zip_postal_code`),
  KEY `state_province` (`state_province`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
INSERT INTO `employees` (`id`, `company`, `last_name`, `first_name`, `email_address`, `job_title`, `business_phone`, `home_phone`, `mobile_phone`, `fax_number`, `address`, `city`, `state_province`, `zip_postal_code`, `country_region`, `web_page`, `notes`, `attachments`)
VALUES
    (10, 'Acme Industries', 'Smith', 'Mike', '[email protected]', 'Horticultarlist', '0118 9843212', NULL, NULL, NULL, '343 Friary Road', 'Manchester', 'Lancs.', 'M3 3DL', 'United Kingdom', NULL, NULL, NULL);

Orders (Eloquent Model)

<?php namespace App\Model\Database;

use Illuminate\Database\Eloquent\Model;

class Orders extends Model
{   
    public $timestamps = false;
    protected $table = 'orders';
    protected $primaryKey = 'id';

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function employee()
    {
        return $this->belongsTo(Employees::class, 'employee_id');
    }
}

Orders SQL

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `employee_id` int(11) DEFAULT NULL,
  `customer_id` int(11) DEFAULT NULL,
  `order_date` datetime DEFAULT NULL,
  `shipped_date` datetime DEFAULT NULL,
  `shipper_id` int(11) DEFAULT NULL,
  `ship_name` varchar(50) DEFAULT NULL,
  `ship_address` longtext,
  `ship_city` varchar(50) DEFAULT NULL,
  `ship_state_province` varchar(50) DEFAULT NULL,
  `ship_zip_postal_code` varchar(50) DEFAULT NULL,
  `ship_country_region` varchar(50) DEFAULT NULL,
  `shipping_fee` decimal(19,4) DEFAULT '0.0000',
  `taxes` decimal(19,4) DEFAULT '0.0000',
  `payment_type` varchar(50) DEFAULT NULL,
  `paid_date` datetime DEFAULT NULL,
  `notes` longtext,
  `tax_rate` double DEFAULT '0',
  `tax_status_id` tinyint(4) DEFAULT NULL,
  `status_id` tinyint(4) DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `customer_id` (`customer_id`),
  KEY `employee_id` (`employee_id`),
  KEY `id` (`id`),
  KEY `shipper_id` (`shipper_id`),
  KEY `tax_status` (`tax_status_id`),
  KEY `ship_zip_postal_code` (`ship_zip_postal_code`),
  KEY `fk_orders_orders_status1` (`status_id`),  
  CONSTRAINT `fk_orders_employees1` FOREIGN KEY (`employee_id`) REFERENCES `employees` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=82 DEFAULT CHARSET=utf8;
INSERT INTO `orders` (`id`, `employee_id`, `customer_id`, `order_date`, `shipped_date`, `shipper_id`, `ship_name`, `ship_address`, `ship_city`, `ship_state_province`, `ship_zip_postal_code`, `ship_country_region`, `shipping_fee`, `taxes`, `payment_type`, `paid_date`, `notes`, `tax_rate`, `tax_status_id`, `status_id`)
VALUES
    (82, 10, NULL, '2015-03-12 00:00:00', '2015-03-12 00:00:00', NULL, NULL, '43, Borrowed Drive', 'New Oreleans', 'Louisiana', '4322', 'USA', 1.4000, 0.0000, NULL, NULL, NULL, 0, NULL, 0);

Follow up, we'll be creating Transformers. One Transformer is required for each class and it must implement the \NilPortugues\Api\Mappings\JsonApiMapping interface.

We will be placing these files at app/Model/Api:

EmployeesTransformer

<?php namespace App\Model\Api;

use App\Model\Database\Employees;
use NilPortugues\Api\Mappings\JsonApiMapping;

class EmployeesTransformer implements JsonApiMapping
{
    /**
     * Returns a string with the full class name, including namespace.
     *
     * @return string
     */
    public function getClass()
    {
        return Employees::class;
    }

    /**
     * Returns a string representing the resource name 
     * as it will be shown after the mapping.
     *
     * @return string
     */
    public function getAlias()
    {
        return 'employee';
    }

    /**
     * Returns an array of properties that will be renamed.
     * Key is current property from the class. 
     * Value is the property's alias name.
     *
     * @return array
     */
    public function getAliasedProperties()
    {
        return [
            'last_name' => 'surname',
            
        ];
    }

    /**
     * List of properties in the class that will be  ignored by the mapping.
     *
     * @return array
     */
    public function getHideProperties()
    {
        return [
            'attachments'
        ];
    }

    /**
     * Returns an array of properties that are used as an ID value.
     *
     * @return array
     */
    public function getIdProperties()
    {
        return ['id'];
    }

    /**
     * Returns a list of URLs. This urls must have placeholders 
     * to be replaced with the getIdProperties() values.
     *
     * @return array
     */
    public function getUrls()
    {
        return [
            'self' => ['name' => 'employees.show', 'as_id' => 'id'],
            'employees' => ['name' => 'employees.index'],
            'employee_orders' => ['name' => 'employees.orders', 'as_id' => 'id']
        ];
    }

    /**
     * Returns an array containing the relationship mappings as an array.
     * Key for each relationship defined must match a property of the mapped class.
     *
     * @return array
     */
    public function getRelationships()
    {
        return [];
    }
} 

Same goes for Orders, these files will also be placed at app/Model/Api:

OrdersTransformer

<?php namespace App\Model\Api;

use App\Model\Database\Orders;
use NilPortugues\Api\Mappings\JsonApiMapping;

class OrdersTransformer implements JsonApiMapping
{
    /**
     * {@inheritDoc}
     */
    public function getClass()
    {
        return Orders::class;
    }
    /**
     * {@inheritDoc}
     */
    public function getAlias()
    {
        return 'order';
    }
    /**
     * {@inheritDoc}
     */
    public function getAliasedProperties()
    {
        return [];
    }
    /**
     * {@inheritDoc}
     */
    public function getHideProperties()
    {
        return [];
    }
    /**
     * {@inheritDoc}
     */
    public function getIdProperties()
    {
        return ['id'];
    }
    /**
     * {@inheritDoc}
     */
    public function getUrls()
    {
        return [
            'self'     => ['name' => 'orders.show', 'as_id' => 'id'],
            'employee' => ['name' => 'employees.show', 'as_id' => 'employee_id'],
        ];
    }
    /**
     * {@inheritDoc}
     */
    public function getRelationships()
    {
        return [];
    }
    
    /**
     * List the fields that are mandatory in a persitence action (POST/PUT). 
     * If empty array is returned, all fields are mandatory.
     */
    public function getRequiredProperties()
    {
        return [];
    }    
} 

Step 4: Usage

Create file config/jsonapi.php. This file should return an array returning all the class mappings.

<?php
use App\Model\Api\EmployeesTransformer;
use App\Model\Api\OrdersTransformer;

return [
    EmployeesTransformer::class,
    OrdersTransformer::class,
];

Configuration for Lumen

Step 1: Add the Service Provider

Open up bootstrap/app.phpand add the following lines before the return $app; statement:

$app->register(\NilPortugues\Laravel5\JsonApi\Laravel5JsonApiServiceProvider::class);
$app->configure('jsonapi');

Also, enable Facades by uncommenting:

$app->withFacades();

Step 2: Defining routes

We will be planning the resources ahead its implementation. All routes require to have a name.

This is how our app/Http/routes.php will look:

<?php
$app->group(
    ['namespace' => 'Api'], function($app) {
        $app->get(
            'employees', [
            'as' => 'employees.index',
            'uses' =>'EmployeesController@index'
        ]);
        $app->post(
            'employees', [
            'as' => 'employees.store',
            'uses' =>'EmployeesController@store'
        ]);
        $app->get(
            'employees/{employee_id}', [
            'as' => 'employees.show', 
            'uses' =>'EmployeesController@show'
        ]);
        $app->put(
            'employees/{employee_id}', [
            'as' => 'employees.update', 
            'uses' =>'EmployeesController@update'
        ]);
        $app->patch(
            'employees/{employee_id}', [
            'as' => 'employees.patch',
            'uses' =>'EmployeesController@update'
        ]);
        $app->delete(
            'employees/{employee_id}', [
            'as' => 'employees.destroy',
            'uses' =>'EmployeesController@destroy'
        ]);
        
        $app->get(
            'employees/{employee_id}/orders', [
            'as' => 'employees.orders', 
            'uses' => 'EmployeesController@getOrdersByEmployee'
        ]);
    }
);
//...

Step 3: Definition

Same as Laravel 5.

Step 4: Usage

Same as Laravel 5.

JsonApiController

Whether it's Laravel 5 or Lumen, usage is exactly the same.

Let's create a new controller that extends the JsonApiController provided by this package, as follows:

Lumen users must extends from LumenJsonApiController not JsonApiController.

<?php namespace App\Http\Controllers;

use App\Model\Database\Employees;
use NilPortugues\Laravel5\JsonApi\Controller\JsonApiController;

class EmployeesController extends JsonApiController
{
    /**
     * Return the Eloquent model that will be used 
     * to model the JSON API resources. 
     *
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function getDataModel()
    {
        return new Employees();
    }
}

In case you need to overwrite any default behaviour, the JsonApiController methods are:

//Constructor and defined actions
public function __construct(JsonApiSerializer $serializer);
public function listAction();
public function getAction(Request $request);
public function postAction(Request $request);
public function patchAction(Request $request);
public function putAction(Request $request);
public function deleteAction(Request $request);

//Methods returning callables that access the persistence layer
protected function totalAmountResourceCallable();
protected function listResourceCallable();
protected function findResourceCallable(Request $request);
protected function createResourceCallable();
protected function updateResourceCallable();

//Allows modification of the response object
protected function addHeaders(Response $response);

But wait! We're missing out one action, EmployeesController@getOrdersByEmployee.

As the name suggests, it should list orders, so the behaviour should be the same as the one of ListAction.

If you look inside the listActionyou'll find a code similar to the one below, but we just ajusted the behaviour and used it in our controller to support an additional action:

<?php namespace App\Http\Controllers;

use App\Model\Database\Employees;
use App\Model\Database\Orders;
use NilPortugues\Laravel5\JsonApi\Controller\JsonApiController;

class EmployeesController extends JsonApiController
{
    /**
     * Return the Eloquent model that will be used 
     * to model the JSON API resources. 
     *
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function getDataModel()
    {
        return new Employees();
    }    
    
    /**
     * @param Request $request
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function getOrdersByEmployee(Request $request)
    {       
        $apiRequest = RequestFactory::create();
        $page = $apiRequest->getPage();

        if (!$page->size()) {
            $page->setSize(10); //Default elements per page
        }

        $resource = new ListResource(
            $this->serializer,
            $page,
            $apiRequest->getFields(),
            $apiRequest->getSort(),
            $apiRequest->getIncludedRelationships(),
            $apiRequest->getFilters()
        );
        
        $totalAmount = function() use ($request) {
            $id = (new Orders())->getKeyName();
            return Orders::query()
                ->where('employee_id', '=', $request->employee_id)
                ->get([$id])
                ->count();
        };

        $results = function()  use ($request) {
            return EloquentHelper::paginate(
                $this->serializer,
                Orders::query()
                    ->where('employee_id', '=', $request->employee_id)
            )->get();
        };

        $uri = route('employees.orders', ['employee_id' => $request->employee_id]);
        
        return $resource->get($totalAmount, $results, $uri, Orders::class);
    }
}

And you're ready to go. Yes, it is THAT simple!

Examples: Consuming the API

GET

This is the output for EmployeesController@getAction being consumed from command-line method issuing: curl -X GET "http://localhost:9000/employees/1".

Output:

HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json
{
    "data": {
        "type": "employee",
        "id": "1",
        "attributes": {
            "company": "Northwind Traders",
            "surname": "Freehafer",
            "first_name": "Nancy",
            "email_address": "[email protected]",
            "job_title": "Sales Representative",
            "business_phone": "(123)555-0100",
            "home_phone": "(123)555-0102",
            "mobile_phone": null,
            "fax_number": "(123)555-0103",
            "address": "123 1st Avenue",
            "city": "Seattle",
            "state_province": "WA",
            "zip_postal_code": "99999",
            "country_region": "USA",
            "web_page": "http://northwindtraders.com",
            "notes": null,
            "full_name": "Nancy Freehafer"
        },
        "links": {
            "self": {
                "href": "http://localhost:9000/employees/1"
            },
            "employee_orders": {
                "href": "http://localhost:9000/employees/1/orders"
            }
        },
        "relationships": {
            "latest_orders": [
                {
                    "data": {
                        "type": "order",
                        "id": "71"
                    }
                }
            ]
        }
    },
    "included": [        
        {
            "type": "order",
            "id": "71",
            "attributes": {
                "employee_id": "1",
                "customer_id": "1",
                "order_date": "2006-05-24 00:00:00",
                "shipped_date": null,
                "shipper_id": "3",
                "ship_name": "Anna Bedecs",
                "ship_address": "123 1st Street",
                "ship_city": "Seattle",
                "ship_state_province": "WA",
                "ship_zip_postal_code": "99999",
                "ship_country_region": "USA",
                "shipping_fee": "0.0000",
                "taxes": "0.0000",
                "payment_type": null,
                "paid_date": null,
                "notes": null,
                "tax_rate": "0",
                "tax_status_id": null,
                "status_id": "0"
            },
            "links": {
                "self": {
                    "href": "http://localhost:9000/orders/71"
                },
                "employee": {
                    "href": "http://localhost:9000/employees/1"
                }
            }
        }
    ],
    "links": {
        "employees": {
            "href": "http://localhost:9000/employees"
        },
        "employee_orders": {
            "href": "http://localhost:9000/employees/1/orders"
        }
    },
    "jsonapi": {
        "version": "1.0"
    }
}

POST

POST requires all member attributes to be accepted, even those hidden by the mapper.

For instance, attachments member was hidden, but it is required, so it needs to be passed in with a valid value. On the other hand, full_name member value must not be passed in as an attribute or resource creation will fail.

Passing and id is optional and will be used instead of a server-side generated value if provided.

Sending the following data to the server using POSTto the following URI http://localhost:9000/employees:

{
    "data": {
        "type": "employee",
        "attributes": {
            "company": "NilPortugues.com",
            "surname": "Portuguรฉs",
            "first_name": "Nil",
            "email_address": "[email protected]",
            "job_title": "Web Developer",
            "business_phone": "(123)555-0100",
            "home_phone": "(123)555-0102",
            "mobile_phone": null,
            "fax_number": "(123)555-0103",
            "address": "Plaรงa Catalunya 1",
            "city": "Barcelona",
            "state_province": "Barcelona",
            "zip_postal_code": "08028",
            "country_region": "Spain",
            "web_page": "http://nilportugues.com",
            "notes": null,
            "attachments": null
        }
    }        
}        

Will produce:

HTTP/1.1 201 Created
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json
Location: http://localhost:9000/employees/10

Notice how 201 HTTP Status Code is returned and Location header too. Also attachments is not there anymore, and full_name was displayed.

{
    "data": {
        "type": "employee",
        "id": "10",
        "attributes": {
            "company": "NilPortugues.com",
            "surname": "Portuguรฉs",
            "first_name": "Nil",
            "email_address": "[email protected]",
            "job_title": "Web Developer",
            "business_phone": "(123)555-0100",
            "home_phone": "(123)555-0102",
            "mobile_phone": null,
            "fax_number": "(123)555-0103",
            "address": "Plaรงa Catalunya 1",
            "city": "Barcelona",
            "state_province": "Barcelona",
            "zip_postal_code": "08028",
            "country_region": "Spain",
            "web_page": "http://nilportugues.com",
            "notes": null,
            "full_name": "Nil Portuguรฉs"
        },
        "links": {
            "self": {
                "href": "http://localhost:9000/employees/10"
            },
            "employee_orders": {
                "href": "http://localhost:9000/employees/10/orders"
            }
        }
    },
    "links": {
        "employees": {
            "href": "http://localhost:9000/employees"
        },
        "employee_orders": {
            "href": "http://localhost:9000/employees/10/orders"
        }
    },
    "jsonapi": {
        "version": "1.0"
    }
}

PUT

PUT requires all member attributes to be accepted, just like POST.

For the sake of this example, we'll just send in a new job_title value, and keep everything else exactly the same.

It's important to notice this time we are required to pass in the id, even if it has been passed in by the URI, and of course the id values must match. Otherwise it will fail.

Sending the following data to the server using PUTto the following URI http://localhost:9000/employees/10:

{
  "data": {
    "type": "employee",
    "id": 10,
    "attributes": {
      "company": "NilPortugues.com",
      "surname": "Portuguรฉs",
      "first_name": "Nil",
      "email_address": "[email protected]",
      "job_title": "Full Stack Web Developer",
      "business_phone": "(123)555-0100",
      "home_phone": "(123)555-0102",
      "mobile_phone": null,
      "fax_number": "(123)555-0103",
      "address": "Plaรงa Catalunya 1",
      "city": "Barcelona",
      "state_province": "Barcelona",
      "zip_postal_code": "08028",
      "country_region": "Spain",
      "web_page": "http://nilportugues.com",
      "notes": null,
      "attachments": null
    }
  }
}

Will produce:

HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json
{
    "data": {
        "type": "employee",
        "id": "10",
        "attributes": {
            "company": "NilPortugues.com",
            "surname": "Portuguรฉs",
            "first_name": "Nil",
            "email_address": "[email protected]",
            "job_title": "Full Stack Web Developer",
            "business_phone": "(123)555-0100",
            "home_phone": "(123)555-0102",
            "mobile_phone": null,
            "fax_number": "(123)555-0103",
            "address": "Plaรงa Catalunya 1",
            "city": "Barcelona",
            "state_province": "Barcelona",
            "zip_postal_code": "08028",
            "country_region": "Spain",
            "web_page": "http://nilportugues.com",
            "notes": null,
            "full_name": "Nil Portuguรฉs"
        },
        "links": {
            "self": {
                "href": "http://localhost:9000/employees/10"
            },
            "employee_orders": {
                "href": "http://localhost:9000/employees/10/orders"
            }
        }
    },
    "included": [],
    "links": {
        "employees": {
            "href": "http://localhost:9000/employees"
        },
        "employee_orders": {
            "href": "http://localhost:9000/employees/10/orders"
        }
    },
    "jsonapi": {
        "version": "1.0"
    }
}

PATCH

PATCH allows partial updates, unlike PUT.

We are required to pass in the id member, even if it has been passed in by the URI, and of course the id values must match. Otherwise it will fail.

For instance, sending the following data to the server using the following URI http://localhost:9000/employees/10:

{
  "data": {
    "type": "employee",
    "id": 10,
    "attributes": {
      "email_address": "[email protected]"
    }
  }
}

Will produce:

HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json
{
    "data": {
        "type": "employee",
        "id": "10",
        "attributes": {
            "company": "NilPortugues.com",
            "surname": "Portuguรฉs",
            "first_name": "Nil",
            "email_address": "[email protected]",
            "job_title": "Full Stack Web Developer",
            "business_phone": "(123)555-0100",
            "home_phone": "(123)555-0102",
            "mobile_phone": null,
            "fax_number": "(123)555-0103",
            "address": "Plaรงa Catalunya 1",
            "city": "Barcelona",
            "state_province": "Barcelona",
            "zip_postal_code": "08028",
            "country_region": "Spain",
            "web_page": "http://nilportugues.com",
            "notes": null,
            "full_name": "Nil Portuguรฉs"
        },
        "links": {
            "self": {
                "href": "http://localhost:9000/employees/10"
            },
            "employee_orders": {
                "href": "http://localhost:9000/employees/10/orders"
            }
        }
    },
    "included": [],
    "links": {
        "employees": {
            "href": "http://localhost:9000/employees"
        },
        "employee_orders": {
            "href": "http://localhost:9000/employees/10/orders"
        }
    },
    "jsonapi": {
        "version": "1.0"
    }
}

DELETE

DELETE is the easiest method to use, as it does not require body. Just issue a DELETE to http://localhost:9000/employees/10/ and Employee with id 10 will be gone.

It will produce the following output:

HTTP/1.1 204 No Content
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json

And notice how response will be empty:


GET Query Params: include, fields, sort and page

According to the standard, for GET method, it is possible to:

  • Show only those fields requested using fieldsquery parameter.
    • &fields[resource]=field1,field2

For instance, passing /employees/10?fields[employee]=company,first_name will produce the following output:

{
    "data": {
        "type": "employee",
        "id": "10",
        "attributes": {
            "company": "NilPortugues.com",
            "first_name": "Nil"
        },
        "links": {
            "self": {
                "href": "http://localhost:9000/employees/10"
            },
            "employee_orders": {
                "href": "http://localhost:9000/employees/10/orders"
            }
        }
    },
    "links": {
        "employees": {
            "href": "http://localhost:9000/employees"
        },
        "employee_orders": {
            "href": "http://localhost:9000/employees/10/orders"
        }
    },
    "jsonapi": {
        "version": "1.0"
    }
}
  • Show only those include resources by passing in the relationship between them separated by dot, or just pass in list of resources separated by comma.
    • &include=resource1
    • &include=resource1.resource2,resource2.resource3

For instance, /employees?include=order will only load order type data inside include member, but /employees?include=order.employee will only load those orders related to the employee type.

  • Sort results using sort and passing in the member names of the main resource defined in data[type] member. If it starts with a - order is DESCENDING, otherwise it's ASCENDING.

    • &sort=field1,-field2
    • &sort=-field1,field2

For instance: /employees?sort=surname,-first_name

  • Pagination is also defined to allow doing page pagination, cursor pagination or offset pagination.
    • &page[number]
    • &page[limit]
    • &page[cursor]
    • &page[offset]
    • &page[size]

For instance: /employees?page[number]=1&page[size]=10

POST/PUT/PATCH with Relationships

The JSON API allows resource creation and modification and passing in relationships that will create or alter existing resources too.

Let's say we want to create a new Employee and pass in its first Ordertoo.

This could be done issuing 2 POST to the end-points (one for Employee, one for Order) or pass in the first Order as a relationship with our Employee, for instance:

{
  "data": {
    "type": "employee",
    "attributes": {
        "company": "Northwind Traders",
        "surname": "Giussani",
        "first_name": "Laura",
        "email_address": "[email protected]",
        "job_title": "Sales Coordinator",
        "business_phone": "(123)555-0100",
        "home_phone": "(123)555-0102",
        "mobile_phone": null,
        "fax_number": "(123)555-0103",
        "address": "123 8th Avenue",
        "city": "Redmond",
        "state_province": "WA",
        "zip_postal_code": "99999",
        "country_region": "USA",
        "web_page": "http://northwindtraders.com",
        "notes": "Reads and writes French.",
        "full_name": "Laura Giussani"
    },    
    "relationships": {
      "order": {
        "data": [
          {
            "type": "order",
            "attributes": {
              "customer_id": "28",
              "order_date": "2006-05-11 00:00:00",
              "shipped_date": "2006-05-11 00:00:00",
              "shipper_id": "3",
              "ship_name": "Amritansh Raghav",
              "ship_address": "789 28th Street",
              "ship_city": "Memphis",
              "ship_state_province": "TN",
              "ship_zip_postal_code": "99999",
              "ship_country_region": "USA",
              "shipping_fee": "10.0000",
              "taxes": "0.0000",
              "payment_type": "Check",
              "paid_date": "2006-05-11 00:00:00",
              "notes": null,
              "tax_rate": "0",
              "tax_status_id": null,
              "status_id": "0"
            }
          }
        ]
      }
    }    
  }
}       

Due to the existance of this use case, we'll have to ajust our Controller implementation overwriting some methods provided by the JsonApiController: createResourceCallable, updateResourceCallable and patchResourceCallable.

Here's how it would be done for createResourceCallable.

<?php namespace App\Http\Controllers;

use App\Model\Database\Employees;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use NilPortugues\Api\JsonApi\Server\Errors\Error;
use NilPortugues\Api\JsonApi\Server\Errors\ErrorBag;
use NilPortugues\Laravel5\JsonApi\Controller\JsonApiController;

class EmployeesController extends JsonApiController
{
    /**
     * Now you can actually create Employee and Orders at once.
     * Use transactions - DB::beginTransaction() for data integrity!
     *
     * @return callable
     */
    protected function createResourceCallable()
    {
        $createOrderResource = function (Model $model, array $data) {
            if (!empty($data['relationships']['order']['data'])) {
                $orderData = $data['relationships']['order']['data'];

                if (!empty($orderData['type'])) {
                    $orderData = [$orderData];
                }

                foreach ($orderData as $order) {
                    $attributes = array_merge($order['attributes'], ['employee_id' => $model->getKey()]);
                    Orders::create($attributes);
                }
            }
        };

        return function (array $data, array $values, ErrorBag $errorBag) use ($createOrderResource) {

            $attributes = [];
            foreach ($values as $name => $value) {
                $attributes[$name] = $value;
            }

            if (!empty($data['id'])) {
                $attributes[$this->getDataModel()->getKeyName()] = $values['id'];
            }

            DB::beginTransaction();
            try {
                $model = $this->getDataModel()->create($attributes);
                $createOrderResource($model, $data);
                DB::commit();
                return $model;
                
            } catch(\Exception $e) {
                DB::rollback();
                $errorBag[] = new Error('creation_error', 'Resource could not be created');
                throw $e;
            }
        };
    }

}

It is important, in order to use Transactions, do define in Eloquent models the $fillable values.

Here's how Employees and Orders look like with $fillable defined.

Employees (Eloquent Model) with $fillable

<?php namespace App\Model\Database;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Validation\ValidatesRequests;

class Employees extends Model
{
    public $timestamps = false;
    protected $table = 'employees';    
    protected $primaryKey = 'id';
    protected $appends = ['full_name'];
    
    /**
     * @var array
     */
    protected $fillable = [
        'company',
        'last_name',
        'first_name',
        'email_address',
        'job_title',
        'business_phone',
        'home_phone',
        'mobile_phone',
        'fax_number',
        'address',
        'city',
        'state_province',
        'zip_postal_code',
        'country_region',
        'web_page',
        'notes',
        'attachments',
    ];

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function latestOrders()
    {
        return $this->hasMany(Orders::class, 'employee_id')->limit(10);
    }

    /**
     * @return string
     */
    public function getFullNameAttribute()
    {
        return $this->first_name.' '.$this->last_name;
    }
}

Orders (Eloquent Model) with $fillable

<?php namespace App\Model\Database;

use Illuminate\Database\Eloquent\Model;

class Orders extends Model
{   
    public $timestamps = false;
    protected $table = 'orders';
    protected $primaryKey = 'id';
    
    /**
     * @var array
     */
    protected $fillable = [
        'employee_id',
        'customer_id',
        'order_date',
        'shipped_date',
        'shipper_id',
        'ship_name',
        'ship_address',
        'ship_city',
        'ship_state_province',
        'ship_zip_postal_code',
        'ship_country_region',
        'shipping_fee',
        'taxes',
        'payment_type',
        'paid_date',
        'notes',
        'tax_rate',
        'tax_status_id',
        'status_id',
    ];
    
    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function employee()
    {
        return $this->belongsTo(Employees::class, 'employee_id');
    }
}

Custom Response Headers

Adding custom response headers can be done for multiple reasons: versioning, setting expire headers, caching, setting private or public the served content...

In order to do this, it's as simple as overwriting the JsonApiController addHeaders method. For instance, let's use the EmployeeController as an example:

<?php namespace App\Http\Controllers;

use App\Model\Database\Employees;
use NilPortugues\Laravel5\JsonApi\Controller\JsonApiController;
use Symfony\Component\HttpFoundation\Response;

class EmployeesController extends JsonApiController
{
    //All your supported methods...
    
    /**
     * @param Response $response
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function addHeaders(Response $response) {
        $response->headers->set('X-API-Version', '1.0');
        $response->setPublic();
        $response->setMaxAge(60);
        $response->setSharedMaxAge(60);

        return $response;
    }
}    

Now all supported actions will include the added custom headers.

Common Errors and Solutions

"Undefined index: @type"

This usually happens because you did not write the namespace of your Mapping in config/jsonapi.php. Double check, if missing, add it and refresh the resource. It should be gone!


Contribute

Contributions to the package are always welcome!

Support

Get in touch with me using one of the following means:

Authors

License

The code base is licensed under the MIT license.

laravel5-jsonapi's People

Contributors

alexjoffroy avatar jeremymlane avatar nilportugues avatar oskard avatar remo avatar robindfuller avatar shermaza avatar yurykabanov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel5-jsonapi's Issues

Unable to get serializer in Controller working

I have extended my controller to LumenJsonApiController, but I'm not getting the Serializer (NULL). All output from the controller is untouched json when doing return $results or similar from my methods. What am I doing wrong?

better error handling

I'm trying to get this package working but so far I only get a blank response from JsonApiSerializer->serialize. I believe my mappings should be okay, but I'll start debugging this.

It would be great if there was a better error handling and not just a blank return value though. Would certainly make it easier and more fun for those who haven't worked with this package before.

Update documentation

Add the following:

  • Class Mapping
  • Full Eloquent Example.
  • Warning on fetching related eloquent models.

Incompatibility with group prefix.

Using this routes.php:

Route::group(['middleware' => ['web'], 'prefix' => 'api'], function () {
Route::resource('/schedule', 'ScheduleController');
});

This exception is thrown: Exception in Laravel52Provider.php line 51: Provided route name does not exist

This occurs because the Laravel52Provider ignores the prefix information:
" if ($routerObject->getName() === $value['name']) { "
" if ('api.resources.show' === 'resources.show') {"

The fix that I suggest is to concatenate the prefix with "$value['name']" and return correctly the full path.

"
$prefix = str_replace('/', '.', $routerObject->getPrefix());
if ($routerObject->getName() === $prefix . $value['name']) {
"

I will submit a pull request ASAP.

Simplified models

Instead of returning models with their relationships, I would like to return them with their related properties instead. For instance, let's say I have this database structure:

users
- id
- name
- group_id

user_groups
- id
- name

user_notes
- user_id
- text

I would want to return this as the user attributes:

       {
        "id": 1,
        "name": "Some name",
        "group": "Some group name",
        "user_notes": {
              "First note text",
              "Second note text"
         }
       },

How can I approach this?

None default namespace application

Fatal error: Class 'App\Http\Controllers\Controller' not found in vendor/nilportugues/laravel5-json-api/src/NilPortugues/Laravel5/JsonApi/Controller/JsonApiController.php on line 33

Composer.json

"autoload": {
        "psr-4": {
            "MyApp\\V3core\\": "app/"
        },
        "classmap": [
            "database/"
        ]
}

As our namespace is not App

maybe the solution would be using Laravel\Lumen\Routing\Controller as App\Controller is an extension of it.

Error running `composer require nilportugues/laravel5-json-api`

I'm using Laravel 5.2.

When I run composer require nilportugues/laravel5-json-api, I'm having error:

composer require nilportugues/laravel5-json-api
Using version ^2.1 for nilportugues/laravel5-json-api
./composer.json has been updated
> php artisan clear-compiled
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.9
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.8
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.7
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.6
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.5
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.4
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.3
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.2
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.1
    - Conclusion: remove symfony/http-foundation v3.0.1
    - Conclusion: don't install symfony/http-foundation v3.0.1
    - nilportugues/json-api 2.1.0 requires symfony/http-foundation ^2.7 -> satisfiable by symfony/http-foundation[v2.7.0, v2.7.1, v2.7.2, v2.7.3, v2.7.4, v2.7.5, v2.7.6, v2.7.7, v2.7.8, v2.7.9, v2.8.0, v2.8.1, v2.8.2].
    - nilportugues/json-api 2.1.1 requires symfony/http-foundation ^2.7 -> satisfiable by symfony/http-foundation[v2.7.0, v2.7.1, v2.7.2, v2.7.3, v2.7.4, v2.7.5, v2.7.6, v2.7.7, v2.7.8, v2.7.9, v2.8.0, v2.8.1, v2.8.2].
    - nilportugues/json-api 2.1.2 requires symfony/http-foundation ^2.7 -> satisfiable by symfony/http-foundation[v2.7.0, v2.7.1, v2.7.2, v2.7.3, v2.7.4, v2.7.5, v2.7.6, v2.7.7, v2.7.8, v2.7.9, v2.8.0, v2.8.1, v2.8.2].
    - Can only install one of: symfony/http-foundation[v2.8.0, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.8.1, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.8.2, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.0, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.1, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.2, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.3, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.4, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.5, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.6, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.7, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.8, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.9, v3.0.1].
    - Conclusion: don't install symfony/console v3.0.1|install nilportugues/json-api 2.1.0|install nilportugues/json-api 2.1.1|install nilportugues/json-api 2.1.2
    - Installation request for symfony/http-foundation == 3.0.1.0 -> satisfiable by symfony/http-foundation[v3.0.1].
    - Installation request for nilportugues/laravel5-json-api ^2.1 -> satisfiable by nilportugues/laravel5-json-api[2.1.0, 2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9].
    - Conclusion: remove symfony/console v3.0.1|install nilportugues/json-api 2.1.0|install nilportugues/json-api 2.1.1|install nilportugues/json-api 2.1.2
    - nilportugues/laravel5-json-api 2.1.0 requires nilportugues/json-api 2.1.* -> satisfiable by nilportugues/json-api[2.1.0, 2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5].
    - nilportugues/json-api 2.1.3 requires nilportugues/php_backslasher ^0.2.1 -> satisfiable by nilportugues/php_backslasher[0.2.1, 0.2.2].
    - nilportugues/json-api 2.1.4 requires nilportugues/php_backslasher ^0.2.1 -> satisfiable by nilportugues/php_backslasher[0.2.1, 0.2.2].
    - nilportugues/json-api 2.1.5 requires nilportugues/php_backslasher ^0.2.1 -> satisfiable by nilportugues/php_backslasher[0.2.1, 0.2.2].
    - nilportugues/php_backslasher 0.2.1 requires symfony/console ~2.2 -> satisfiable by symfony/console[v2.2.0, v2.2.1, v2.2.10, v2.2.11, v2.2.2, v2.2.3, v2.2.4, v2.2.5, v2.2.6, v2.2.7, v2.2.8, v2.2.9, v2.3.0, v2.3.1, v2.3.10, v2.3.11, v2.3.12, v2.3.13, v2.3.14, v2.3.15, v2.3.16, v2.3.17, v2.3.18, v2.3.19, v2.3.2, v2.3.20, v2.3.21, v2.3.22, v2.3.23, v2.3.24, v2.3.25, v2.3.26, v2.3.27, v2.3.28, v2.3.29, v2.3.3, v2.3.30, v2.3.31, v2.3.32, v2.3.33, v2.3.34, v2.3.35, v2.3.36, v2.3.37, v2.3.4, v2.3.5, v2.3.6, v2.3.7, v2.3.8, v2.3.9, v2.4.0, v2.4.1, v2.4.10, v2.4.2, v2.4.3, v2.4.4, v2.4.5, v2.4.6, v2.4.7, v2.4.8, v2.4.9, v2.5.0, v2.5.1, v2.5.10, v2.5.11, v2.5.12, v2.5.2, v2.5.3, v2.5.4, v2.5.5, v2.5.6, v2.5.7, v2.5.8, v2.5.9, v2.6.0, v2.6.1, v2.6.10, v2.6.11, v2.6.12, v2.6.13, v2.6.2, v2.6.3, v2.6.4, v2.6.5, v2.6.6, v2.6.7, v2.6.8, v2.6.9, v2.7.0, v2.7.1, v2.7.2, v2.7.3, v2.7.4, v2.7.5, v2.7.6, v2.7.7, v2.7.8, v2.7.9, v2.8.0, v2.8.1, v2.8.2].
    - nilportugues/php_backslasher 0.2.2 requires symfony/console ~2.2 -> satisfiable by symfony/console[v2.2.0, v2.2.1, v2.2.10, v2.2.11, v2.2.2, v2.2.3, v2.2.4, v2.2.5, v2.2.6, v2.2.7, v2.2.8, v2.2.9, v2.3.0, v2.3.1, v2.3.10, v2.3.11, v2.3.12, v2.3.13, v2.3.14, v2.3.15, v2.3.16, v2.3.17, v2.3.18, v2.3.19, v2.3.2, v2.3.20, v2.3.21, v2.3.22, v2.3.23, v2.3.24, v2.3.25, v2.3.26, v2.3.27, v2.3.28, v2.3.29, v2.3.3, v2.3.30, v2.3.31, v2.3.32, v2.3.33, v2.3.34, v2.3.35, v2.3.36, v2.3.37, v2.3.4, v2.3.5, v2.3.6, v2.3.7, v2.3.8, v2.3.9, v2.4.0, v2.4.1, v2.4.10, v2.4.2, v2.4.3, v2.4.4, v2.4.5, v2.4.6, v2.4.7, v2.4.8, v2.4.9, v2.5.0, v2.5.1, v2.5.10, v2.5.11, v2.5.12, v2.5.2, v2.5.3, v2.5.4, v2.5.5, v2.5.6, v2.5.7, v2.5.8, v2.5.9, v2.6.0, v2.6.1, v2.6.10, v2.6.11, v2.6.12, v2.6.13, v2.6.2, v2.6.3, v2.6.4, v2.6.5, v2.6.6, v2.6.7, v2.6.8, v2.6.9, v2.7.0, v2.7.1, v2.7.2, v2.7.3, v2.7.4, v2.7.5, v2.7.6, v2.7.7, v2.7.8, v2.7.9, v2.8.0, v2.8.1, v2.8.2].
    - Can only install one of: symfony/console[v2.3.10, v3.0.1].
    - Can only install one of: symfony/console[v2.3.11, v3.0.1].
    - Can only install one of: symfony/console[v2.3.12, v3.0.1].
    - Can only install one of: symfony/console[v2.3.13, v3.0.1].
    - Can only install one of: symfony/console[v2.3.14, v3.0.1].
    - Can only install one of: symfony/console[v2.3.15, v3.0.1].
    - Can only install one of: symfony/console[v2.3.16, v3.0.1].
    - Can only install one of: symfony/console[v2.3.17, v3.0.1].
    - Can only install one of: symfony/console[v2.3.18, v3.0.1].
    - Can only install one of: symfony/console[v2.3.19, v3.0.1].
    - Can only install one of: symfony/console[v2.3.20, v3.0.1].
    - Can only install one of: symfony/console[v2.3.21, v3.0.1].
    - Can only install one of: symfony/console[v2.3.22, v3.0.1].
    - Can only install one of: symfony/console[v2.3.23, v3.0.1].
    - Can only install one of: symfony/console[v2.3.24, v3.0.1].
    - Can only install one of: symfony/console[v2.3.25, v3.0.1].
    - Can only install one of: symfony/console[v2.3.26, v3.0.1].
    - Can only install one of: symfony/console[v2.3.27, v3.0.1].
    - Can only install one of: symfony/console[v2.3.28, v3.0.1].
    - Can only install one of: symfony/console[v2.3.29, v3.0.1].
    - Can only install one of: symfony/console[v2.3.30, v3.0.1].
    - Can only install one of: symfony/console[v2.3.31, v3.0.1].
    - Can only install one of: symfony/console[v2.3.32, v3.0.1].
    - Can only install one of: symfony/console[v2.3.33, v3.0.1].
    - Can only install one of: symfony/console[v2.3.34, v3.0.1].
    - Can only install one of: symfony/console[v2.3.35, v3.0.1].
    - Can only install one of: symfony/console[v2.3.36, v3.0.1].
    - Can only install one of: symfony/console[v2.3.37, v3.0.1].
    - Can only install one of: symfony/console[v2.4.10, v3.0.1].
    - Can only install one of: symfony/console[v2.4.2, v3.0.1].
    - Can only install one of: symfony/console[v2.4.3, v3.0.1].
    - Can only install one of: symfony/console[v2.4.4, v3.0.1].
    - Can only install one of: symfony/console[v2.4.5, v3.0.1].
    - Can only install one of: symfony/console[v2.4.6, v3.0.1].
    - Can only install one of: symfony/console[v2.4.7, v3.0.1].
    - Can only install one of: symfony/console[v2.4.8, v3.0.1].
    - Can only install one of: symfony/console[v2.4.9, v3.0.1].
    - Can only install one of: symfony/console[v2.5.0, v3.0.1].
    - Can only install one of: symfony/console[v2.5.1, v3.0.1].
    - Can only install one of: symfony/console[v2.5.10, v3.0.1].
    - Can only install one of: symfony/console[v2.5.11, v3.0.1].
    - Can only install one of: symfony/console[v2.5.12, v3.0.1].
    - Can only install one of: symfony/console[v2.5.2, v3.0.1].
    - Can only install one of: symfony/console[v2.5.3, v3.0.1].
    - Can only install one of: symfony/console[v2.5.4, v3.0.1].
    - Can only install one of: symfony/console[v2.5.5, v3.0.1].
    - Can only install one of: symfony/console[v2.5.6, v3.0.1].
    - Can only install one of: symfony/console[v2.5.7, v3.0.1].
    - Can only install one of: symfony/console[v2.5.8, v3.0.1].
    - Can only install one of: symfony/console[v2.5.9, v3.0.1].
    - Can only install one of: symfony/console[v2.6.0, v3.0.1].
    - Can only install one of: symfony/console[v2.6.1, v3.0.1].
    - Can only install one of: symfony/console[v2.6.10, v3.0.1].
    - Can only install one of: symfony/console[v2.6.11, v3.0.1].
    - Can only install one of: symfony/console[v2.6.12, v3.0.1].
    - Can only install one of: symfony/console[v2.6.13, v3.0.1].
    - Can only install one of: symfony/console[v2.6.2, v3.0.1].
    - Can only install one of: symfony/console[v2.6.3, v3.0.1].
    - Can only install one of: symfony/console[v2.6.4, v3.0.1].
    - Can only install one of: symfony/console[v2.6.5, v3.0.1].
    - Can only install one of: symfony/console[v2.6.6, v3.0.1].
    - Can only install one of: symfony/console[v2.6.7, v3.0.1].
    - Can only install one of: symfony/console[v2.6.8, v3.0.1].
    - Can only install one of: symfony/console[v2.6.9, v3.0.1].
    - Can only install one of: symfony/console[v2.7.0, v3.0.1].
    - Can only install one of: symfony/console[v2.7.1, v3.0.1].
    - Can only install one of: symfony/console[v2.7.2, v3.0.1].
    - Can only install one of: symfony/console[v2.7.3, v3.0.1].
    - Can only install one of: symfony/console[v2.7.4, v3.0.1].
    - Can only install one of: symfony/console[v2.7.5, v3.0.1].
    - Can only install one of: symfony/console[v2.7.6, v3.0.1].
    - Can only install one of: symfony/console[v2.7.7, v3.0.1].
    - Can only install one of: symfony/console[v2.7.8, v3.0.1].
    - Can only install one of: symfony/console[v2.7.9, v3.0.1].
    - Can only install one of: symfony/console[v2.8.0, v3.0.1].
    - Can only install one of: symfony/console[v2.8.1, v3.0.1].
    - Can only install one of: symfony/console[v2.8.2, v3.0.1].
    - Can only install one of: symfony/console[v2.2.0, v3.0.1].
    - Can only install one of: symfony/console[v2.2.1, v3.0.1].
    - Can only install one of: symfony/console[v2.2.10, v3.0.1].
    - Can only install one of: symfony/console[v2.2.11, v3.0.1].
    - Can only install one of: symfony/console[v2.2.2, v3.0.1].
    - Can only install one of: symfony/console[v2.2.3, v3.0.1].
    - Can only install one of: symfony/console[v2.2.4, v3.0.1].
    - Can only install one of: symfony/console[v2.2.5, v3.0.1].
    - Can only install one of: symfony/console[v2.2.6, v3.0.1].
    - Can only install one of: symfony/console[v2.2.7, v3.0.1].
    - Can only install one of: symfony/console[v2.2.8, v3.0.1].
    - Can only install one of: symfony/console[v2.2.9, v3.0.1].
    - Can only install one of: symfony/console[v2.3.0, v3.0.1].
    - Can only install one of: symfony/console[v2.3.1, v3.0.1].
    - Can only install one of: symfony/console[v2.3.2, v3.0.1].
    - Can only install one of: symfony/console[v2.3.3, v3.0.1].
    - Can only install one of: symfony/console[v2.3.4, v3.0.1].
    - Can only install one of: symfony/console[v2.3.5, v3.0.1].
    - Can only install one of: symfony/console[v2.3.6, v3.0.1].
    - Can only install one of: symfony/console[v2.3.7, v3.0.1].
    - Can only install one of: symfony/console[v2.3.8, v3.0.1].
    - Can only install one of: symfony/console[v2.3.9, v3.0.1].
    - Can only install one of: symfony/console[v2.4.0, v3.0.1].
    - Can only install one of: symfony/console[v2.4.1, v3.0.1].
    - Installation request for symfony/console == 3.0.1.0 -> satisfiable by symfony/console[v3.0.1].

Installation failed, reverting ./composer.json to its original content.

Debug mode

It would be useful to alter just a couple of behaviours if the app debug mode is set to true, as it would be during development:

  • Don't cache the mappings.
  • Return the full $e instead of a 400 error in NilPortugues\Api\JsonApi\Server\Actions\GetResource->getErrorResponse($e)

I simply used:

        if (config('app.debug')) {
            dd($e);
        }

How to use Request objects

In the documentations it says Request Objects provides you access to all the interactions expected in a JSON API
but i'm not sure how to implement it. I will be great if you can provide some guide for using

&page[limit]
&filter[resource]=field1

Status Code

Is it possible to control the HTTP status code that is returned ?

Testing URI problem.

When I try to test with phpunit I get the following errors when I try to access the api:

A request to [http://localhost/api/tags/1] failed. Received status code [500].

/Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithPages.php:168
/Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithPages.php:64
/Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithPages.php:45
/Users/ven7ura/Code/qp/tests/TagsTest.php:35
/Users/ven7ura/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php:147
/Users/ven7ura/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php:99

Caused by
exception 'InvalidArgumentException' with message 'The source URI string appears to be malformed' in /Users/ven7ura/Code/qp/vendor/zendframework/zend-diactoros/src/Uri.php:440
Stack trace:
#0 /Users/ven7ura/Code/qp/vendor/zendframework/zend-diactoros/src/Uri.php(104): Zend\Diactoros\Uri->parseUri('http://:/')
#1 /Users/ven7ura/Code/qp/vendor/zendframework/zend-diactoros/src/RequestTrait.php(80): Zend\Diactoros\Uri->__construct('http://:/')
#2 /Users/ven7ura/Code/qp/vendor/zendframework/zend-diactoros/src/ServerRequest.php(93): Zend\Diactoros\ServerRequest->initialize('http://:/', 'GET', Object(Zend\Diactoros\Stream), Array)
#3 /Users/ven7ura/Code/qp/vendor/symfony/psr-http-message-bridge/Factory/DiactorosFactory.php(62): Zend\Diactoros\ServerRequest->__construct(Array, Array, 'http://:/', 'GET', Object(Zend\Diactoros\Stream), Array)
#4 /Users/ven7ura/Code/qp/vendor/nilportugues/json-api/src/Http/Request/Request.php(32): Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory->createRequest(Object(Symfony\Component\HttpFoundation\Request))
#5 /Users/ven7ura/Code/qp/vendor/nilportugues/json-api/src/Http/Factory/RequestFactory.php(34): NilPortugues\Api\JsonApi\Http\Request\Request->__construct()
#6 /Users/ven7ura/Code/qp/vendor/nilportugues/laravel5-json-api/src/NilPortugues/Laravel5/JsonApi/Controller/JsonApiTrait.php(141): NilPortugues\Api\JsonApi\Http\Factory\RequestFactory::create()
#7 [internal function]: NilPortugues\Laravel5\JsonApi\Controller\JsonApiController->show('1')
#8 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(76): call_user_func_array(Array, Array)
#9 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(146): Illuminate\Routing\Controller->callAction('show', Array)
#10 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(94): Illuminate\Routing\ControllerDispatcher->call(Object(App\Http\Controllers\TagsController), Object(Illuminate\Routing\Route), 'show')
#11 [internal function]: Illuminate\Routing\ControllerDispatcher->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#12 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(52): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#13 [internal function]: Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#14 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#15 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(96): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#16 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(54): Illuminate\Routing\ControllerDispatcher->callWithinStack(Object(App\Http\Controllers\TagsController), Object(Illuminate\Routing\Route), Object(Illuminate\Http\Request), 'show')
#17 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Route.php(174): Illuminate\Routing\ControllerDispatcher->dispatch(Object(Illuminate\Routing\Route), Object(Illuminate\Http\Request), 'App\\Http\\C...', 'show')
#18 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Route.php(140): Illuminate\Routing\Route->runController(Object(Illuminate\Http\Request))
#19 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Router.php(703): Illuminate\Routing\Route->run(Object(Illuminate\Http\Request))
#20 [internal function]: Illuminate\Routing\Router->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#21 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(52): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#22 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(52): Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#23 [internal function]: Illuminate\Routing\Middleware\ThrottleRequests->handle(Object(Illuminate\Http\Request), Object(Closure), '60', '1')
#24 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(124): call_user_func_array(Array, Array)
#25 [internal function]: Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#26 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(32): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#27 [internal function]: Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#28 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#29 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Router.php(705): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#30 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Router.php(678): Illuminate\Routing\Router->runRouteWithinStack(Object(Illuminate\Routing\Route), Object(Illuminate\Http\Request))
#31 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Router.php(654): Illuminate\Routing\Router->dispatchToRoute(Object(Illuminate\Http\Request))
#32 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(246): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request))
#33 [internal function]: Illuminate\Foundation\Http\Kernel->Illuminate\Foundation\Http\{closure}(Object(Illuminate\Http\Request))
#34 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(52): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#35 /Users/ven7ura/Code/qp/vendor/itsgoingd/clockwork/Clockwork/Support/Laravel/ClockworkMiddleware.php(41): Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#36 [internal function]: Clockwork\Support\Laravel\ClockworkMiddleware->handle(Object(Illuminate\Http\Request), Object(Closure))
#37 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(124): call_user_func_array(Array, Array)
#38 [internal function]: Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#39 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(32): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#40 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php(44): Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#41 [internal function]: Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode->handle(Object(Illuminate\Http\Request), Object(Closure))
#42 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(124): call_user_func_array(Array, Array)
#43 [internal function]: Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#44 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(32): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#45 [internal function]: Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#46 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): call_user_func(Object(Closure), Object(Illuminate\Http\Request))
#47 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(132): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#48 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(99): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#49 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php(462): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#50 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithPages.php(62): Illuminate\Foundation\Testing\TestCase->call('GET', 'http://localhos...', Array, Array, Array)
#51 /Users/ven7ura/Code/qp/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/InteractsWithPages.php(45): Illuminate\Foundation\Testing\TestCase->makeRequest('GET', '/api/tags/1')
#52 /Users/ven7ura/Code/qp/tests/TagsTest.php(35): Illuminate\Foundation\Testing\TestCase->visit('/api/tags/1')
#53 [internal function]: TagsTest->testGetATag()
#54 /Users/ven7ura/Code/qp/vendor/phpunit/phpunit/src/Framework/TestCase.php(909): ReflectionMethod->invokeArgs(Object(TagsTest), Array)
#55 /Users/ven7ura/Code/qp/vendor/phpunit/phpunit/src/Framework/TestCase.php(768): PHPUnit_Framework_TestCase->runTest()
#56 /Users/ven7ura/Code/qp/vendor/phpunit/phpunit/src/Framework/TestResult.php(612): PHPUnit_Framework_TestCase->runBare()
#57 /Users/ven7ura/Code/qp/vendor/phpunit/phpunit/src/Framework/TestCase.php(724): PHPUnit_Framework_TestResult->run(Object(TagsTest))
#58 /Users/ven7ura/Code/qp/vendor/phpunit/phpunit/src/Framework/TestSuite.php(747): PHPUnit_Framework_TestCase->run(Object(PHPUnit_Framework_TestResult))
#59 /Users/ven7ura/Code/qp/vendor/phpunit/phpunit/src/Framework/TestSuite.php(747): PHPUnit_Framework_TestSuite->run(Object(PHPUnit_Framework_TestResult))
#60 /Users/ven7ura/Code/qp/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(440): PHPUnit_Framework_TestSuite->run(Object(PHPUnit_Framework_TestResult))
#61 /Users/ven7ura/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php(147): PHPUnit_TextUI_TestRunner->doRun(Object(PHPUnit_Framework_TestSuite), Array)
#62 /Users/ven7ura/.composer/vendor/phpunit/phpunit/src/TextUI/Command.php(99): PHPUnit_TextUI_Command->run(Array, true)
#63 /Users/ven7ura/.composer/vendor/phpunit/phpunit/phpunit(36): PHPUnit_TextUI_Command::main()
#64 {main}

If I try to access with the browser or postman, I correctly get the data. So the problem is only when in a testing environment.

Any help will be appreciated.

Error thrown with example code using Laravel 5.2

I've implemented the code provided in the readme, and it fails under 5.2:

Illuminate \ Routing \ Exceptions \ UrlGenerationException
Missing required parameters for [Route: employees.show] [URI: employees/{employees}].

https://github.com/nilportugues/laravel5-jsonapi/blob/master/src/NilPortugues/Laravel5/JsonApi/Laravel5JsonApiServiceProvider.php#L140

    private static function calculateRoute(array $value)
    {
        $route = urldecode(route($value['name']));
...
    }

Should return a route, but as of this commit:
laravel/framework@cef79d8
Laravel 5.2 checks the route for parameters and won't return the route if the required parameters are missing.

        if (preg_match('/\{.*?\}/', $uri)) {
            throw UrlGenerationException::forMissingParameters($route);
        }

This looks like it might take more refactoring than I'm comfortable trying in a pull request.

Api ignores base route in href

Hello,

I have a setup with an alias (in my case: http://localhost/testproject) to my laravel public directory.

Here is the example of the readme.md (omployee, orders example). This generates wrong links in href, like:

"links": {
        "self": {
          "href": "http://localhost/employees/1"
        },
        "employee_orders": {
          "href": "http://localhost/employees/1/orders"
        }
      }

note that the "testproject" is missing in the link.
Interesting is, that the pagination seems to generate correct links, therefore i suspect that my setup is correct and there is a bug in the package.
The response for the pagination:

"links": {
    "self": {
      "href": "http://localhost/testproject/employees?page[number]=1&page[size]=10"
    },
    "first": {
      "href": "http://localhost/testproject/employees?page[number]=1&page[size]=10"
    },
    "last": {
      "href": "http://localhost/testproject/employees?page[number]=1&page[size]=10"
    }
  }

Thank you very much

I got ErrorException in MappingFactory.php line 112.

I'm using Laravel 5.2 and I already created a model, a transformer, a controller but I got an error like this which I can't investigate what is the cause of the error.

I have 3 models. Organization, Course and User. Organization has many courses and users. Courses and users are using many to many relationship.

ErrorException in MappingFactory.php line 112:
Argument 1 passed to NilPortugues\Api\Mapping\MappingFactory::fromArray() must be an instance of array, string given
in MappingFactory.php line 112
at HandleExceptions->handleError('4096', 'Argument 1 passed to NilPortugues\Api\Mapping\MappingFactory::fromArray() must be an instance of array, string given', '/web/laravel/vendor/nilportugues/api-transformer/src/Mapping/MappingFactory.php', '112', array('mappedClass' => 'App\Transformer\UsersTransformer'), array(array('file' => '/web/laravel/vendor/nilportugues/api-transformer/src/Mapping/MappingFactory.php', 'line' => '112'), array('file' => '/web/laravel/vendor/nilportugues/laravel5-json-api/src/NilPortugues/Laravel5/JsonApi/Mapper/Mapper.php', 'line' => '24', 'function' => 'fromArray', 'class' => 'NilPortugues\Api\Mapping\MappingFactory', 'type' => '::', 'args' => array('App\Transformer\UsersTransformer')), array('file' => '/web/laravel/vendor/nilportugues/api-transformer/src/Mapping/Mapper.php', 'line' => '36', 'function' => 'buildMapping', 'class' => 'NilPortugues\Laravel5\JsonApi\Mapper\Mapper', 'type' => '->', 'args' => array('App\Transformer\UsersTransformer')), array('file' => '/web/laravel/vendor/nilportugues/laravel5-json-api/src/NilPortugues/Laravel5/JsonApi/Providers/Laravel51Provider.php', 'line' => '29', 'function' => '__construct', 'class' => 'NilPortugues\Api\Mapping\Mapper', 'type' => '->', 'args' => array(array('App\Transformer\UsersTransformer', 'App\Transformer\OrganizationsTransformer', 'App\Transformer\CoursesTransformer'))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Cache/Repository.php', 'line' => '325', 'function' => 'Closure$NilPortugues\Laravel5\JsonApi\Providers\Laravel51Provider::provider', 'args' => array()), array('function' => 'rememberForever', 'class' => 'Illuminate\Cache\Repository', 'type' => '->', 'args' => array('jsonapi.mapping', object(Laravel51Provider::provider;850661141))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php', 'line' => '296', 'function' => 'call_user_func_array', 'args' => array(array(object(Repository), 'rememberForever'), array('jsonapi.mapping', object(Laravel51Provider::provider;850661141)))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php', 'line' => '221', 'function' => '__call', 'class' => 'Illuminate\Cache\CacheManager', 'type' => '->', 'args' => array('rememberForever', array('jsonapi.mapping', object(Laravel51Provider::provider;850661141)))), array('file' => '/web/laravel/vendor/nilportugues/laravel5-json-api/src/NilPortugues/Laravel5/JsonApi/Providers/Laravel51Provider.php', 'line' => '30', 'function' => '__callStatic', 'class' => 'Illuminate\Support\Facades\Facade', 'type' => '::', 'args' => array('rememberForever', array('jsonapi.mapping', object(Laravel51Provider::provider;850661141)))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php', 'line' => '735', 'function' => 'Closure$NilPortugues\Laravel5\JsonApi\Providers\Laravel51Provider::provider#2', 'args' => array(object(Application), array())), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php', 'line' => '633', 'function' => 'build', 'class' => 'Illuminate\Container\Container', 'type' => '->', 'args' => array(object(Laravel51Provider::provider#2;850661141), array())), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php', 'line' => '674', 'function' => 'make', 'class' => 'Illuminate\Container\Container', 'type' => '->', 'args' => array('NilPortugues\Laravel5\JsonApi\JsonApiSerializer', array())), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php', 'line' => '853', 'function' => 'make', 'class' => 'Illuminate\Foundation\Application', 'type' => '->', 'args' => array('NilPortugues\Laravel5\JsonApi\JsonApiSerializer')), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php', 'line' => '808', 'function' => 'resolveClass', 'class' => 'Illuminate\Container\Container', 'type' => '->', 'args' => array(object(ReflectionParameter))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php', 'line' => '779', 'function' => 'getDependencies', 'class' => 'Illuminate\Container\Container', 'type' => '->', 'args' => array(array(object(ReflectionParameter)), array())), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Container/Container.php', 'line' => '633', 'function' => 'build', 'class' => 'Illuminate\Container\Container', 'type' => '->', 'args' => array('App\Http\Controllers\OrganizationController', array())), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php', 'line' => '674', 'function' => 'make', 'class' => 'Illuminate\Container\Container', 'type' => '->', 'args' => array('App\Http\Controllers\OrganizationController', array())), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php', 'line' => '67', 'function' => 'make', 'class' => 'Illuminate\Foundation\Application', 'type' => '->', 'args' => array('App\Http\Controllers\OrganizationController')), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php', 'line' => '52', 'function' => 'makeController', 'class' => 'Illuminate\Routing\ControllerDispatcher', 'type' => '->', 'args' => array('App\Http\Controllers\OrganizationController')), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Route.php', 'line' => '174', 'function' => 'dispatch', 'class' => 'Illuminate\Routing\ControllerDispatcher', 'type' => '->', 'args' => array(object(Route), object(Request), 'App\Http\Controllers\OrganizationController', 'index')), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Route.php', 'line' => '140', 'function' => 'runController', 'class' => 'Illuminate\Routing\Route', 'type' => '->', 'args' => array(object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php', 'line' => '704', 'function' => 'run', 'class' => 'Illuminate\Routing\Route', 'type' => '->', 'args' => array(object(Request))), array('function' => 'Closure$Illuminate\Routing\Router::runRouteWithinStack', 'args' => array(object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php', 'line' => '52', 'function' => 'call_user_func', 'args' => array(object(Router::runRouteWithinStack;124210213), object(Request))), array('file' => '/web/laravel/app/Http/Middleware/TokenAuth.php', 'line' => '50', 'function' => 'Closure$Illuminate\Routing\Pipeline::getInitialSlice', 'args' => array(object(Request))), array('function' => 'handle', 'class' => 'App\Http\Middleware\TokenAuth', 'type' => '->', 'args' => array(object(Request), object(Pipeline::getInitialSlice;253528204))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php', 'line' => '124', 'function' => 'call_user_func_array', 'args' => array(array(object(TokenAuth), 'handle'), array(object(Request), object(Pipeline::getInitialSlice;253528204)))), array('function' => 'Closure$Illuminate\Pipeline\Pipeline::getSlice', 'args' => array(object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php', 'line' => '32', 'function' => 'call_user_func', 'args' => array(object(Pipeline::getSlice;579281960), object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php', 'line' => '52', 'function' => 'Closure$Illuminate\Routing\Pipeline::getSlice', 'args' => array(object(Request))), array('function' => 'handle', 'class' => 'Illuminate\Routing\Middleware\ThrottleRequests', 'type' => '->', 'args' => array(object(Request), object(Pipeline::getSlice;253528204), '60', '1')), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php', 'line' => '124', 'function' => 'call_user_func_array', 'args' => array(array(object(ThrottleRequests), 'handle'), array(object(Request), object(Pipeline::getSlice;253528204), '60', '1'))), array('function' => 'Closure$Illuminate\Pipeline\Pipeline::getSlice', 'args' => array(object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php', 'line' => '32', 'function' => 'call_user_func', 'args' => array(object(Pipeline::getSlice;579281960), object(Request))), array('function' => 'Closure$Illuminate\Routing\Pipeline::getSlice', 'args' => array(object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php', 'line' => '103', 'function' => 'call_user_func', 'args' => array(object(Pipeline::getSlice;253528204), object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php', 'line' => '705', 'function' => 'then', 'class' => 'Illuminate\Pipeline\Pipeline', 'type' => '->', 'args' => array(object(Router::runRouteWithinStack;124210213))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php', 'line' => '678', 'function' => 'runRouteWithinStack', 'class' => 'Illuminate\Routing\Router', 'type' => '->', 'args' => array(object(Route), object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php', 'line' => '654', 'function' => 'dispatchToRoute', 'class' => 'Illuminate\Routing\Router', 'type' => '->', 'args' => array(object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php', 'line' => '246', 'function' => 'dispatch', 'class' => 'Illuminate\Routing\Router', 'type' => '->', 'args' => array(object(Request))), array('function' => 'Closure$Illuminate\Foundation\Http\Kernel::dispatchToRouter', 'args' => array(object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php', 'line' => '52', 'function' => 'call_user_func', 'args' => array(object(Kernel::dispatchToRouter;1400198055), object(Request))), array('file' => '/web/laravel/app/Http/Middleware/AccessControlAllowLocal.php', 'line' => '37', 'function' => 'Closure$Illuminate\Routing\Pipeline::getInitialSlice', 'args' => array(object(Request))), array('function' => 'handle', 'class' => 'App\Http\Middleware\AccessControlAllowLocal', 'type' => '->', 'args' => array(object(Request), object(Pipeline::getInitialSlice;253528204))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php', 'line' => '124', 'function' => 'call_user_func_array', 'args' => array(array(object(AccessControlAllowLocal), 'handle'), array(object(Request), object(Pipeline::getInitialSlice;253528204)))), array('function' => 'Closure$Illuminate\Pipeline\Pipeline::getSlice', 'args' => array(object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php', 'line' => '32', 'function' => 'call_user_func', 'args' => array(object(Pipeline::getSlice;579281960), object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php', 'line' => '44', 'function' => 'Closure$Illuminate\Routing\Pipeline::getSlice', 'args' => array(object(Request))), array('function' => 'handle', 'class' => 'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode', 'type' => '->', 'args' => array(object(Request), object(Pipeline::getSlice;253528204))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php', 'line' => '124', 'function' => 'call_user_func_array', 'args' => array(array(object(CheckForMaintenanceMode), 'handle'), array(object(Request), object(Pipeline::getSlice;253528204)))), array('function' => 'Closure$Illuminate\Pipeline\Pipeline::getSlice', 'args' => array(object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php', 'line' => '32', 'function' => 'call_user_func', 'args' => array(object(Pipeline::getSlice;579281960), object(Request))), array('function' => 'Closure$Illuminate\Routing\Pipeline::getSlice', 'args' => array(object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php', 'line' => '103', 'function' => 'call_user_func', 'args' => array(object(Pipeline::getSlice;253528204), object(Request))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php', 'line' => '132', 'function' => 'then', 'class' => 'Illuminate\Pipeline\Pipeline', 'type' => '->', 'args' => array(object(Kernel::dispatchToRouter;1400198055))), array('file' => '/web/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php', 'line' => '99', 'function' => 'sendRequestThroughRouter', 'class' => 'Illuminate\Foundation\Http\Kernel', 'type' => '->', 'args' => array(object(Request))), array('file' => '/web/laravel/public/index.php', 'line' => '54', 'function' => 'handle', 'class' => 'Illuminate\Foundation\Http\Kernel', 'type' => '->', 'args' => array(object(Request))))) in MappingFactory.php line 112
at MappingFactory::fromArray('App\Transformer\UsersTransformer') in Mapper.php line 24
at Mapper->buildMapping('App\Transformer\UsersTransformer') in Mapper.php line 36
at Mapper->__construct(array('App\Transformer\UsersTransformer', 'App\Transformer\OrganizationsTransformer', 'App\Transformer\CoursesTransformer')) in Laravel51Provider.php line 29
at Closure$NilPortugues\Laravel5\JsonApi\Providers\Laravel51Provider::provider() in Repository.php line 325
at Repository->rememberForever('jsonapi.mapping', object(Laravel51Provider::provider;850661141))
at call_user_func_array(array(object(Repository), 'rememberForever'), array('jsonapi.mapping', object(Laravel51Provider::provider;850661141))) in CacheManager.php line 296
at CacheManager->__call('rememberForever', array('jsonapi.mapping', object(Laravel51Provider::provider;850661141))) in Facade.php line 221
at Facade::__callStatic('rememberForever', array('jsonapi.mapping', object(Laravel51Provider::provider;850661141))) in Laravel51Provider.php line 30
at Closure$NilPortugues\Laravel5\JsonApi\Providers\Laravel51Provider::provider#2(object(Application), array()) in Container.php line 735
at Container->build(object(Laravel51Provider::provider#2;850661141), array()) in Container.php line 633
at Container->make('NilPortugues\Laravel5\JsonApi\JsonApiSerializer', array()) in Application.php line 674
at Application->make('NilPortugues\Laravel5\JsonApi\JsonApiSerializer') in Container.php line 853
at Container->resolveClass(object(ReflectionParameter)) in Container.php line 808
at Container->getDependencies(array(object(ReflectionParameter)), array()) in Container.php line 779
at Container->build('App\Http\Controllers\OrganizationController', array()) in Container.php line 633
at Container->make('App\Http\Controllers\OrganizationController', array()) in Application.php line 674
at Application->make('App\Http\Controllers\OrganizationController') in ControllerDispatcher.php line 67
at ControllerDispatcher->makeController('App\Http\Controllers\OrganizationController') in ControllerDispatcher.php line 52
at ControllerDispatcher->dispatch(object(Route), object(Request), 'App\Http\Controllers\OrganizationController', 'index') in Route.php line 174
at Route->runController(object(Request)) in Route.php line 140
at Route->run(object(Request)) in Router.php line 704
at Closure$Illuminate\Routing\Router::runRouteWithinStack(object(Request))
at call_user_func(object(Router::runRouteWithinStack;124210213), object(Request)) in Pipeline.php line 52
at Closure$Illuminate\Routing\Pipeline::getInitialSlice(object(Request)) in TokenAuth.php line 50
at TokenAuth->handle(object(Request), object(Pipeline::getInitialSlice;253528204))
at call_user_func_array(array(object(TokenAuth), 'handle'), array(object(Request), object(Pipeline::getInitialSlice;253528204))) in Pipeline.php line 124
at Closure$Illuminate\Pipeline\Pipeline::getSlice(object(Request))
at call_user_func(object(Pipeline::getSlice;579281960), object(Request)) in Pipeline.php line 32
at Closure$Illuminate\Routing\Pipeline::getSlice(object(Request)) in ThrottleRequests.php line 52
at ThrottleRequests->handle(object(Request), object(Pipeline::getSlice;253528204), '60', '1')
at call_user_func_array(array(object(ThrottleRequests), 'handle'), array(object(Request), object(Pipeline::getSlice;253528204), '60', '1')) in Pipeline.php line 124
at Closure$Illuminate\Pipeline\Pipeline::getSlice(object(Request))
at call_user_func(object(Pipeline::getSlice;579281960), object(Request)) in Pipeline.php line 32
at Closure$Illuminate\Routing\Pipeline::getSlice(object(Request))
at call_user_func(object(Pipeline::getSlice;253528204), object(Request)) in Pipeline.php line 103
at Pipeline->then(object(Router::runRouteWithinStack;124210213)) in Router.php line 705
at Router->runRouteWithinStack(object(Route), object(Request)) in Router.php line 678
at Router->dispatchToRoute(object(Request)) in Router.php line 654
at Router->dispatch(object(Request)) in Kernel.php line 246
at Closure$Illuminate\Foundation\Http\Kernel::dispatchToRouter(object(Request))
at call_user_func(object(Kernel::dispatchToRouter;1400198055), object(Request)) in Pipeline.php line 52
at Closure$Illuminate\Routing\Pipeline::getInitialSlice(object(Request)) in AccessControlAllowLocal.php line 37
at AccessControlAllowLocal->handle(object(Request), object(Pipeline::getInitialSlice;253528204))
at call_user_func_array(array(object(AccessControlAllowLocal), 'handle'), array(object(Request), object(Pipeline::getInitialSlice;253528204))) in Pipeline.php line 124
at Closure$Illuminate\Pipeline\Pipeline::getSlice(object(Request))
at call_user_func(object(Pipeline::getSlice;579281960), object(Request)) in Pipeline.php line 32
at Closure$Illuminate\Routing\Pipeline::getSlice(object(Request)) in CheckForMaintenanceMode.php line 44
at CheckForMaintenanceMode->handle(object(Request), object(Pipeline::getSlice;253528204))
at call_user_func_array(array(object(CheckForMaintenanceMode), 'handle'), array(object(Request), object(Pipeline::getSlice;253528204))) in Pipeline.php line 124
at Closure$Illuminate\Pipeline\Pipeline::getSlice(object(Request))
at call_user_func(object(Pipeline::getSlice;579281960), object(Request)) in Pipeline.php line 32
at Closure$Illuminate\Routing\Pipeline::getSlice(object(Request))
at call_user_func(object(Pipeline::getSlice;253528204), object(Request)) in Pipeline.php line 103
at Pipeline->then(object(Kernel::dispatchToRouter;1400198055)) in Kernel.php line 132
at Kernel->sendRequestThroughRouter(object(Request)) in Kernel.php line 99
at Kernel->handle(object(Request)) in index.php line 54

Hydrated Model Relations Get Re-Fetched

If a model has had it's relations hydrated prior to a being passed to ListResource::get(), will the same relations be re-fetched, i.e. does laravel5-jsonapi know that there is data already present in the relation or does it just fetch them regardless ?

$results = Listing::searchByQuery(false, false, false, $limit, false, false);
$results = Listing::hydrate($results->toArray());

foreach ($results as $r) {
     $r->setRelation('listingType', (new listingType())->fill(
         $r['listing_type']
     ));
 }

$total = function () use ($model_class) {
        return (new $model_class())->count();
};

 $url = $request->url();

 $results = function () use ($results) {
     return $results;
 };

 $api_request = RequestFactory::create();

 $page_size = $this->page_size;
 $page      = $api_request->getPage();

 if (!$page->size()) {
      $page->setSize($page_size);
 }

 $fields    = $api_request->getFields();
 $sorting   = $api_request->getSort();
 $included  = $api_request->getIncludedRelationships();
 $filters   = $api_request->getFilters();

 $resource = new ListResource($this->serializer, $page, $fields, $sorting, $included, $filters);

 return $resource->get($total, $results, $url, Listing::class);

As the relations have been hydrated I would not expect to see any relational queries in

print_r(\DB::getQueryLog());

However I do.

Any help would be greatly appreciated :-)

Provided route name does not exist

I'm attempting to implement the README example. I got it working initially, but then "something" happened as I was making changes and I started getting "Provided route name does not exist" error for all requests.
I've since rebuild the README example from scratch and I'm getting this error right off the bat.

I see where it is being thrown, but I'm not able to determine why? Any help/pointers is greatly appreciated.

Laravel: v5.2.27
laravel5-jsonapi: 2.1.12

These are the files I'm using, which should pretty much be as per the README example. The only change I aware of is that the use App\Orders; (as shown in the README) statement in OrdersTransformer.php seems like it should be use App\Model\Database\Orders; (as I have it here).

Thanks in advance, let me know if you need further details

routes.php

<?php

Route::group(['namespace' => 'Api'], function() {
    Route::resource('employees', 'EmployeesController');
    Route::get(
        'employees/{employee_id}/orders', [
        'as' => 'employees.orders',
        'uses' => 'EmployeesController@getOrdersByEmployee'
    ]);
});

Employees.php

<?php

namespace App\Model\Database;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Validation\ValidatesRequests;

class Employees extends Model
{
    public $timestamps = false;
    protected $table = 'employees';
    protected $primaryKey = 'id';
    protected $appends = ['full_name'];

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function latestOrders()
    {
        return $this->hasMany(Orders::class, 'employee_id')->limit(10);
    }

    /**
     * @return string
     */
    public function getFullNameAttribute()
    {
        return $this->first_name.' '.$this->last_name;
    }
}

EmployeesController.php

<?php

namespace App\Http\Controllers\Api;

use App\Model\Database\Employees;
use App\Model\Database\Orders;
use NilPortugues\Laravel5\JsonApi\Controller\JsonApiController;

class EmployeesController extends JsonApiController
{
    /**
     * Return the Eloquent model that will be used
     * to model the JSON API resources.
     *
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function getDataModel()
    {
        return new Employees();
    }

    /**
     * @param Request $request
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function getOrdersByEmployee(Request $request)
    {
        $apiRequest = RequestFactory::create();
        $page = $apiRequest->getPage();

        if (!$page->size()) {
            $page->setSize(10); //Default elements per page
        }

        $resource = new ListResource(
            $this->serializer,
            $page,
            $apiRequest->getFields(),
            $apiRequest->getSort(),
            $apiRequest->getIncludedRelationships(),
            $apiRequest->getFilters()
        );

        $totalAmount = function() use ($request) {
            $id = (new Orders())->getKeyName();
            return Orders::query()
                ->where('employee_id', '=', $request->employee_id)
                ->get([$id])
                ->count();
        };

        $results = function()  use ($request) {
            return EloquentHelper::paginate(
                $this->serializer,
                Orders::query()
                    ->where('employee_id', '=', $request->employee_id)
            )->get();
        };

        $uri = route('employees.orders', ['employee_id' => $request->employee_id]);

        return $resource->get($totalAmount, $results, $uri, Orders::class);
    }
}

EmployeesTransformer.php

<?php

namespace App\Model\Api;

use App\Model\Database\Employees;
use NilPortugues\Api\Mappings\JsonApiMapping;

class EmployeesTransformer implements JsonApiMapping
{
    /**
     * Returns a string with the full class name, including namespace.
     *
     * @return string
     */
    public function getClass()
    {
        return Employees::class;
    }

    /**
     * Returns a string representing the resource name
     * as it will be shown after the mapping.
     *
     * @return string
     */
    public function getAlias()
    {
        return 'employee';
    }

    /**
     * Returns an array of properties that will be renamed.
     * Key is current property from the class.
     * Value is the property's alias name.
     *
     * @return array
     */
    public function getAliasedProperties()
    {
        return [
            'last_name' => 'surname',

        ];
    }

    /**
     * List of properties in the class that will be  ignored by the mapping.
     *
     * @return array
     */
    public function getHideProperties()
    {
        return [
            'attachments'
        ];
    }

    /**
     * Returns an array of properties that are used as an ID value.
     *
     * @return array
     */
    public function getIdProperties()
    {
        return ['id'];
    }

    /**
     * Returns a list of URLs. This urls must have placeholders
     * to be replaced with the getIdProperties() values.
     *
     * @return array
     */
    public function getUrls()
    {
        return [
            'self' => ['name' => 'employees.show', 'as_id' => 'id'],
            'employees' => ['name' => 'employees.index'],
            'employee_orders' => ['name' => 'employees.orders', 'as_id' => 'id']
        ];
    }

    /**
     * Returns an array containing the relationship mappings as an array.
     * Key for each relationship defined must match a property of the mapped class.
     *
     * @return array
     */
    public function getRelationships()
    {
        return [];
    }
}

jsonapi.php

<?php
use App\Model\Api\EmployeesTransformer;
use App\Model\Api\OrdersTransformer;

return [
    EmployeesTransformer::class,
    OrdersTransformer::class,
];

Orders.php

<?php

namespace App\Model\Database;

use Illuminate\Database\Eloquent\Model;

class Orders extends Model
{
    public $timestamps = false;
    protected $table = 'orders';
    protected $primaryKey = 'id';

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function employee()
    {
        return $this->belongsTo(Employees::class, 'employee_id');
    }
}

OrdersTransformer.php

<?php

namespace App\Model\Api;

use App\Model\Database\Orders;
use NilPortugues\Api\Mappings\JsonApiMapping;

class OrdersTransformer implements JsonApiMapping
{
    /**
     * {@inheritDoc}
     */
    public function getClass()
    {
        return Orders::class;
    }
    /**
     * {@inheritDoc}
     */
    public function getAlias()
    {
        return 'order';
    }
    /**
     * {@inheritDoc}
     */
    public function getAliasedProperties()
    {
        return [];
    }
    /**
     * {@inheritDoc}
     */
    public function getHideProperties()
    {
        return [];
    }
    /**
     * {@inheritDoc}
     */
    public function getIdProperties()
    {
        return ['id'];
    }
    /**
     * {@inheritDoc}
     */
    public function getUrls()
    {
        return [
            'self'     => ['name' => 'orders.show', 'as_id' => 'id'],
            'employee' => ['name' => 'employees.get', 'as_id' => 'employee_id'],
        ];
    }
    /**
     * {@inheritDoc}
     */
    public function getRelationships()
    {
        return [];
    }
}

Previous page not setting in pagination on page 2

The pagination links in the returned JSON API response include the previous link on all pages except for pages 1 and 2. This is understandable and correct for page 1, however page 2 should have a previous page included.

Having looked through the code I can see on line 179 of json-api/src/Server/Actions/ListResource.php that the previous page number is set if the value is greater than 1:

'previous' => ($previous > 1) ? $previous : null,

To fix this issue the operator should be changed from > to >=

Client-Generated ID

Hi!

When I want to store a model with a pre-defined ID, in the JsonApiController class createResourceCallable method

        if (!empty($data['id'])) {
            $model->setAttribute($model->getKeyName(), $values['id']);
        }

I think the $values['id'] have to be $data['id'].

Relationships Example

Hi,

Is there an example anywhere for relationships with this package and being able to call them with ?include=[relationship]?

Thanks.

Add cache

The Laravel51Provider class can do cache by generation a key with the $mapping array or with the new Mapper instance.

Operations such as Reflection should be cached, so it MUST be done before actually parsing routes.

Inclusion with belongsTo relationship

Hi there,

I have a property which belongs to another model (belongsTo relationship), how can I implement to include it in the inclusion part of json?

For example: just take your default example, is there anyway to show the employee information in the inclusion part of order result? (the opposite direction of your default example.)

Undefined index: [Random relation]

I am getting this error when listing my resource:

object(ErrorException)[155]
  protected 'message' => string 'Undefined index: App\Party\Group' (length=32)
  private 'string' (Exception) => string '' (length=0)
  protected 'code' => int 0
  protected 'file' => string '/home/vagrant/parties/api/vendor/nilportugues/json-api/src/Helpers/DataLinksHelper.php' (length=86)
  protected 'line' => int 107
...

App\Party\Group is a model related to the listed resource, which is App\Party\Party.

I tried clearing my cache etc. and it seems like the undefined index is different every time. It it always the classname of one of the listed resource's relations. The index (The $mappings variable) only contains the classname of the listed resource, App\Party\Party.

I don't understand why this error occurs, so I don't know how to fix it. I've made sure all my relations work as they should, so it doesn't seem to be that.

Can't figure out the file names and their paths

Hi, I'm at the EmployeesTransformer section now.

It says:

Follow up, we'll be creating Transformers. One Transformer is required for each class and it must implement the \NilPortugues\Api\Mappings\JsonApiMapping interface.

  1. Can you please tell me where in the app and what file name should I create for the code snippet you showed there?
  2. And when you say Employees (Eloquent Model), is it Employees.php under app/Models?

It'd be easier to follow for newbies if you provide with file names and their paths, so that it'll be easier to learn.

Thanks for your efforts.

example doesn't work

on get request:
NilPortugues\Api\Mapping\MappingFactory::fromArray() must be of the type array, string given .....

Relationship example

Laravel version: 5.1

Hi @nilopc,

I try to create a relationship between user and products, but the error below is showing, can you create an example?

Error

Could not find property product in class App\User because it does not exist.

User.php

<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;

class User extends Model implements AuthenticatableContract,
                                    AuthorizableContract,
                                    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword;

    protected $table = 'users';

    protected $fillable = ['name', 'email', 'password'];

    protected $hidden = ['password', 'remember_token'];

    public function products()
    {
        return $this->belongsToMany('App\Product');
    }
}

jsonapi.api

<?php

return [
    [
        'class' => 'App\User',
        'alias' => 'User',
        'id_properties' => [
            'id',
        ],
        'urls' => [
            'self' => 'user.show',
        ],
        'relationships' => [
            'products' => [
                'related' => 'user.products',
            ]
        ],
    ],
];

routes.php

<?php

Route::get('/users/{id}', [
    'as'   => 'user.show',
    'uses' => 'ClientController@show'
]);

Route::get('/users/{id}/products', [
    'as'   => 'user.products',
    'uses' => 'ClientController@products'
]);

UserController.php

<?php

namespace App\Http\Controllers;

use App\User;
use NilPortugues\Laravel5\JsonApiSerializer\JsonApiSerializer;
use NilPortugues\Laravel5\JsonApiSerializer\JsonApiResponseTrait;

class ClientController extends \App\Http\Controllers\Controller
{
    use JsonApiResponseTrait;

    private $serializer;

    public function __construct(JsonApiSerializer $serializer)
    {
        $this->serializer = $serializer;
    }

    public function show($id)
    {
        $user = User::find($id);

        $transformer = $this->serializer->getTransformer();
        $transformer->setSelfUrl(route('user.show', ['id' => $id]));

        return $this->response($this->serializer->serialize($user));
    }

    public function products($id)
    {
        $user   = User::find($id);
        $products = $user->products;

        $transformer = $this->serializer->getTransformer();
        $transformer->setSelfUrl(route('user.products', ['id' => $id]));

        return $this->response($this->serializer->serialize($products));
    }
}

Where can user get ServerRequestInterface?

Not a bug. Probably real life issue.

It's nice to comply with PSR7 however you do package for defined framework and from practical point of view how do you use NilPortugues\Api\JsonApi\Http\Message\Request? Using Diactoros or similar is overkill IMHO

use \Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;

$psr7Factory = new DiactorosFactory();
$psrRequest = $psr7Factory->createRequest($laravelRequest);

Illuminate\Foundation\Application not available in Lumen

FatalThrowableError in Laravel5JsonApiServiceProvider.php line 45:
Fatal error: Class 'Illuminate\Foundation\Application' not found

Illuminate\Foundation Does not exists in Lumen 5.1

From what I can see in Laravel5JsonApiServiceProvider::register() line 45, it is need to determine the version of Laravel.

$version = Application::VERSION;

getAlias value not mirrored in response

I have this code in my PartiesTransformer class

public function getAlias()
{
    return 'party';
}

but when I GET the list of the resource, it shows "type": "parties"

loss of default page size

$apiRequest = RequestFactory::create(); 
$page = $apiRequest->getPage();

if (! $page->size()) {
    $page->setSize(5); // Default elements per page
}

When using the above code at the top of a controller action, as is done in the example in the README.

After calling EloquentHelper::paginate() the $page->size() returns null.

I believe this to be caused by the fact that $page is not a singleton like Request, as in Request::getPage() a new Page() object is created based of the query parameters.

$page = new Page(
   (!empty($queryParam['number'])) ? $queryParam['number'] : 1,
   (!empty($queryParam['cursor'])) ? $queryParam['cursor'] : null,
   (!empty($queryParam['limit'])) ? $queryParam['limit'] : null,
   (!empty($queryParam['offset'])) ? $queryParam['offset'] : null,
   (!empty($queryParam['size'])) ? $queryParam['size'] : null
);

DataObject throws a DataException with no message

An uncaught DataException is thrown without a message or anything in the DataObject class

I know it belongs to a different repo but I thought it was relevant to this too since this is where I have encountered it. Feel free to let me know if you want me to put it on the other issue tracker.

It was thrown in my applicaton because I had removed a field from my database:

object(NilPortugues\Api\JsonApi\Server\Errors\ErrorBag)[39]
  protected 'errors' => 
    array (size=1)
      0 => 
        object(NilPortugues\Api\JsonApi\Server\Errors\MissingAttributeError)[58]
          protected 'id' => null
          protected 'links' => 
            array (size=0)
              ...
          protected 'status' => null
          protected 'code' => string 'unprocessable_entity' (length=20)
          protected 'title' => string 'Missing Attribute' (length=17)
          protected 'detail' => string 'Attribute `party_group_id` is missing.' (length=38)
          protected 'source' => 
            array (size=1)
              ...
          protected 'meta' => null
  protected 'httpCode' => null

If the error message could be relayed through the Exception, it would be easier to troubleshoot the problem

No method 'resource' available in Lumen

Lumen does not include some of the route methods available in Laravel, like resource(). Simple fix, but you'll want to update the docs with the proper lumen routes.

ErrorBag missing on create

Hi!

In JsonApiTrait

    /**
     * Reads the input and creates and saves a new Eloquent Model.
     *
     * @return callable
     * @codeCoverageIgnore
     */
    protected function createResourceCallable()
    {
        return function (array $data, array $values) {
            $model = $this->getDataModel()->newInstance();

            foreach ($values as $attribute => $value) {
                $model->setAttribute($attribute, $value);
            }

            if (!empty($data['id'])) {
                $model->setAttribute($model->getKeyName(), $values['id']);
            }

            try {
                $model->save();
            } catch (\Exception $e) {
                $errorBag[] = new Error('creation_error', 'Resource could not be created');
                throw $e;
            }

            return $model;
        };
    }

The ErrorBag object not passed through the parameter list.

It should be:

return function (array $data, array $values, ErrorBag $errorBag) {

Lumen installation throws action() exception

I am trying to implement this with a Lumen application, and I get the following exception:

"exception": {
      "type": "FatalThrowableError",
      "file": "JsonApiTrait.php",
      "line": 74,
      "message": "Fatal error: Call to undefined function NilPortugues\Laravel5\JsonApi\Controller\action()"
    }

Am I missing something or are there compatibility problems?

JsonApiTrait uses the action() helper, which is unavailable with Lumen.

Inclusion of relationships in data items

When requesting the following endpoint /api/v1/user/1

I would expect the relationships attribute to be present in the data section and for it to contain any related objects for each item. However it is not.

If I request /api/v1/user/1/client I get the relationships returned for the client object, which is all the users associated to the client.

The only difference that I can see between the two endpoints is that in the read controller method $results is a Builder object and in client controller method client is a Client model.

route

$app->get('user/{id}', ['as' => 'user.get', 'uses' => "UserController@read"]);
$app->get('user/{id}/client', ['as' => 'user.client', 'uses' => "UserController@client"]);

Controllers

public function read(Request $request, $id = false)
{
    $results = User::query();

    if ($id) {
        $results->where('id', $id);
    }

    $results = EloquentHelper::paginate($this->serializer, $results)->get();
    return $this->toJsonAPI(User::class, $results, $request);
}

public function client(Request $request, $id)
{
    $user   = User::find($id);
    $client = $user->client;

    $transformer = $this->serializer->getTransformer();
    $transformer->setSelfUrl(route('user.client', ['id' => $id]));

    return $this->toJsonAPI(Client::class, $client, $request);
}

_toJsonAPI_

public function toJsonAPI($model_class, $results, $request)
{
    $url = $request->url();

    $results = function () use ($results) {
        return $results;
    };

    $total = function () use ($results) {
        return $results()->count();
    };

    $api_request = RequestFactory::create();

    $page_size = $this->page_size;
    $page      = $api_request->getPage();

    if (! $page->size()) {
        $page->setSize($page_size);
    }

    $fields    = $api_request->getFields();
    $sorting   = $api_request->getSort();
    $included  = $api_request->getIncludedRelationships();
    $filters   = $api_request->getFilters();

    $resource = new ListResource($this->serializer, $page, $fields, $sorting, $included, $filters);

    return $resource->get($total, $results, $url, $model_class);
}

Got problem when try to add package (symphony/http-foundation)

Please help, I got this error when try to install your package to my Laravel 5.2 project.

Problem 1
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.5
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.4
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.3
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.2
    - Conclusion: don't install nilportugues/laravel5-json-api 2.1.1
    - Conclusion: remove symfony/http-foundation v3.0.1
    - Installation request for nilportugues/laravel5-json-api ^2.1 -> satisfiable by nilportugues/laravel5-json-api[2.1.0, 2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5].
    - Conclusion: don't install symfony/http-foundation v3.0.1
    - nilportugues/laravel5-json-api 2.1.0 requires nilportugues/json-api 2.1.* -> satisfiable by nilportugues/json-api[2.1.0, 2.1.1, 2.1.2].
    - nilportugues/json-api 2.1.0 requires symfony/http-foundation ^2.7 -> satisfiable by symfony/http-foundation[v2.7.0, v2.7.1, v2.7.2, v2.7.3, v2.7.4, v2.7.5, v2.7.6, v2.7.7, v2.7.8, v2.8.0, v2.8.1].
    - nilportugues/json-api 2.1.1 requires symfony/http-foundation ^2.7 -> satisfiable by symfony/http-foundation[v2.7.0, v2.7.1, v2.7.2, v2.7.3, v2.7.4, v2.7.5, v2.7.6, v2.7.7, v2.7.8, v2.8.0, v2.8.1].
    - nilportugues/json-api 2.1.2 requires symfony/http-foundation ^2.7 -> satisfiable by symfony/http-foundation[v2.7.0, v2.7.1, v2.7.2, v2.7.3, v2.7.4, v2.7.5, v2.7.6, v2.7.7, v2.7.8, v2.8.0, v2.8.1].
    - Can only install one of: symfony/http-foundation[v2.8.0, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.8.1, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.0, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.1, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.2, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.3, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.4, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.5, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.6, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.7, v3.0.1].
    - Can only install one of: symfony/http-foundation[v2.7.8, v3.0.1].
    - Installation request for symfony/http-foundation == 3.0.1.0 -> satisfiable by symfony/http-foundation[v3.0.1].


Installation failed, reverting ./composer.json to its original content.

Add Cache to controller rest methods

Using the eloquent class name and rest methods ร— query params, caching should be possible... improving performance which is good already.

Relational Sorting

Is relational sorting implemented ? if so, is there documentation.

http://localhost/api/v3/listing?sort=client.id

http://jsonapi.org/format/#fetching-sorting

"It is recommended that dot-separated (U+002E FULL-STOP, ".") sort fields be used to request sorting based upon relationship attributes. For example, a sort field of author.name could be used to request that the primary data be sorted based upon the name attribute of the author relationship."

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.