Git Product home page Git Product logo

json-api's Introduction

JSON:API Resource: a Laravel package by Tim MacDonald

JSON:API Resource for Laravel

A lightweight API resource for Laravel that helps you adhere to the JSON:API standard with support for sparse fieldsets, compound documents, and more baked in.

Note These docs are not designed to introduce you to the JSON:API specification and the associated concepts, instead you should head over and read the specification if you are not yet familiar with it. The documentation that follows only covers how to implement the specification via the package.

Table of contents

Version support

  • PHP: 8.1, 8.2
  • Laravel: 9.0, 10.0

Installation

You can install using composer from Packagist.

composer require timacdonald/json-api

Getting started

The JsonApiResource class provided by this package is a specialisation of Laravel's Eloquent API resource. All public facing APIs are still accessible; in a controller, for example, you interact with a JsonApiResource as you would with Laravel's JsonResource class.

<?php

namespace App\Http\Controllers;

use App\Http\Resources\UserResource;
use App\Models\User;

class UserController
{
    public function index()
    {
        $users = User::with([/* ... */])->paginate();

        return UserResource::collection($users);
    }

    public function show(User $user)
    {
        $user->load([/* ... */]);

        return UserResource::make($user);
    }
}

As we make our way through the examples you will see that new APIs are introduced when interacting with the class internally, for example, the toArray() method is no longer used.

Creating your first JSON:API resource

To get started, let's create a UserResource for our User model. In our user resource will expose the user's name, website, and twitter_handle in the response.

First we will create a new API resource that extends TiMacDonald\JsonApi\JsonApiResource.

<?php

namespace App\Http\Resources;

use TiMacDonald\JsonApi\JsonApiResource;

class UserResource extends JsonApiResource
{
    //
}

Adding attributes

We will now create an $attributes property and list the model's attributes we want to expose.

<?php

namespace App\Http\Resources;

use TiMacDonald\JsonApi\JsonApiResource;

class UserResource extends JsonApiResource
{
    public $attributes = [
        'name',
        'website',
        'twitter_handle',
    ];
}

When making a request to an endpoint that returns the UserResource, for example:

Route::get('users/{user}', fn (User $user) => UserResource::make($user));

The following JSON:API formatted data would be returned:

{
  "data": {
    "type": "users",
    "id": "74812",
    "attributes": {
      "name": "Tim",
      "website": "https://timacdonald.me",
      "twitter_handle": "@timacdonald87"
    }
  }
}

🎉 You have just created your first JSON:API resource 🎉

Congratulations...and what. a. rush!

We will now dive into adding relationships to your resources, but if you would like to explore more complex attribute features you may like to jump ahead:

Adding relationships

Available relationships may be specified in a $relationships property, similar to the $attributes property, however you may use a key / value pair to provide the resource class that should be used for the given relationship.

We will make two relationships available on the resource:

  • $user->team: a "toOne" / HasOne relationship.
  • $user->posts: a "toMany" / HasMany relationship.
<?php

namespace App\Http\Resources;

use TiMacDonald\JsonApi\JsonApiResource;

class UserResource extends JsonApiResource
{
    public $attributes = [
        'name',
        'website',
        'twitter_handle',
    ];

    public $relationships = [
        'team' => TeamResource::class,
        'posts' => PostResource::class,
    ];
}

Assuming the key / value pair follows the convention '{myKey}' => {MyKey}Resource::class, the class may be omitted to streamline things further.

<?php

namespace App\Http\Resources;

use TiMacDonald\JsonApi\JsonApiResource;

class UserResource extends JsonApiResource
{
    public $attributes = [
        'name',
        'website',
        'twitter_handle',
    ];

    public $relationships = [
        'team',
        'posts',
    ];
}
Example request and response

The client may now request these relationships via the include query parameter.

GET /users/74812?include=posts,team

Note Relationships are not exposed in the response unless they are requested by the calling client via the include query parameter. This is intended and is part of the JSON:API specification.

