Giter Club home page Giter Club logo

laravel-json-api's Introduction

Tests

cloudcreativity/laravel-json-api

Status

DO NOT USE THIS PACKAGE FOR NEW PROJECTS - USE laravel-json-api/laravel INSTEAD.

This package has now been rewritten, substantially improved and released as the laravel-json-api/laravel package. Documentation for the new version is available on our new website laraveljsonapi.io and the code is now developed under the Laravel JSON:API Github organisation.

The cloudcreativity/laravel-json-api package is now considered to be the legacy package. As we know it is in use in a lot of production applications, it will continue to receive bug fixes and updates for new Laravel versions. However, it is no longer supported for new features.

If you are starting a new project, you MUST use the new package laravel-json-api/laravel.

Introduction

Build feature-rich and standards-compliant APIs in Laravel.

This package provides all the capabilities you need to add JSON API compliant APIs to your application. Extensive support for the specification, including:

  • Fetching resources
  • Fetching relationships
  • Inclusion of related resources (compound documents)
  • Sparse fieldsets.
  • Sorting.
  • Pagination.
  • Filtering
  • Creating resources.
  • Updating resources.
  • Updating relationships.
  • Deleting resources.
  • Validation of:
    • JSON API documents; and
    • Query parameters.

The following additional features are also supported:

  • Full support for Eloquent resources, with features such as:
    • Automatic eager loading when including related resources.
    • Easy relationship end-points.
    • Soft-deleting and restoring Eloquent resources.
    • Page and cursor based pagination.
  • Asynchronous processing.
  • Support multiple media-types within your API.
  • Generators for all the classes you need to add a resource to your API.

What is JSON API?

From jsonapi.org

If you've ever argued with your team about the way your JSON responses should be formatted, JSON API is your anti-bikeshedding weapon.

By following shared conventions, you can increase productivity, take advantage of generalized tooling, and focus on what matters: your application. Clients built around JSON API are able to take advantage of its features around efficiently caching responses, sometimes eliminating network requests entirely.

For full information on the spec, plus examples, see http://jsonapi.org.

Documentation

Full package documentation is available on Read the Docs.

Slack

Join the Laravel JSON:API community on Slack.

Laravel Versions

