Skip to content

Commit d69b63a

Browse files
committed
Preliminary support for extensions and various refactoring
1 parent 5c34ac0 commit d69b63a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1356
-946
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"description": "A fully automated JSON:API server implementation in PHP.",
44
"require": {
55
"php": ">=7.1",
6+
"ext-json": "*",
67
"doctrine/inflector": "^1.3",
78
"json-api-php/json-api": "^2.2",
89
"nyholm/psr7": "^1.3",

docs/.vuepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ module.exports = {
5050
collapsable: false,
5151
children: [
5252
'errors',
53+
'extensions',
5354
'laravel',
5455
]
5556
}

docs/.vuepress/styles/palette.styl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$accentColor = #0000ff

docs/adapters.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ You'll need to supply an adapter for each [resource type](https://jsonapi.org/fo
77
```php
88
use Tobyz\JsonApiServer\Schema\Type;
99

10-
$api->resource('users', $adapter, function (Type $type) {
10+
$api->resourceType('users', $adapter, function (Type $type) {
1111
// define your schema
1212
});
1313
```
@@ -26,10 +26,10 @@ $adapter = new EloquentAdapter(User::class);
2626
When using the Eloquent Adapter, the `$model` passed around in the schema will be an instance of the given model, and the `$query` will be a `Illuminate\Database\Eloquent\Builder` instance querying the model's table:
2727

2828
```php
29-
$type->scope(function (Builder $query) { });
29+
$type->scope(function (Builder $query) {});
3030

3131
$type->attribute('name')
32-
->get(function (User $user) { });
32+
->get(function (User $user) {});
3333
```
3434

3535
### Custom Adapters

docs/create.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,22 @@ $type->newModel(function (Context $context) {
2424

2525
## Events
2626

27-
### `onCreating`
27+
### `creating`
2828

29-
Run before the model is saved.
29+
Run after values have been set on the model, but before it is saved.
3030

3131
```php
32-
$type->onCreating(function (&$model, Context $context) {
32+
$type->creating(function (&$model, Context $context) {
3333
// do something
3434
});
3535
```
3636

37-
### `onCreated`
37+
### `created`
3838

39-
Run after the model is saved.
39+
Run after the model is saved, and before it is shown in a JSON:API document.
4040

4141
```php
42-
$type->onCreated(function (&$model, Context $context) {
42+
$type->created(function (&$model, Context $context) {
4343
$context->meta('foo', 'bar');
4444
});
4545
```

docs/delete.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,22 @@ $type->deletable(function (Context $context) {
1414

1515
## Events
1616

17-
### `onDeleting`
17+
### `deleting`
1818

1919
Run before the model is deleted.
2020

2121
```php
22-
$type->onDeleting(function (&$model, Context $context) {
22+
$type->deleting(function (&$model, Context $context) {
2323
// do something
2424
});
2525
```
2626

27-
### `onDeleted`
27+
### `deleted`
2828

2929
Run after the model is deleted.
3030

3131
```php
32-
$type->onDeleted(function (&$model, Context $context) {
32+
$type->deleted(function (&$model, Context $context) {
3333
// do something
3434
});
3535
```

docs/extensions.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Extensions
2+
3+
[Extensions](https://jsonapi.org/format/1.1/#extensions) allow your API to support additional functionality that is not part of the base specification.
4+
5+
## Defining Extensions
6+
7+
Extensions can be defined by extending the `Tobyz\JsonApiServer\Extension\Extension` class and implementing two methods: `uri` and `process`.
8+
9+
You must return your extension's unique URI from `uri`.
10+
11+
For every request that includes your extension in the media type, the `handle` method will be called. If your extension is able to handle the request, it should return a PSR-7 response. Otherwise, return null to let the normal handling of the request take place.
12+
13+
```php
14+
use Tobyz\JsonApiServer\Extension\Extension;
15+
use Psr\Http\Message\ResponseInterface;
16+
17+
use function Tobyz\JsonApiServer\json_api_response;
18+
19+
class MyExtension extends Extension
20+
{
21+
public function uri(): string
22+
{
23+
return 'https://example.org/my-extension';
24+
}
25+
26+
public function handle(Context $context): ?ResponseInterface;
27+
{
28+
if ($context->getPath() === '/my-extension') {
29+
return json_api_response([
30+
'my-extension:greeting' => 'Hello world!'
31+
]);
32+
}
33+
34+
return null;
35+
}
36+
}
37+
```
38+
39+
::: warning
40+
The current implementation of extensions has no support for augmentation of standard API responses. This API may change dramatically in the future. Please [create an issue](https://github.com/tobyzerner/json-api-server/issues/new) if you have a specific use-case you want to achieve.
41+
:::
42+
43+
## Registering Extensions
44+
45+
Extensions can be registered on your `JsonApi` instance using the `extension` method:
46+
47+
```php
48+
use Tobyz\JsonApiServer\JsonApi;
49+
50+
$api = new JsonApi('/api');
51+
52+
$api->extension(new MyExtension());
53+
```
54+
55+
The `JsonApi` class will automatically perform appropriate [content negotiation](https://jsonapi.org/format/1.1/#content-negotiation-servers) and activate the specified extensions on each request.
56+
57+
## Atomic Operations
58+
59+
An implementation of the [Atomic Operations](https://jsonapi.org/ext/atomic/) extension is available at `Tobyz\JsonApi\Extension\Atomic`.
60+
61+
When using this extension, you are responsible for wrapping the `$api->handle` call in a transaction to ensure any database (or other) operations performed are actually atomic in nature. For example, in Laravel:
62+
63+
```php
64+
use Illuminate\Support\Facades\DB;
65+
use Tobyz\JsonApiServer\Extension\Atomic;
66+
use Tobyz\JsonApiServer\JsonApi;
67+
68+
$api = new JsonApi('/api');
69+
70+
$api->extension(new Atomic());
71+
72+
/** @var Psr\Http\Message\ServerRequestInterface $request */
73+
/** @var Psr\Http\Message\ResponseInterface $response */
74+
try {
75+
return DB::transaction(function () use ($api, $request) {
76+
return $api->handle($request);
77+
});
78+
} catch (Exception $e) {
79+
$response = $api->error($e);
80+
}
81+
```

docs/filtering.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ GET /users?filter[postCount]=5..15
2323

2424
## Custom Filters
2525

26-
To define filters with custom logic, or ones that do not correspond to an attribute, use the `filter` method:
26+
To define filters with custom logic, or ones that do not correspond to a field, use the `filter` method:
2727

2828
```php
2929
$type->filter('minPosts', function ($query, $value, Context $context) {
@@ -34,7 +34,7 @@ $type->filter('minPosts', function ($query, $value, Context $context) {
3434
Just like [fields](visibility.md), filters can be made conditionally `visible` or `hidden`:
3535

3636
```php
37-
$type->filter('email', $callback)
37+
$type->filter('minPosts', $callback)
3838
->visible(function (Context $context) {
3939
return $context->getRequest()->getAttribute('isAdmin');
4040
});

docs/index.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
json-api-server is a [JSON:API](http://jsonapi.org) server implementation in PHP.
44

5-
It allows you to define your API's schema, and then use an [adapter](adapters.md) to connect it to your application's models and database layer, without having to worry about any of the server boilerplate, routing, query parameters, or JSON:API document formatting.
5+
It allows you to define your API's schema, and then use an [adapter](adapters.md) to connect it to your application's database layer. You don't have to worry about any of the server boilerplate, routing, query parameters, or JSON:API document formatting.
66

77
Based on your schema definition, the package will serve a **complete JSON:API that conforms to the [spec](https://jsonapi.org/format/)**, including support for:
88

@@ -15,7 +15,7 @@ Based on your schema definition, the package will serve a **complete JSON:API th
1515
- **Deleting** resources (`DELETE /api/articles/1`)
1616
- **Error handling**
1717

18-
The schema definition is extremely powerful and lets you easily apply [permissions](visibility.md), [transformations](writing.md#transformers), [validation](writing.md#validation), and custom [filtering](filtering.md) and [sorting](sorting.md) logic to build a fully functional API in minutes.
18+
The schema definition is extremely powerful and lets you easily apply [permissions](visibility.md), [transformations](writing.md#transformers), [validation](writing.md#validation), and custom [filtering](filtering.md) and [sorting](sorting.md) logic to build a fully functional API with ease.
1919

2020
### Example
2121

@@ -25,25 +25,26 @@ The following example uses Eloquent models in a Laravel application. However, js
2525
use App\Models\{Article, Comment, User};
2626
use Tobyz\JsonApiServer\JsonApi;
2727
use Tobyz\JsonApiServer\Schema\Type;
28-
use Tobyz\JsonApiServer\Laravel\EloquentAdapter;
28+
use Tobyz\JsonApiServer\Adapter\EloquentAdapter;
2929
use Tobyz\JsonApiServer\Laravel;
3030

3131
$api = new JsonApi('http://example.com/api');
3232

33-
$api->resource('articles', new EloquentAdapter(Article::class), function (Type $type) {
33+
$api->resourceType('articles', new EloquentAdapter(Article::class), function (Type $type) {
3434
$type->attribute('title')
3535
->writable()
3636
->validate(Laravel\rules('required'));
3737

38-
$type->hasOne('author')->type('users')
38+
$type->hasOne('author')
39+
->type('users')
3940
->includable()
4041
->filterable();
4142

4243
$type->hasMany('comments')
4344
->includable();
4445
});
4546

46-
$api->resource('comments', new EloquentAdapter(Comment::class), function (Type $type) {
47+
$api->resourceType('comments', new EloquentAdapter(Comment::class), function (Type $type) {
4748
$type->creatable(Laravel\authenticated());
4849
$type->updatable(Laravel\can('update-comment'));
4950
$type->deletable(Laravel\can('delete-comment'));
@@ -56,12 +57,13 @@ $api->resource('comments', new EloquentAdapter(Comment::class), function (Type $
5657
->writable()->once()
5758
->validate(Laravel\rules('required'));
5859

59-
$type->hasOne('author')->type('users')
60+
$type->hasOne('author')
61+
->type('users')
6062
->writable()->once()
6163
->validate(Laravel\rules('required'));
6264
});
6365

64-
$api->resource('users', new EloquentAdapter(User::class), function (Type $type) {
66+
$api->resourceType('users', new EloquentAdapter(User::class), function (Type $type) {
6567
$type->attribute('firstName')->sortable();
6668
$type->attribute('lastName')->sortable();
6769
});

docs/laravel.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Laravel Helpers
22

3+
These helpers improve the ergonomics of your API resource definitions when using the Laravel framework.
4+
35
## Validation
46

57
### `rules`
@@ -10,10 +12,20 @@ Use Laravel's [Validation component](https://laravel.com/docs/8.x/validation) as
1012
use Tobyz\JsonApiServer\Laravel;
1113

1214
$type->attribute('name')
13-
->validate(Laravel\rules('required|min:3|max:20'));
15+
->validate(Laravel\rules(['required', 'min:3', 'max:20']));
16+
```
17+
18+
Pass a string or array of validation rules to be applied to the value. Validating array contents is also supported:
19+
20+
```php
21+
$type->attribute('jobs')
22+
->validate(Laravel\rules([
23+
'required', 'array',
24+
'*' => ['string', 'min:3', 'max:255']
25+
]));
1426
```
1527

16-
Pass a string or array of validation rules to be applied to the value. You can also pass an array of custom messages and custom attribute names as the second and third arguments.
28+
You can also pass an array of custom messages and custom attribute names as the second and third arguments.
1729

1830
## Authentication
1931

0 commit comments

Comments
 (0)