{
  "data": {
    "id": "74812",
    "type": "users",
    "attributes": {
      "name": "Tim",
      "website": "https://timacdonald.me",
      "twitter_handle": "@timacdonald87"
    },
    "relationships": {
      "posts": {
        "data": [
          {
            "type": "posts",
            "id": "25240"
          },
          {
            "type": "posts",
            "id": "39974"
          }
        ]
      },
      "team": {
        "data": {
          "type": "teams",
          "id": "18986"
        }
      }
    }
  },
  "included": [
    {
      "id": "25240",
      "type": "posts",
      "attributes": {
        "title": "So what is `JSON:API` all about anyway?",
        "content": "...",
        "excerpt": "..."
      }
    },
    {
      "id": "39974",
      "type": "posts",
      "attributes": {
        "title": "Building an API with Laravel, using the `JSON:API` specification.",
        "content": "...",
        "excerpt": "..."
      }
    },
    {
      "id": "18986",
      "type": "teams",
      "attributes": {
        "name": "Laravel"
      }
    }
  ]
}

To learn about more complex relationship features you may like to jump ahead:

A note on eager loading

This package does not eager load Eloquent relationships. If a relationship is not eagerly loaded, the package will lazy load the relationship on the fly. I highly recommend using Spatie's query builder package which will eager load your models against the JSON:API query parameter standards.

Spatie provide comprehensive documentation on how to use the package, but I will briefly give an example of how you might use this in a controller.

<?php

namespace App\Http\Controllers;

use App\Http\Resources\UserResource;
use App\Models\User;
use Spatie\QueryBuilder\QueryBuilder;

class UserController
{
    public function index()
    {
        $users = QueryBuilder::for(User::class)
            ->allowedIncludes(['team', 'posts'])
            ->paginate();

        return UserResource::collection($users);
    }

    public function show($id)
    {
        $user = QueryBuilder::for(User::class)
            ->allowedIncludes(['team', 'posts'])
            ->findOrFail($id);

        return UserResource::make($user);
    }
}

Digging deeper

We have now covered the basics of exposing attributes and relationships on your resources. We will now cover more advanced topics to give you even greater control.

Attributes

toAttributes()

As we saw in the adding attributes section, the $attributes property is the fastest way to expose attributes for a resource. In some scenarios you may need greater control over the attributes you are exposing. If that is the case, you may implement the toAttributes() method. This will grant you access to the current request and allow for conditional logic.

<?php

namespace App\Http\Resources;

use TiMacDonald\JsonApi\JsonApiResource;

class UserResource extends JsonApiResource
{
    /**
     * @param  \Illuminate\Http\Request  $request
     * @return array<string, mixed>
     */
    public function toAttributes($request)
    {
        return [
            'name' => $this->name,
            'website' => $this->website,
            'twitter_handle' => $this->twitter_handle,
            'email' => $this->when($this->email_is_public, $this->email, '<private>'),
            'address' => [
                'city' => $this->address('city'),
                'country' => $this->address('country'),
            ],
        ];
    }
}
Example response
{
  "data": {
    "id": "74812",
    "type": "users",
    "attributes": {
      "name": "Tim",
      "website": "https://timacdonald.me",
      "twitter_handle": "@timacdonald87",
      "email": "<private>",
      "address": {
        "city": "Melbourne",
        "country": "Australia"
      }
    }
  }
}

Sparse fieldsets

Sparse fieldsets are a feature of the JSON:API specification that allows clients to specify which attributes, for any given resource type, they would like to receive. This allows for more deterministic responses, while also improving server-side performance and reducing payload sizes. Sparse fieldsets work out of the box for your resources.

We will cover them briefly here, but we recommend reading the specification to learn more.

As an example, say we are building out an index page for a blog. The page will show each post's title and excerpt, and also the name of the post's author. If the client wishes, they may limit the response to only include the required attributes for each resource type, and exclude the other attributes, such as the post's content and the authors twitter_handle.

To achieve this we will send the following request.

GET /posts?include=author&fields[posts]=title,excerpt&fields[users]=name

Note The include query parameter key is author, while the sparse fieldset parameter key is users. This is because authors are users, e.g. the Eloquent author() relationship returns a User model.

Example response
{
  "data": [
    {
      "id": "25240",
      "type": "posts",
      "attributes": {
        "title": "So what is `JSON:API` all about anyway?",
        "excerpt": "..."
      },
      "relationships": {
        "author": {
          "data": {
            "type": "users",
            "id": "74812"
          }
        }
      }
    },
    {
      "id": "39974",
      "type": "posts",
      "attributes": {
        "title": "Building an API with Laravel, using the `JSON:API` specification.",
        "excerpt": "..."
      },
      "relationships": {
        "author": {
          "data": {
            "type": "users",
            "id": "74812"
          }
        }
      }
    }
  ],
  "included": [
    {
      "type": "users",
      "id": "74812",
      "attributes": {
        "name": "Tim"
      }
    }
  ]
}