Laravel This Package
^9.0 ^4.0
^8.0 `^3.0
^7.0 ^2.0
^6.0 ^1.7
5.8.* ^1.7
5.7.* ^1.0
5.6.* ^1.0
5.5.* ^1.0

Make sure you consult the Upgrade Guide when upgrading between major or pre-release versions.

License

Apache License (Version 2.0). Please see License File for more information.

Installation

Installation is via composer. See the documentation for complete instructions.

Contributing

Contributions are absolutely welcome. Ideally submit a pull request, even more ideally with unit tests. Please note the following:

  • Bug Fixes - submit a pull request against the master branch.
  • Enhancements / New Features - submit a pull request against the develop branch.

We recommend submitting an issue before taking the time to put together a pull request.

laravel-json-api's People

Contributors

acorncom avatar askmrsinh avatar balaianu avatar ben221199 avatar benkingcode avatar calvinps avatar chris-doehring avatar daryledesilva avatar dneykov avatar dtr0yan avatar giantcrab avatar gregpeden avatar half2me avatar igorsantos07 avatar its-schmii avatar iwasherefirst2 avatar jeanlucesser avatar jelhan avatar jstoone avatar kierynannette avatar laravel-shift avatar lindyhopchris avatar m-bymike avatar malitta avatar mcmatters avatar mina-r-meshriky avatar mnrafg avatar pedroluislopez avatar rekrios avatar zlodes 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-json-api's Issues

Authorize ability to access/update specific model attributes

We're in the process of updating our API to use the newest version of this lib. Really great work with the recent updates!

One thing we're in doubt about now though, is how we would go about whitelisting attributes for updating based on which user has been authorized.

It doesn't seem like the dirty attributes are available in the Authorizer. But maybe the correct way would be to check for unauthorized change of attributes via the Hydrator? If that's the case, I'm not sure how to throw an error, in the case that I would like to accept some changed attributes, but return an error to tell which ones didn't update.

Do you have any insight into this?

Getting empty 'errors' result

I have a case where the API is returning 'errors' with an empty array. Since it's handled by the API and not the exception handler, error tracing this is very difficult. I suspect it is an error event for which no message is provided in the default config.

I am not familiar enough with this package to trace the error generation at this time, otherwise I might try to provide a better hint!

So, two suggestions:

  1. If there are error events without a message, maybe they should be added to the json-api-errors.php?

  2. Perhaps a default error message should be provided, and when this default is used a log dump to the server log file should occur?

Problem with belongsToMany relationship

Note, that I am currently using 0.5.x-dev (as I'm working with Laravel 5.3) so the problem below may not apply to earlier versions of this package.

BelongsToMany relationships leads to the following Exception.

Next Illuminate\Database\QueryException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'helios.model_model' doesn't exist (SQL: select `roles`.*, `model_model`.`model_id` as `pivot_model_id`, `model_model`.`created_at` as `pivot_created_at`, `model_model`.`updated_at` as `pivot_updated_at` from `roles` inner join `model_model` on `roles`.`id` = `model_model`.`model_id` where `model_model`.`model_id` = 1) in /var/www/helios/vendor/laravel/framework/src/Illuminate/Database/Connection.php:761

At first glance, it seems like the package can't figure out the name of pivot table and foreign key in accordance with Laravel naming conventions.

When I manually specify names (i.e. return $this->belongsToMany(\App\JsonApi\Roles\Model::class, 'role_user', 'id', 'role_id')->withTimestamps();), Exception is no longer thrown but I encounter another issue: $resource->roles should be a collection with multiple(!) items but I get the collection with only the very first one in response.

Below is the code that lead to this post.

Model

<?php

namespace App\JsonApi\Users;

use Illuminate\Database\Eloquent\Model as EloquentModel;

class Model extends EloquentModel
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'users';

    /**
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function roles()
    {
        return $this->belongsToMany(\App\JsonApi\Roles\Model::class)->withTimestamps();
    }
}

Schema

<?php

namespace App\JsonApi\Users;

use CloudCreativity\LaravelJsonApi\Schema\EloquentSchema;

class Schema extends EloquentSchema
{
    const RESOURCE_TYPE = 'users';

    /**
     * @var string
     */
    protected $resourceType = self::RESOURCE_TYPE;

    /**
     * Whether resource member names are hyphenated
     *
     * @var bool
     */
    protected $hyphenated = false;

    /**
     * The model attribute keys to serialize.
     *
     * @var array
     */
    protected $attributes = [
        'email',
        'first_name',
        'last_name',
    ];

    /**
     * Get resource links.
     *
     * @param object $resource
     * @param bool   $isPrimary
     * @param array  $includeRelationships
     * @return array
     */
    public function getRelationships($resource, $isPrimary, array $includeRelationships)
    {
        return [
            'roles' => [
                self::SHOW_SELF => true,
                self::SHOW_RELATED => true,
                self::DATA => $resource->roles,
            ],
        ];
    }
}

The path to 1.0

Now that neomerx/json-api is at 1.0 we want to get this library to 1.0 as soon as possible - to give people confidence that the API is stable.

The recent routing refactor and reducing the number of units per resource type (currently on develop - will be released as 0.8) puts in a lot of pre-1.0 changes that we wanted to do to simplify the package.

Required 1.0 Features

  • Full support for filtering, paging etc for relationship endpoints - including test helpers. See #22, #23, #27, #77
  • Support with when retrieving single records. See #62
  • Hydrator simplification. See #69
  • Hydrate Eloquent polymorphic relationships. See #35
  • Generic authorizer that hands off to Laravel policies. See #45
  • Refactor JsonApiController so that it has the majority of the workflow that currently exists within EloquentController. Extend the Eloquent controller from this new controller.
  • Add a broadcast helper trait. See #36
  • Add a blade template helper to encode JSON API data into a HTML page. See #36
  • Convert tests to Laravel 5.4 style - i.e. remove dependency on laravel/browser-kit-testing.
  • Drop support for Laravel 5.3 and ensure support for 5.4 and 5.5 at 1.0.
  • Unit tests in this package rather than relying on the demo app to test package is working.
    Add json-api:cache and json-api:clear commands to cache the ApiInterface object. This will give a performance benefit to HTTP requests along the lines of route:cache.

Required Internal Changes

  • Rename the Resource class as it is a reserved word in PHP 7.0
  • Extract StandardObjectInterface and related generic classes to a separate utility library.
  • Single ValidatorErrorFactoryInterface in the cloudcreativity/json-api package rather than extending that interface in laravel-json-api.
  • Ideally stop injecting HttpServiceInterface and instead inject the ApiInterface and RequestInterface directly. This is possible as long as no services are created post the JSON-API middleware running.

Opening this issue so that people can comment on any features that they think are needed for 1.0.

Control count of included resources when querying the relation of an endpoint

Story time

When requesting the relation of an endpoint i.e: /api/v1/companies/1/users i get a list of users within the company of ID 1, true.
Now let's say I want to include some "likes" for each user, so we add that as a query parameter: /app/v1/companies/1/users?include=stars. Now we get a list of users, and all their stars have been included in the response as well, true.

Problem time

Let's say I want to be more specific, and only get 5 stars per user included. How would I go around solving this?

  • If there are 3 users we should get 5 stars for each of them, totaling at 15 stars
  • If one of the users only has 2 stars those are still included and the other two users will still have their 5 stars. Giving a total of 12 stars being included.

First try

In the App\Models\User eloquent model of mine i just cold heartedly added a limit onto the query:

public function stars()
{
    return $this->hasMany(Stars::class)->limit(5);
}

But this resulted in there only being returned 5 stars in total.

Where to go next

I am trying to dig deeper how loading of included models works, but I am having a hard time navigating around and finding the source where the final query is constructed.
It does seem a bit naughty to me that the stars relationship shares its limit globally, since it should only limit the amount of stars for that one user instance, right? I would understand if the relation method was static and therefore would apply to the entire collection of users.

Am I missing something, a filter or something that actually lets me do exactly this? Looking forward to hearing from you @lindyhopchris :)

Use hyphen as default for member names when converting Eloquent attributes

The JSON API spec recommends that member names use the lower case characters a-z and the hyphen - as the only allowed characters for member names. This is also the default being adopted by other libraries that implement the spec, e.g. Ember Data.

The recommendation is made here:
http://jsonapi.org/recommendations/#naming

Make this the default implementation for this library, by changing EloquentSchema::keyForAttribute() and AbstractSortedSearch::columnForField() methods.

Applications not following this recommendation can keep the old behaviour by overloading these methods.

Schemas loading for relationships

Hi @lindyhopchris and thanks in advance for this awesome work!
I'm getting an error when I try to add a relationship in my schema, it seems it can't find the schema for the relationship, even if I've set it in the configuration file.

The error I get is

InvalidArgumentException in Parser.php line 241:
Schema is not registered for a resource at path 'addons'.

In json-api.php I set the schemas array as follows

/**
 * Schemas
 */
C::SCHEMAS => [
    Schemas::DEFAULTS => [
        'App\Models\Addon'    => 'App\Schemas\AddonSchema',        // The relationship
        'App\Models\Shipment' => 'App\Schemas\ShipmentSchema',
    ],
    // merged with defaults if JSON API middleware uses the 'extra-schemas' name.
    'extra-schemas' => [
        'User' => 'UserSchema',
    ],
],

And in the parent model schema I have

public function getRelationships(
    $shipment,
    array $includeList = []
) {
    /** @var Shipment $shipment */
    return [
        'addons'   => [
            self::DATA         => $shipment->addons,
            self::SHOW_SELF    => true,
            self::SHOW_RELATED => true
        ],
        'sender'   => [self::DATA => $shipment->sender],
        'receiver' => [self::DATA => $shipment->receiver],
    ];
}

I'm having troubles understanding the docs 100%, maybe I did something wrong. Maybe I'm not including enough from neomerx/json-api...I am jumping from an error to another ๐Ÿ˜”

Thanks for your help

Merge `AbstractSortedSearch` into `AbstractSearch`

There's no requirement to have both AbstractSearch and AbstractSortedSearch - it would be simpler to merge the child into the parent. If someone wants to implement their own sort method, they can always override the sort method anyway.

Searching can be disabled by not allowing the client to send any sort parameters anyway - i.e. through the request definition.

Where to dispatch jobs/send events

My normal approach for dispatching jobs/sending events would be in the controller action, for example here where we would send an email that a model has been updated:

public function update(Model $model, Request $request) {
    $model->update($request->all());

    dispatch(new SendModelUpdatedEmail($model));

    // ...
}

What would be the recommended approach when extending from the EloquentController to achieve something similar?

My initial thoughts would be to:

  1. Override the update() method on the controller
    • Would have to duplicate the code used in the parent class.
  2. Using Eloquents updated event
    • For me, this logic shouldn't be handled in the Model.

Class 'CloudCreativity\JsonApi\Integration\EnvironmentService' not found

Hey there @lindyhopchris it's been a while,

I've been using this package for a few weeks now but from time to time...when performing some changes on my L5 app I get an error like this in my log:

[2015-11-27 12:08:12] local.ERROR: exception 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'Class 'CloudCreativity\JsonApi\Integration\EnvironmentService' not found' in /opt/admysis/panel-webserver/webserver/vendor/cloudcreativity/laravel-json-api/src/Integration/LaravelEnvironment.php:32
Stack trace:
#0 {main}  

I already know it's not your package's fault but rather something I did wrong that broke the app (e.g. wrong namespaces most of the times)....but instead of getting the corresponding error information I only get that 1 error and I'm left uncertain as for what it was that went wrong really. I've checked inside the file CloudCreativity\JsonApi\Integration\LaravelEnvironment and this is what I found:

<?php

namespace CloudCreativity\JsonApi\Integration;

use CloudCreativity\JsonApi\Integration\EnvironmentService as BaseService;

// ...

class LaravelEnvironment extends BaseService
{

// ...

Now, personally I have no clue how that gets resolved since I couldn't find such a class EnvironmentService but I wonder if there is any way to avoid getting that error message instead of the corresponding error that caused my app to break. Any thoughts on that?

[Proposal] Add artisan command class generators

It's hacktober, and I still have not made any pull-requests. And since I really like your work on this project, I'd love to contribute! Therefore i propose the following feature.

Since Laravel has started to amp up their use of generators significantly version by version, I have been thinking if you're interested in having one or more generators as artisan commands covering creating:

  • Schema
  • Hydrator
  • Request
  • Search
  • Validators

Lingo proposal

php artisan make:api:resource Task --attributes="body,completed"

php artisan make:api:schema Task/Schema
php artisan make:api:hydrator Task/Hydrator
php artisan make:api:request Task/Request
php artisan make:api:search Task/Search
php artisan make:api:validators Task/Validators

Since it varies where developers want to locate their JSON API related classes e.g:

App\Api\{Schemas,Requests,Hydrators, ...}\Task;
                                         \People;
                                         [...]
or
App\Api\Task\{Schema,Request,Hydator, ...};

This could be configured within the config files.

Any feedback is very welcome, and please do tell if you feel like this doesn't fit the project. Because then I'll be extracting this to a separate companion package. :)

Hydrate polymorphic relationships

I've recently had to hydrate a polymorphic relationship, which at least to my knowledge isn't supported natively in laravel-json-api, so after some tinkering I've written down some implementation ideas which I'd gladly compile down to a PR.

But I see that there is a lot of pending work pending regarding relationships. So I was wondering if there is anything that you see as a higher priority task, that I can help you get started with?

I use your library every day at work, and I would love to help keep the ball rolling, and improve on this already amazing product. As that would benefit everyone.

So in a more informal sense I'm saying this: jstoone at your service, what can I help you with?

Pagination

Hey I've been looking through the docs and code to try and figure out pagination on this package.

I've figured out how to get the paging array in the controller via $this->getParameters()->getPaginationParameters() after adding the $allowedPagingParameters definition to the controller, but when I call $model->paginate($page['size']) Laravel returns an instance of Illuminate\Pagination\LengthAwarePaginator which in turn generates a schema definition error of Schema is not registered for type 'Illuminate\Pagination\LengthAwarePaginator'

What is the correct way to implement pagination correctly?

Hydration of belongsToMany relationship

Hey!

I'm having an issue with my Hydrator, and I can't seem to figure our how to solve this.

    /**
     * @param RelationshipInterface $relationship
     * @param Post $model
     */
    protected function hydrateCompaniesRelationship(RelationshipInterface $relationship, User $user)
    {
        $user->companies()->attach($relationship->getIdentifiers()->getIds());
    }

This method is intended to register the companies that belongs to a user. But upon creating a new user it fails, as attach will attempt to register the relationship immediately and the user is still to have an ID issued by the database.

How do I go about this?

Custom resource routes

Hello, first of all thanks for sharing this great project with the public!

I'm trying to set up a custom resource route but can't find a way to do it properly. The routing documentation only shows the possibility to include or exclude controller actions of the predefined pool (index, create, read, etc.).

Is it possible to create a custom route which points to a custom controller action inside of the JsonApi::api( ... ) block? Or is this the wrong place for custom actions and the route should be defined "by hand" as classic Route::get( ... )?

Class api-v1 does not exist

Hi, I'm getting this error for some reason while trying to access the index route of a model.

ReflectionException in Container.php line 749:
Class api-v1 does not exist

I'm using laravel 5.3 and the routes are set up differently compared to the demo project.

RestrictiveQueryChecker (filter, sort, etc)

Hey again,

Just trying to figure out where to pass the rules for the RestrictiveQueryChecker in the underlying neomerx package?

I'm getting an error on ->checkFiltering etc, but I don't know where in the controller to allow filtering params?

Would be a good update to the docs if it's an easy answer :) ๐Ÿ‘

Resource idName

This is more of a clarification than an issue but still.

In the json-api config file, you can specify the column to use for the resource id. Or so I thought. I tried adding something like People\Schema::RESOURCE_TYPE => 'mynewid' with no luck.

The only way I could change the id used in the returned Json was by overriding theidName property in the resource Schema: protected $idName = 'mynewid';

This may be a newbie question, but did I misunderstood the use of the eloquent-adapter's columns array in the config file?

Cursor based pagination

I didn't find anything on a cursor based pagination which is allowed by the spec. This sort of pagination is very useful and common in an API, specially if coupled with an index of sort (alphabetical mostly):

  "meta": {
    "page": {
      "size": 20,
      "cursor": 0,
      "total": 10000,
      "index": {
        "A": 0,
        "B": 227,
        "C": 1136,
        ...
        "X": null,
        "Y": 9837,
        "Z": 9884,
        "#": null
      }
    }
  },
  "links": {
    "prev": null,
    "next": "http://jsonapi.dev/api/v1/people?page[cursor]=20&page[size]=20"
  },

How would you go about implementing this?
For now, I don't see another way than bypassing your pagination and using the filter method on the Search class to 1/ manually paginate via skip/take and 2/ clone the builder to create the alphabetical index. But then I don't see how to manually create the meta and links objects and pass them to the returned Json.

Any help would be much appreciated.

Documentation about validation of relationships object using getResourceObject

Hey there,
Forgive me if I read incorrectly, but I don't think there's any documentation about validating relationships as part of a new resource creation, such as the request body shown in the example under http://jsonapi.org/format/#document-resource-objects

The current solution I'm using is to manually construct the relationships validator and set it on the main resource validator. eg.

$validator = $this->getResourceObjectValidator(...)
$validator->setRelationshipsValidator(new RelationshipsValidator([
   'relationshipName' => $this->getHasOneValidator('relationship_type')
]);

Although I'm not 100% sure that this is the recommended method.
If you would like me to submit a change to README to that effect under content validation, I'm happy to do that too.

Test helper for relationships

I'm not sure if I haven't found them or if they do not exist yet. Are there any helpers to test the relationship retrieval/creation/update of an resource. I would expect something like doReadRelationship(). If thats missing (and not coming very soon) I would implement that on my own and can, of course, create a pull request..

Multiple filter values

I'm trying to filter my query through multiple filter values like so:

api/v1/questions?filter[questionType]=questionType1,questionType2

But apparently this is not supported out of the box. Any guidance on how to achieve this would be appreciated.

Filter in multiple properties of one resource

Hi!

I am newb and I was just wondering whether I do something the right way.
I want to search for one value across multiple properties of one resource. Or rather I want to retrieve all records containing one value in multiple filters of one resource.

I believe this would be a reasonable representation of the URL design of what I want to achieve:
api/v1/posts?filter[title,author,content]=somevalue

Of course this does not work, so I created a new filter parameter called all:
api/v1/posts?filter[all]=somevalue

I just added the all parameter to my allowed filters:

protected $allowedFilteringParameters = [
    'id',
    'title',
    'author',
    'content',

    // For querying all of the above
    'all',
];

And then I used orWhere clauses on my query builder:

// For querying all properties
if ($filters->has('all')) {
    $builder->where('posts.title', 'like', '%' .  $filters->get('all') . '%')
            ->orWhere('posts.author', 'like', '%' .  $filters->get('all') . '%')
            ->orWhere('posts.content', 'like', '%' .  $filters->get('all') . '%');
}

Now this works the way I want it to, but considering best practice and efficiency, Is this the right way to go about it?

How to include Laravel polymoprhic relationship as JSON API relationship?

(Update - I have made progress, check below before wasting time wading through this.)

What is the proper method for declaring a polymorphic relationship? I cannot figure this out and I cannot find an issue discussion on this.

So, in my case, I have an "Address" model which is used commonly for various assets, example "user", "building", "office", etc. Each of these assets may or may not have an "address" relationship object. In Laravel this is managed using its built-in polymorphic system, and each asset has a relationship called "addressable".

If I try to include the "address" data via the relationship on a parent object (ex. "building") I get an error like so:

[2017-04-22 16:20:20] local.ERROR: CloudCreativity\JsonApi\Exceptions\RuntimeException: Expecting address on App\Models\Building\Building to be a belongs-to relationship. in /home/vagrant/Code/hoistway-cms/vendor/cloudcreativity/laravel-json-api/src/Schema/CreatesEloquentIdentities.php:49

Here is a lot of code from my project to help with diagnostics, in case I am doing it wrong:

Buildings\Request:

<?php

namespace App\JsonApi\Buildings;

use CloudCreativity\JsonApi\Http\Requests\RequestHandler;

class Request extends RequestHandler
{

    /**
     * @var array
     */
    protected $hasOne = [
        'address'
    ];

    /**
     * @var array
     */
    protected $hasMany = [
        'devices'
    ];

    /**
     * @var array
     */
    protected $allowedSortParameters = [
        'name',
        'slug'
    ];

    /**
     * @var array
     */
    protected $allowedFilteringParameters = [
        'id',
        'name',
        'slug'
    ];

    /**
     * Request constructor.
     * @param Authorizer $authorizer
     * @param Validators $validator
     */
    public function __construct(Authorizer $authorizer, Validators $validator)
    {
        parent::__construct($authorizer, $validator);
    }

    /**
     * @return string
     */
    public function getResourceType()
    {
        return Schema::RESOURCE_TYPE;
    }
}

Buildings\Schema:

<?php

namespace App\JsonApi\Buildings;

use App\Models\Building\Building;
use CloudCreativity\JsonApi\Exceptions\RuntimeException;
use CloudCreativity\LaravelJsonApi\Schema\EloquentSchema;

class Schema extends EloquentSchema
{

    /**
     * @var string
     */
    const RESOURCE_TYPE = 'buildings';

    /**
     * @var array
     */
    protected $attributes = [
        'name',
        'slug'
    ];

    /**
     * @return string
     */
    public function getResourceType()
    {
        return self::RESOURCE_TYPE;
    }


    /**
     * @param object $resource
     * @param bool $isPrimary
     * @param array $includeRelationships
     * @return array
     */
    public function getRelationships($resource, $isPrimary, array $includeRelationships)
    {
        if (!$resource instanceof Building) {
            throw new RuntimeException('Expecting a Building model.');
        }

        return [
            'address' => [
                self::SHOW_SELF => true,
                self::SHOW_RELATED => true,
                self::DATA => isset($includeRelationships['address']) ?
                    $resource->address : $this->createBelongsToIdentity($resource, 'address'),
            ],
            'devices' => [
                self::SHOW_SELF => true,
                self::SHOW_RELATED => true,
                self::DATA => function () use ($resource) {
                    return $resource->devices;
                },
            ]
        ];
    }
}

Address\Request:

<?php

namespace App\JsonApi\Addresses;

use CloudCreativity\JsonApi\Http\Requests\RequestHandler;

class Request extends RequestHandler
{

    /**
     * @var array
     */
    protected $hasOne = [
        'city',
        'country-division',
        'country'
    ];

    /**
     * @var array
     */
    protected $hasMany = [
        //
    ];

    /**
     * @var array
     */
    protected $allowedSortParameters = [
        'street',
        'email',
        'latitude',
        'longitude',
    ];

    /**
     * @var array
     */
    protected $allowedFilteringParameters = [
        'street',
        'street_extra',
        'postal_code',
        'email',
        'phone_mobile',
        'phone_desk',
        'phone_company',
        'phone_fax'
    ];

    /**
     * Request constructor.
     * @param Authorizer $authorizer
     * @param Validators $validator
     */
    public function __construct(Authorizer $authorizer, Validators $validator)
    {
        parent::__construct($authorizer, $validator);
    }

    /**
     * @return string
     */
    public function getResourceType()
    {
        return Schema::RESOURCE_TYPE;
    }
}

Address\Schema:

<?php

namespace App\JsonApi\Addresses;

use App\Models\Address\Address;
use CloudCreativity\JsonApi\Exceptions\RuntimeException;
use CloudCreativity\LaravelJsonApi\Schema\EloquentSchema;

class Schema extends EloquentSchema
{

    /**
     * @var string
     */
    const RESOURCE_TYPE = 'addresses';

    /**
     * @var array
     */
    protected $attributes = [
        'street',
        'street_extra',
        'postal_code',
        'email',
        'phone_mobile',
        'phone_desk',
        'phone_company',
        'phone_fax',
        'latitude',
        'longitude',
        'place_id',
    ];

    /**
     * @return string
     */
    public function getResourceType()
    {
        return self::RESOURCE_TYPE;
    }


    /**
     * @param object $resource
     * @param bool $isPrimary
     * @param array $includeRelationships
     * @return array
     */
    public function getRelationships($resource, $isPrimary, array $includeRelationships)
    {
        if (!$resource instanceof Address) {
            throw new RuntimeException('Expecting an Address model.');
        }

        return [
            'city' => [
                self::SHOW_SELF => true,
                self::SHOW_RELATED => true,
                self::SHOW_DATA => true,
                self::DATA => $resource->city,
            ],
            'country' => [
                self::SHOW_SELF => true,
                self::SHOW_RELATED => true,
                self::SHOW_DATA => true,
                self::DATA => $resource->country,
            ],
            'country-division' => [
                self::SHOW_SELF => true,
                self::SHOW_RELATED => true,
                self::SHOW_DATA => true,
                self::DATA => $resource->division,
            ],
            'addressable' => [
                self::SHOW_SELF => true,
                self::SHOW_RELATED => true,
                self::DATA => isset($includeRelationships['addressable']) ?
                    $resource->addressable : $this->createBelongsToIdentity($resource, 'addressable'),
            ]
        ];
    }
}

config\json-api.php (trimmed down for presentation)

<?php

namespace App\Models;

use \App\JsonApi;

return [

    ...
    'schemas' => [
        'defaults' => [
            Address\Address::class => JsonApi\Addresses\Schema::class,
            Building\Building::class => JsonApi\Buildings\Schema::class,
            Address\City::class => JsonApi\Cities\Schema::class,
            Address\Country::class => JsonApi\Countries\Schema::class,
            Address\CountryDivision::class => JsonApi\CountryDivisions\Schema::class,
            Building\Device::class => JsonApi\Devices\Schema::class,
            Building\DeviceType::class => JsonApi\DeviceTypes\Schema::class,
        ],
        'v1' => [],
    ],

    'eloquent-adapter' => [
        'map' => [
            JsonApi\Addresses\Schema::RESOURCE_TYPE => Address\Address::class,
            JsonApi\Buildings\Schema::RESOURCE_TYPE => Building\Building::class,
            JsonApi\Cities\Schema::RESOURCE_TYPE => Address\City::class,
            JsonApi\Countries\Schema::RESOURCE_TYPE => Address\Country::class,
            JsonApi\CountryDivisions\Schema::RESOURCE_TYPE => Address\CountryDivision::class,
            JsonApi\Devices\Schema::RESOURCE_TYPE => Building\Device::class,
            JsonApi\DeviceTypes\Schema::RESOURCE_TYPE => Building\DeviceType::class,
        ],
        'columns' => [],
    ],
];

Pagination in readRelatedResource

I can't figure out an way of doing pagination of a related resource.

EloquentController#readRelatedResource

        return $this
            ->reply()
            ->content($model->{$key});

"Method Not Allowed" on POST request

I am really struggling to get a POST request to create a new model entry to work. I get "405 Method Not Allowed" no matter what I do.

Note I am using Laravel 5.3 and trying to use Passport with v0.6.2 of this API package on a Homestead development environment. When using Postman to test, if I have the Passport auth API disabled then a GET request works but with Passport on (and no auth effort attempted in Postman) it refuses. This is expected and appears to show that Passport is working as expected.

So now I am trying to consume the API within my website itself per one of the features of Passport. Here, a GET request works but a POST request to the same resource gets "method not allowed". The "X-CSRF-TOKEN" and "X-XSRF-TOKEN" is being included with every request.

Laravel docs mention a "laravel_token" baked in to the session cookie I can see it is provided with each session but is not included in a request cookie or the headers, and for the life of me I cannot figure out how or if I am meant to return this on a request. I can find almost nothing about "laravel_token" online.

https://laravel.com/docs/5.3/passport#consuming-your-api-with-javascript

Any thoughts on where to start?

Code references will follow.

[Proposal] Route Registration Refactor

Resource Route Changes

This change will modify route registration and remove the need for the Request class.

The new route registration will look like this:

Route::group(['middleware' => 'json-api:v1'], function () {
    JsonApi::resource('posts', [
        'has-one' => ['author'],
        'has-many' => ['commments'],
    ]);
});

Our intention is to remove the Request class to reduce the number of units that need to be generated per resource.
We are moving its logic to middleware, and the properties on it that affect validation of query parameters are being moved to the Validators class.

Controllers

The controller used for each resource will be the resource name plus Controller. For example, posts will be mapped to PostsController.

To override the controller, the following is used:

Route::group(['middleware' => 'json-api:v1'], function () {
    JsonApi::resource('posts', [
        'controller' => 'SomeOtherController',
        'has-one' => ['author'],
        'has-many' => ['commments'],
    ]);
});

Authorizers

The route register will automatically register the JSON API Authorizer to use for the resource type.
This will be based on the resource name and the namespace and by-resource settings in the json-api config file.

For example, with a namespace of App\JsonApi and by-resource as true the following class will be used as the authorizer if it exists:
App\JsonApi\Posts\Authorizer.

If by-resource is false then this class would be used:
App\JsonApi\Authorizers\Posts

To specify an authorizer that is different from this logic:

Route::group(['middleware' => 'json-api:v1'], function () {
    JsonApi::resource('posts', [
        'authorizer' => \App\JsonApi\GenericAuthorizer::class,
        'has-one' => ['author'],
        'has-many' => ['commments'],
    ]);
});

Validators

These will also be registered using the same logic as described for the authorizer.

For example, with a namespace of App\JsonApi and by-resource as true the following class will be used as the validators if it exists:
App\JsonApi\Posts\Validators.

If by-resource is false then this class would be used:
App\JsonApi\Validators\Posts

To specify validators that are different from this logic:

Route::group(['middleware' => 'json-api:v1'], function () {
    JsonApi::resource('posts', [
        'validators' => \App\JsonApi\GenericValidators::class,
        'has-one' => ['author'],
        'has-many' => ['commments'],
    ]);
});

Id Constraints

To add an id constraint for a particular resource:

Route::group(['middleware' => 'json-api:v1'], function () {
    JsonApi::resource('posts', [
        'has-one' => ['author'],
        'has-many' => ['commments'],
        'id' => '[1-9]{1}[0-9]*'
    ]);
});

To add an id constraint for all resources, a global pattern can already be registered for resource_id.

Internal Implementation

This:

Route::group(['middleware' => 'json-api:v1'], function () {
    JsonApi::resource('posts', [
        'has-one' => ['author'],
        'has-many' => ['commments'],
    ]);
});

Is equivalent to this:

Route::group(['middleware' => 'json-api:v1'], function () {
    Route::group(['prefix' => '/posts', 'middleware' => [
        'json-api.authorize:\App\JsonApi\Posts\Authorizer',
        'json-api.validate:\Appi\JsonApi\Posts\Validators',
    ]], function () {
        Route::get('/', 'PostsController@index');
        Route::post('/', 'PostsController@create');
        Route::get('/{resource_id}', 'PostsController@read');
        Route::patch('/{resource_id}', 'PostsController@update');
        Route::delete('/{resource_id}', 'PostController@delete');

        // has-one and has-many:
        Route::get('/{resource_id}/{relationship_name}', 'PostsController@readRelatedResource')->where('relationship_name', 'author|comments');
        Route::get('/{resource_id}/relationships/{relationship_name}', 'PostsController@readRelationship')->where('relationship_name', 'author|comments');
        Route::patch('/{resource_id}/relationships/{relationship_name}', 'PostsController@replaceRelationship')->where('relationship_name', 'author|comments');
        // has-many only:
        Route::post('/{resource_id}/relationships/{relationship_name}', 'PostsController@addToRelationship')->where('relationship_name', 'comments');
        Route::delete('/{resource_id}/relationships/{relationship_name}', 'PostsController@removeFromRelationship')->where('relationship_name', 'comments');
    });
});

The above also needs to push the resource_type param into the defaults for each route so that the request interpreter knows which resource the request relates to.

Note that the json-api.authorizer and json-api.validators middleware will only be added to the group if those classes exist.

Include related resources

Our app have a fair amount of hasMany relationsships - and we are including them in our json-api responses - resulting eg. 5 queries pr. entity; and with 200 entities we have a bottleneck.

I were thinking about overwrite the EloquentAdapter#find method and do my own service-provide to register with my own class.

But is it the best way to handle it? It seems as a generic problem, and a standardized way of handling relationships.

Method for pagination on related resource?

Per the subject, I can't figure how to do this. I would like to force pagination, however even user-requested pagination is not respected. Am I mistaken?

For example, given this resource...
http://demo.dev/api/v1/countries

... and this relationship URL...
http://demo.dev/api/v1/countries/CA/cities?page%5Bnumber%5D=1&page%5Bsize%5D=15

...pagination is not performed. I additionally wish to force pagination but see no method.

Might I suggest that pagination be determined on the Schema instead of the Search class? Then the pagination rules for the given asset are applied on relationships, including when the top-level ID data is included within 'relationships'.

Similarly, a global setting for pagination rules would be swell.

JsonApiRequest Feature

Add a feature so that JSON API validation is run in a way similar to the FormRequest feature provided by the Laravel framework.

This would mean that all validation is taken off the controller and isolated into a JsonApiRequest class instead - which would be a lot nicer implementation.

Relationship links not working

When trying to access the relationship link like so
/api/v1/answers/1/relationships/question

I get a 404 status code.

{
  "errors": []
}

Is there something I am missing?

GET|HEAD | 
api/v1/answers/{resource_id}/relationships/{relationship_name}   |
api-v1::answers.relationships   |
App\Http\Controllers\Api\AnswersController@readRelationship

The route is registered correctly but it is not hitting the readRelationship function.

EloquentController does not need to extend JsonApiController

There is little benefit to EloquentController extending JsonApiController, particular as the EloquentController is not using any of the method signatures such as $resourceId. It would make more sense to combine standard controller helpers into a trait and use this in both EloquentController and JsonApiController.

This would also make it easier for people to write their own controllers as they could then just apply the trait if for some reason the method signatures of JsonApiController cannot be used. An example of this is when using wildcard sub-domains, each method on that controller would actually get called with the sub-domain variable first so JsonApiController would not be easy to use.

Method for using Laravel native Policies for API authorization?

Per the subject...

Laravel uses five method calls in the policy classes to describe permissions: index, create, view, update, delete. These mean the same thing as your five in this package. I'd rather not separate policy settings because of a concern that policy changes in one space won't be replicated to the other.

Any thoughts or suggestions?

Is it possible to include nested relationships?

Is it possible to fetch nested relationship. lets say I am having three resource User, Roles, and Permissions. Permissions have reference of Roles and Roles have reference of Users. I want to Fetch a User along-with its all Permissions referenced against all of its Roles.

What I was trying to pass a query parameter http://myapi.com/v1/users?include=roles.permissions because in laravel nested relationships are fetched like this. This is giving me error of not allowed include.

I was looking if there is any work around, Otherwise what I have to do is:

  • Fetching User in first request.
  • Loop through all of its roles and send request to fetch their permissions one by one.

ps: I have considered users, roles, permissions example to make it simple. I am implementing this thing against some complex data models which do have hundreds of relationship records.

Thanks
Furqan Aziz

with not being called in single record request

Hey guys, I have been using the package for a while and love all the updates. One thing I am having problems with is the with function in the Adapter class. I believe this is supposed to be called on index and read, however I am only able to load the include relationships on an index call. This is causing a lot of N+1 queries in my application.

Here is my Adapter

<?php namespace App\JsonApi\Auctions;

use App\Models\Auction;
use Carbon\Carbon;
use CloudCreativity\LaravelJsonApi\Pagination\StandardStrategy;
use CloudCreativity\LaravelJsonApi\Store\EloquentAdapter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;

class Adapter extends EloquentAdapter
{

    /**
     * Adapter constructor.
     * @param StandardStrategy $paging
     */
    public function __construct(StandardStrategy $paging)
    {
        parent::__construct(new Auction(), $paging);
    }

    /**
     * Apply the supplied filters to the builder instance.
     *
     * @param Builder $query
     * @param Collection $filters
     * @return void
     */
    protected function filter(Builder $query, Collection $filters)
    {
        if (!$filters->has('ended')) {
            $query->where('ends_at', '>', Carbon::now('UTC'));
        }
        $query->orderBy('ends_at', 'asc');

    }

    protected function with(Builder $query, Collection $includePaths)
    {
        if($includePaths->contains('location')) {
            $query->with('location');
        }
    }


    /**
     * Is this a search for a singleton resource?
     *
     * @param Collection $filters
     * @return bool
     */
    protected function isSearchOne(Collection $filters)
    {
        return false;
    }

}

And my schema

<?php namespace App\JsonApi\Auctions;

use Carbon\Carbon;
use CloudCreativity\LaravelJsonApi\Schema\EloquentSchema;

class Schema extends EloquentSchema
{
    protected $resourceType = "auctions";

    protected $attributes = [
        'name',
        'removal',
        'highlights',
        'notes',
        'terms',
        'contact',
        'ends_at',
    ];

    protected $dateFormat = Carbon::ISO8601;

    /**
     * @param object $resource
     * @param bool $isPrimary
     * @param array $includeRelationships
     * @return array
     */
    public function getRelationships($resource, $isPrimary, array $includeRelationships)
    {
        $return = [];
        if (isset($includeRelationships['items'])) {
            $return['items'] = [
                self::SHOW_SELF => true,
                self::SHOW_RELATED => true,
                self::DATA => $resource->items,
            ];
        }

        $return['location'] = [
            self::SHOW_SELF => true,
            self::SHOW_RELATED => true,
            self::DATA => isset($includeRelationships['location']) ? $resource->location : $this->createBelongsToIdentity($resource, 'location'),
        ];

        return $return;
    }
}

Include eloquent model on included

Hey guys, ive been struggling with these for an hour or so now. I have my searcher returning my "Tracks" model, with its tag IDs included. It's not including the tag data, such as its name, in the response. Is this possible?

This is all I am doing:

    return [
            'tags' => [
                self::DATA         => $resource->tags,
            ]
        ];

Laravel 5.3 support

It would be nice to get an update with support for Laravel 5.3. Looks like the service provider needs to have it's dependency injection removed from the method signature.

https://laravel.com/docs/5.3/upgrade

We're currently using v0.2, so if an update could be issued to this version, that would be great.

Error: Include paths should contain only allowed ones.

Hi, I'm playing with the demo and can't figure out why I can't use the "include" query string, for example if I call this:

/api/v1/posts/1?include=comments

I get:

{
    "errors": [
        {
            "title": "Include paths should contain only allowed ones.",
            "source": {
                "parameter": "include"
            }
        }
    ]
}

I can't find where to define the mentioned allowed include paths. Is this a bug? or lack of documentation, Thanks.

AbstractSearch to support use of with by default

AbstractSearch should have a with method that receives the builder instance. By default, this should apply the contents of $this->with (a new property) if that property is not empty.

The with method should be invoked for both a standard search and a find many search.

This will encourage use of eager loading with searches, which is best practice because of the inefficiencies of not using eager loading when returning multiple JSON API resources at once.

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.