Minimal attributes

Resources return a maximal attribute payload when sparse fieldsets are not in use i.e. all declared attributes on the resource are returned. If you prefer you can make the use of sparse fieldsets required in order to retrieve any attributes.

You may call the useMinimalAttributes() method in an application service provider.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use TiMacDonald\JsonApi\JsonApiResource;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        JsonApiResource::useMinimalAttributes();

        // ...
    }
}

Lazy attribute evaluation

For attributes that are expensive to calculate, it is possible to have them evaluated only when they are to be included in the response, i.e. they have not been excluded via sparse fieldsets or minimal attributes. This may be useful if you are interacting with a database or making HTTP requests in a resource.

As an example, let's imagine that we expose a base64 encoded avatar for each user. Our implementation downloads the avatar from our in-house avatar microservice.

<?php

namespace App\Http\Resources;

use Illuminate\Support\Facades\Http;
use TiMacDonald\JsonApi\JsonApiResource;

class UserResource extends JsonApiResource
{
    /**
     * @param  \Illuminate\Http\Request  $request
     * @return array<string, mixed>
     */
    public function toAttributes($request)
    {
        return [
            // ...
            'avatar' => Http::get("https://avatar.example.com/{$this->id}")->body(),
        ];
    }
}

The above implementation would make a HTTP request to our microservice even when the client is excluding the avatar attribute via sparse fieldsets or minimal attributes. To improve performance when this attribute is not being returned we can wrap the value in a Closure. The Closure will only be evaluated when the avatar is to be returned.

<?php

namespace App\Http\Resources;

use Illuminate\Support\Facades\Http;
use TiMacDonald\JsonApi\JsonApiResource;

class UserResource extends JsonApiResource
{
    /**
     * @param  \Illuminate\Http\Request  $request
     * @return array<string, mixed>
     */
    public function toAttributes($request)
    {
        return [
            // ...
            'avatar' => fn () => Http::get("https://avatar.example.com/{$this->id}")->body(),
        ];
    }
}

Relationships

toRelationships()

As we saw in the adding relationships section, the $relationships property is the fastest way to specify the available relationships for a resource. In some scenarios you may need greater control over the relationships you are making available. If that is the case, you may implement the toRelationships() method. This will grant you access to the current request and allow for conditional logic.

The value must always be wrapped in a Closure, which will only be called if the relationships is requested by the client.

<?php

namespace App\Http\Resources;

use TiMacDonald\JsonApi\JsonApiResource;

class UserResource extends JsonApiResource
{
    /**
     * @param  \Illuminate\Http\Request  $request
     * @return array<string, (callable(): \TiMacDonald\JsonApi\JsonApiResource|\TiMacDonald\JsonApi\JsonApiResourceCollection|\Illuminate\Http\Resources\PotentiallyMissing)>
     */
    public function toRelationships($request)
    {
        return [
            'team' => fn () => TeamResource::make($this->team),
            'posts' => fn () => $request->user()->is($this->resource)
                ? PostResource::collection($this->posts)
                : PostResource::collection($this->posts->where('published', true)),
        ];
    }
}

Customising the relationship resource class guessing

//----- Everything that follows is WIP and should be ignored ------- //

Resource Identification

[JSON:API docs: Identification](https://jsonapi.org/format/#document-resource-object-identification)

We have defined a sensible default for you so you can hit the ground running without having to fiddle with the small stuff.

The "id" and "type" of a resource is automatically resolved for you under-the-hood if you are using resources solely with Eloquent models.

"id" is resolved by calling the $model->getKey() method and the "type" is resolved by using a camel case of the model's table name, e.g. blog_posts becomes blogPosts.

You can customise how this works to support other types of objects and behaviours, but that will follow in the advanced usage section.

Nice. Well that was easy, so let's move onto...

Resource Links

[JSON:API docs: Links](https://jsonapi.org/format/#document-resource-object-links)

To provide links for a resource, you can implement the toLinks($request) method...

<?php

use TiMacDonald\JsonApi\Link;

class UserResource extends JsonApiResource
{
    public function toLinks($request): array
    {
        return [
            Link::self(route('users.show', $this->resource)),
            'related' => 'https://example.com/related'
        ];
    }
}

Resource Meta

[JSON:API docs: Meta](https://jsonapi.org/format/#document-meta)

To provide meta information for a resource, you can implement the toMeta($request) method...

<?php

class UserResource extends JsonApiResource
{
    public function toMeta($request): array
    {
        return [
            'resourceDeprecated' => true,
        ];
    }
}

Advanced usage

Resource Identification

Customising the resource "id"

You can customise the resolution of the id by specifying an id resolver in your service provider.

<?php

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        JsonApiResource::resolveIdUsing(function (mixed $resource, Request $request): string {
            // your custom resolution logic...
        });
    }
}

Although it is not recommended, you can also override the toId(Request $request): string method on a resource by resource basis.

Customising the resource "type"

You can customise the resolution of the type by specifying a type resolver in your service provider.

<?php

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        JsonApiResource::resolveTypeUsing(function (mixed $resource, Request $request): string {
            // your custom resolution logic...
        });
    }
}

Although it is not recommended, you can also override the toType(Request $request): string method on a resource by resource basis.

Resource Relationships

[JSON:API docs: Inclusion of Related Resources](https://jsonapi.org/format/#fetching-includes)

Relationships can be resolved deeply and also multiple relationship paths can be included. Of course you should be careful about n+1 issues, which is why we recommend using this package in conjunction with Spatie's Query Builder.

# Including deeply nested relationships
/api/posts/8?include=author.comments

# Including multiple relationship paths
/api/posts/8?include=comments,author.comments
  • Using "whenLoaded is an anti-pattern"

Credits

And a special (vegi) thanks to Caneco for the logo ✨

v1 todo

  • Server implementation rethink.
  • Rethink naming of objects and properties
  • Camel case everything
  • Allow resources to specify their JsonResource class.
  • Make all caches WeakMaps.
  • Things that "must" need to be first in the __consstructor. See Links:href
  • Should it be withResourceIdentifier or mapResourceIdentifier. Feel like we are mapping. or pipeResourceIdentifier
  • Should all caches use weakmap with request key?

json-api's People

Contributors

caneco avatar dannyvdsluijs avatar dependabot[bot] avatar fadkeabhi avatar laravel-shift avatar mbanusic avatar snellingio avatar timacdonald 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

json-api's Issues

Is the response following v1.0 or ?

I just realised that all responses have this:

"jsonapi": {
        "version": "1.0"
    }


does this mean that the package uses v1.0 of the specifications ?

laravel 10

I try to install it on Laravel 10 but:
Problem 1
- timacdonald/json-api[v0.1.0, ..., v0.2.0] require laravel/framework ^8.0 -> found laravel/framework[v8.0.0, ..., v8.83.27] but it conflicts with your root composer.json require (^10.10).
- timacdonald/json-api v0.2.1 requires laravel/framework ^8.0 || ^9.0 -> found laravel/framework[v8.0.0, ..., v8.83.27, v9.0.0, ..., v9.52.16] but it conflicts with your root composer.json require (^10.10).
- Root composer.json requires timacdonald/json-api * -> satisfiable by timacdonald/json-api[v0.1.0, ..., v0.2.1].

JsonApiServerImplementation 'final'`, doesn't allow custom implementations

JsonApiServerImplementation class is a simple DTO that keeps jsonapi server information.
There are cases, where some schemas need to declare some profiles etc. Unfortunately, the usage of final keyword, prevents any extension in order to support profile & extension properties.
It would be fine to either remove the final keyword, or support the extra properties JSON:API defines

final class JsonApiServerImplementation implements JsonSerializable

Doesn't show the attributes of a relationship

Hi Tim.

Do you have any idea why don't the attributes of a relationship? I'm testing two API's and both with the same result. I follow the instructions but doesn't work.
12-4-2022 20 4 35 1

I would appreciate any help

Relationship data is not shown as expected

I'm not sure If I'm missing somthing or this is a bug.

I have resource with these releationships:

 public array $relationships = [
        'activities' => ActivityResource::class,
        'recipients' => UserDocRecipientResource::class,
        'placeholders' => UserDocPlaceholderGroupResource::class,
        'placeholderGroups' => UserDocPlaceholderGroupResource::class,
        'emailTemplate' => UserEmailTemplateResource::class,
        'integration' => UserIntegrationResource::class,
        'user' => UserResource::class,
    ];

I than have a specific endpoint to get activities and the response looks like this:

{
    "success": true,
    "data": [
        {
            "id": "368",
            "type": "activity",
            "attributes": {
                "description": "The document was created",
                "created_at": "2023-10-09T14:33:15.000000Z"
            }
        },
        {
            "id": "369",
            "type": "activity",
            "attributes": {
                "description": "Document has been send to receiver for review and signature",
                "created_at": "2023-10-09T14:33:15.000000Z"
            }
        }
    ],
    "message": "ok",
    "code": 200
}

which is correct

but when I try to include the same data when getting the parent' the related data looks different

the call looks like this:
/api/documents/:userDoc_id/show?include=activities

{
    "success": true,
    "data": {
        "id": "9a5425a5-6bcc-4069-a239-1d17baeda096",
        "type": "document",
        "attributes": {
           // Attributes removes as they are not relevant
        },
        "relationships": {
            "activities": {
                "data": [
                    {
                        "type": "activity",
                        "id": "368"
                    },
                    {
                        "type": "activity",
                        "id": "369"
                    }
                ]
            }
        }
    },
    "message": "ok",
    "code": 200
}

I would have expected to see the full data of the activities when using the include

To Many relationship wrong wrapped?

Hi,

Im using a relationship that is an collection. If i look into the response relationship attributes i get
"relationships": { "order_lines": [ { "data": { "id": "12", "type": "orderLines", "meta": [] }, "meta": [], "links": [] } ]

When looking into the json spec it should be like

"comments": { "data": [ { "type": "comments", "id": "5" }, { "type": "comments", "id": "12" } ] }

Why is it wrapping it with an array instead of wrapping the data with the array? Am i missing something or doing it wrong? Or just a bug

Using with non Model resources.

We made a JsonApiResource for our settings class (from spatie/laravel-settings)

/** @property-read \Spatie\LaravelSettings\Settings $resource */
class SettingResource extends JsonApiResource
{
    public function toId(Request $request): string
    {
        return $this->resource::group();
    }

    public function toType(Request $request): string
    {
        return 'settings';
    }

    public function toAttributes(Request $request): array
    {
        return $this->resource->toArray();
    }
}

This used to work before, but when we updated to v1.0.0-beta.2 it broke. Here is the stack trace:

ErrorException: Undefined property: App\Settings\SiteSettings::$attributes in /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/spatie/laravel-settings/src/Settings.php:92
  Stack trace:
  #0 /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php(268): Illuminate\Foundation\Bootstrap\HandleExceptions->handleError()
  #1 /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/spatie/laravel-settings/src/Settings.php(92): Illuminate\Foundation\Bootstrap\HandleExceptions->Illuminate\Foundation\Bootstrap\{closure}()
  #2 /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/laravel/framework/src/Illuminate/Http/Resources/DelegatesToResource.php(139): Spatie\LaravelSettings\Settings->__get()
  #3 /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/timacdonald/json-api/src/Concerns/Attributes.php(73): Illuminate\Http\Resources\Json\JsonResource->__get()
  #4 /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/timacdonald/json-api/src/Concerns/Attributes.php(60): TiMacDonald\JsonApi\JsonApiResource->resolveAttributes()
  #5 /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/timacdonald/json-api/src/JsonApiResource.php(129): TiMacDonald\JsonApi\JsonApiResource->requestedAttributes()
  #6 /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/laravel/framework/src/Illuminate/Http/Resources/Json/JsonResource.php(95): TiMacDonald\JsonApi\JsonApiResource->toArray()
  #7 /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/laravel/framework/src/Illuminate/Http/Resources/Json/ResourceResponse.php(39): Illuminate\Http\Resources\Json\JsonResource->resolve()
  #8 /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/laravel/framework/src/Illuminate/Http/Resources/Json/JsonResource.php(231): Illuminate\Http\Resources\Json\ResourceResponse->toResponse()
  #9 /home/halcyon-pc75/code/laravel/saas-ecommerce/vendor/timacdonald/json-api/src/JsonApiResource.php(187): Illuminate\Http\Resources\Json\JsonResource->toResponse()

Its trying to get the $attribute of the settings class but that doesn't exists.

private function resolveAttributes(Request $request)
{
return Collection::make($this->attributes)
->mapWithKeys(fn (string $attribute, int|string $key): array => [
$attribute => fn () => $this->resource->{$attribute},
])
->merge($this->toAttributes($request));
}

`include` without query string

Currently, there is not a way to include relationships without explicitly asking for them. While this makes sense in almost all cases, I have a unique case where on a specific resource I want to include some morphTo's in the include by default.

Where includes happen:

public function parse(Request $request, string $prefix): Collection

Options could be adding a default to the $request->query('include'), which would work for my use case, but probably wouldn't be flexible enough for most people where they would want to ALWAYS have a default include even when passing in more.

I'll think a little bit about it, but I imagine that @timacdonald will have a better / specific implementation that he wants. If you can describe the best way to do it, I could take a shot at it.

Relationship attributes are not displayed in the response

I'm using Laravel 10 an spatie/laravel-query-builder

 {
    "id": "2",
    "type": "tickets",
    "attributes": {
      "code": "64a2ec21d75e4",
      "total_amount": "75.00",
      "created_at": "2023-07-03T15:41:21.000000Z",
      "updated_at": "2023-07-03T15:41:59.000000Z"
    },
    "relationships": {
      "user": {
       // The key attributes is not present 
        "data": {
          "type": "users",
          "id": "1",
          "meta": {}
        },
        "meta": {},
        "links": {}
      }
    },
    "meta": {},
    "links": {}
  }

Class "TiMacDonald\JsonApi\JsonApiResource" not found

Hi there,

So i've picked up this package this morning and installed into my project, made the necessary change to my resource file and im simply getting the error:
Class "TiMacDonald\JsonApi\JsonApiResource" not found

This is my resource file

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use TiMacDonald\JsonApi\JsonApiResource;

class PostResource extends JsonApiResource
{ 
...

Am I missing something here? I can see the files in the vendor folder etc but just doesn't work. I've tried composer dump-autoload and that hasn't resolved anything either.

Regards

How to get from JsonResource to JsonApiResource

Hi

I'm trying to switch from Laravel JsonResource to JsonApiResource and was under the impression that one could simply replace the JsonResource with JsonApiReource

"Declaration of App\Http\Resources\UserSignatureResource::toArray(Illuminate\Http\Request $request): array must be compatible with TiMacDonald\JsonApi\JsonApiResource::toArray($request)",

<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use TiMacDonald\JsonApi\JsonApiResource;

/** @mixin \App\Models\UserSignature */
class UserSignatureResource extends JsonApiResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'signature' => $this->signature,
            'user_id' => $this->user_id,
        ];
    }
}

[Question] How to include tree relationships?

We have a Menu and Node models where a Menu has many Nodes and a Node also has many child Nodes.

class Menu extends Model
{
    /** @return HasMany<Node> */
    public function nodeTrees(): HasMany
    {
        return $this->nodes()
            ->whereNull('parent_id') // ensure only root nodes will be fetched
            ->with('children');
    }
}

// and 

class Node extends Model implements Sortable
{
    /** @return HasMany<Node> */
    public function children(): HasMany
    {
        return $this->hasMany(self::class, 'parent_id')
            ->ordered() // scope from `spatie/eloquent-sortable`
            ->with('children');
    }
}

Then our controller action:

public function show($menu): MenuResource
{
    return MenuResource::make(
        QueryBuilder::for(Menu::find($menu))
            ->allowedIncludes(['nodeTrees'])
            ->firstOrFail()
    );
}

Now, when we try to /menus/9999?include=nodeTree, it only responds with the root nodes and doesn't show the child nodes nested in them. When try to inspect the model before MenuResource::make(), we do see whole node tree.

Any ideas on how this could be done?

Type is plural

in the respons the type is plural. Can I change that as it's not technical correct

if the return is a User, the type should ber 'user'

Package requires Laravel framework instead of Illuminate packages

As highlighted in composer.json

"laravel/framework": "^8.0 || ^9.0"
this package requires laravel/framework.

Trying to use this package in a Luman application is resulting in Illuminate packages being removed and laravel/framework being installed as its replacement causing some application issues.

I think it would be relatively easy to replace the dependencies for the sub packages of Laravel framework. Before creating such an PR I wanted to check if you would be accepting pull requests for this?

Lumen support?

Does this library work with Laravel/lumen?

I'm getting all kinds of errors like:
Declaration of App\Http\Resources\TestResource::toAttributes($request) must be compatible with TiMacDonald\JsonApi\JsonApiResource::toAttributes(Illuminate\Http\Request $request): array in ...

Adding use Illuminate\Http\Request and public function toAttributes(Request $request): array Results in:
Target [Illuminate\Contracts\Routing\ResponseFactory] is not instantiable.

Laravel 10 support

current version cannot be installed on laravel 10
image
Is it planing for support?

Top level Meta for Collection

Even though this is specifically in the To Do section, I'm making an issue for it to bump the urgency of it as I need it to pass back some additional data.

I am imagining an API that looks something like this:
Resource::collection($data)->withMeta($meta)

Again, if @timacdonald has a specific implementation in mind, let me know. If it sounds good in theory, I can try to take a stab at it.

Relationships not shown while having data

Having trouble getting the relationship to show

namespace App\Http\Resources;

use TiMacDonald\JsonApi\JsonApiResource;
use App\Http\Resources\AssetFormResource;

class FormResource extends JsonApiResource
{
    public function toAttributes($request): array
    {
      return [
        "name" => $this->name,
        "assetforms" => $this->assetForms()->get(),
      ];
    }

    public function toRelationships($request): array
    {
      return [
        'assetforms' => fn () => AssetFormResource::collection($this->assetForms()->get()),
      ];
    }
}

Result:

{
  "data": {
    "id": "9b43fe11-89f0-42fb-94de-f1c770051686",
    "type": "form",
    "attributes": {
      "name": "Form",
      "assetforms": [ <-- ################### Has Items
        {
          "id": "8cba7427-36b8-45e7-aad6-3786b7c6124a",
          "asset_id": "bf767910-d4c4-447f-8020-fee3e97b3797",
          "form_id": "9b43fe11-89f0-42fb-94de-f1c770051686",
          "updated_at": null,
          "created_at": "2023-08-10 12:41:27"
        }
      ]
    },
    "relationships": {}, <-- ################### Empty
    "meta": {},
    "links": {}
  },
  "included": [],
  "jsonapi": {
    "version": "1.0",
    "meta": {}
  }
}

Clarification for how to use with many to many

Hi All.

I am returning a collection of orders with their products (many to many) and each order can have the same product.
I would like to know the pivot table data to know how many of the product was for each order.

However in relationships I only get the type "products" and the id.

In the included property, I only get one instance of that product and the pivot data (ordered_qty, received_qty etc) only relates to one of the orders.

I have 2 orders: SPR0004 and SPR0005 they both have a product chocolate croissant, how can I get the ordered qty of them for each order?

{
  "data": [
    {
      "id": "4",
      "type": "orders",
      "attributes": {
        "ref": "SPR00004",
        "status": "dispatched",
        "requested_delivery": "2024-01-25T06:23:40.000000Z",
        "created_at": "2023-07-13T22:23:40.000000Z"
      },
      "relationships": {
        "products": {
          "data": [
            {
              "type": "products",
              "id": "1"
            }
          ]
        },
      }
    },
    {
      "id": "5",
      "type": "orders",
      "attributes": {
        "ref": "SPR00005",
        "status": "dispatched",
        "requested_delivery": "2024-01-25T22:51:09.000000Z",
        "created_at": "2023-07-13T22:51:09.000000Z"
      },
      "relationships": {
        "products": {
          "data": {
            "0": {
              "type": "products",
              "id": "46"
            },
            "2": {
              "type": "products",
              "id": "1"
            }
          }
        },
      }
    }
  ],
  "included": [
    {
      "id": "1",
      "type": "products",
      "attributes": {
        "name": "Chocolate Croissant",
        "slug": "chocolate-croissant",
        "description": null,
        "order_id": 4,
        "ordered_qty": 2,
        "dispatched_qty": 0,
        "delivered_qty": 0
      }
    },
    {
      "id": "46",
      "type": "products",
      "attributes": {
        "name": "Prawn Rissoles",
        "slug": "prawn-rissoles",
        "description": null,
        "order_id": 5,
        "ordered_qty": 2,
        "dispatched_qty": 0,
        "delivered_qty": 0
      }
    }
  ],
  "jsonapi": {
    "version": "1.0"
  }
}

Thank you very much for your assistance!

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.