Closed pablorsk closed 7 years ago
Short answer: for simple GET
responses you can use plain Response with $result
as body and proper Content-Type
(application/vnd.api+json
).
Deeper answer: encoding objects into JSON API has some specifics such as certain headers should be added in certain cases thus ResponsesInterface
implementation should wrap Encoder itself. So its usage looks more like
$response->getContentResponse($objects);
but not
$response->getContentResponse($encoder->encodeData($objects));
Working practical example is here and ResponsesInterface
implementation is here (based on Zend\Diactoros\Response
PSR-7).
@pablorsk As the lib author can you give a working sample of client app? Preferably with a free license.
@pablorsk I haven't received any feedback from you for some time and have a feeling that your question was answered. If you have any further questions please don't hesitate to contact.
@neomerx,
About ts-angular-jsonapi, It would be an honor to make an example that works with neomerx json-api, but I need an online example, is it possible?
About Responses, first of all, THANK YOU. But, when you make an instance of Resposes can't find how make some params:
MediaTypeInterface $outputMediaType
ContainerInterface $schemes
: I have $encoderArray, but dont find how convert to ContainerInterface
Do you have an example?
For now, with my example+your links...
Route::get('/example/authors', function (ServerRequestInterface $request) {
$data = new Author();
$objects = $data->paginate()->toArrayObjects(); // collection of Author objects
$encoderArray = [
Author::class => AuthorSchema::class,
Photo::class => PhotoSchema::class,
];
$url = '/example/books/';
$encoder = Encoder::instance($encoderArray, new EncoderOptions(JSON_PRETTY_PRINT, $url));
/*
// old way
$result = $encoder->encodeData($objects);
return $result;
*/
$outputMediaType = ???; //MediaTypeInterface
$extensions = new Neomerx\JsonApi\Http\Headers\SupportedExtensions();
$schemes = ???; // ContainerInterface
$responses = new Responses(
$outputMediaType,
$extensions,
$encoder,
$schemes
);
return $responses->getContentResponse($objects);
});
@pablorsk Do you mean an online example of a server? Sure. If you have PHP and composer installed in less than 60 seconds.
$ composer create-project --prefer-dist limoncello-php/app app_name
$ cd app_name
$ composer l:db migrate
$ composer l:db seed
$ php -S 0.0.0.0:8080 -t public/ public/index.php
Done. Now you have a working demo server on port 8080
Basic test to check
$ curl -X GET http://localhost:8080/api/v1/boards -H 'accept: application/vnd.api+json'
will output
{"data":[{"type":"boards","id":"1","attributes":{"title":"Board Rerum sit atque.","created-at":"2017-08-17T13:51:47+0000","updated-at":null},"relationships":{"posts":{"links":{"self":"/api/v1/boards/1/relationships/posts","related":"/api/v1/boards/1/posts"}}},"links":{"self":"/api/v1/boards/1"}},{"type":"boards","id":"2","attributes":{"title":"Board Dignissimos.","created-at":"2017-08-17T13:51:47+0000","updated-at":null},"relationships":{"posts":{"links":{"self":"/api/v1/boards/2/relationships/posts","related":"/api/v1/boards/2/posts"}}},"links":{"self":"/api/v1/boards/2"}},{"type":"boards","id":"3","attributes":{"title":"Board In qui cumque.","created-at":"2017-08-17T13:51:47+0000","updated-at":null},"relationships":{"posts":{"links":{"self":"/api/v1/boards/3/relationships/posts","related":"/api/v1/boards/3/posts"}}},"links":{"self":"/api/v1/boards/3"}},{"type":"boards","id":"4","attributes":{"title":"Board Quaerat magni.","created-at":"2017-08-17T13:51:47+0000","updated-at":null},"relationships":{"posts":{"links":{"self":"/api/v1/boards/4/relationships/posts","related":"/api/v1/boards/4/posts"}}},"links":{"self":"/api/v1/boards/4"}},{"type":"boards","id":"5","attributes":{"title":"Board Deserunt et.","created-at":"2017-08-17T13:51:47+0000","updated-at":null},"relationships":{"posts":{"links":{"self":"/api/v1/boards/5/relationships/posts","related":"/api/v1/boards/5/posts"}}},"links":{"self":"/api/v1/boards/5"}},{"type":"boards","id":"6","attributes":{"title":"Board Odit ducimus vitae.","created-at":"2017-08-17T13:51:47+0000","updated-at":null},"relationships":{"posts":{"links":{"self":"/api/v1/boards/6/relationships/posts","related":"/api/v1/boards/6/posts"}}},"links":{"self":"/api/v1/boards/6"}},{"type":"boards","id":"7","attributes":{"title":"Board Aut qui cupiditate.","created-at":"2017-08-17T13:51:47+0000","updated-at":null},"relationships":{"posts":{"links":{"self":"/api/v1/boards/7/relationships/posts","related":"/api/v1/boards/7/posts"}}},"links":{"self":"/api/v1/boards/7"}},{"type":"boards","id":"8","attributes":{"title":"Board Omnis et.","created-at":"2017-08-17T13:51:47+0000","updated-at":null},"relationships":{"posts":{"links":{"self":"/api/v1/boards/8/relationships/posts","related":"/api/v1/boards/8/posts"}}},"links":{"self":"/api/v1/boards/8"}},{"type":"boards","id":"9","attributes":{"title":"Board Nesciunt dicta.","created-at":"2017-08-17T13:51:47+0000","updated-at":null},"relationships":{"posts":{"links":{"self":"/api/v1/boards/9/relationships/posts","related":"/api/v1/boards/9/posts"}}},"links":{"self":"/api/v1/boards/9"}},{"type":"boards","id":"10","attributes":{"title":"Board Culpa facere ipsum.","created-at":"2017-08-17T13:51:47+0000","updated-at":null},"relationships":{"posts":{"links":{"self":"/api/v1/boards/10/relationships/posts","related":"/api/v1/boards/10/posts"}}},"links":{"self":"/api/v1/boards/10"}}]}
Do you use Postman? The easiest way to play with the server is Postman
.
API documentation and code snippets here.
As for the Responses
I'll answer in the next post.
Of course, I can provide you with a standalone sample of how to create Responses in Laravel environment. However, you should understand that it is only a fraction of what you really need to make fully functional JSON API server. You will need support for such heavy things as CRUD and validation (tough thing btw), filtering, and pagination. If you are interested I also started with Laravel/Lumen but then had to admit it wouldn't be possible to expect needed changes made in Laravel and Lumen.
If you have doubts I've got some knowledge in Laravel/Lumen internals have a look at this.
@pablorsk if it helps, I have a fully running Laravel integration here https://github.com/cloudcreativity/laravel-json-api and a demo app here https://github.com/cloudcreativity/demo-laravel-json-api
@pablorsk projects from @lindyhopchris is also a good option to consider.
@lindyhopchris Don't you mind if I take your project for performance comparison as an example of Laravel project?
I had interesting results based on older version and of course, would be excited to compare with a more advanced app such as JSON API server with similar functionality.
@neomerx thanks a lot for your help. I know my question is very basic, the only reason is obtain a simple example. We work with laravel + neomerx jsonapi here: http://multinexo.com/ (API documentation here) We have CRUD and a lot of things. But I like work with your library but using PSR-7. Actually we use Laravel without PSR-7.
Having said that, where I found and example how can I set this variables? (no problem of simplicity)
$outputMediaType = ???; //MediaTypeInterface
$extensions = new Neomerx\JsonApi\Http\Headers\SupportedExtensions(); // it's rigth?
$schemes = ???; // ContainerInterface
About ts-angular-jsonapi
, it work fine. But I ask you about an ONLINE backend. If you have, I can put an online demo of my library and use your data and server. Or maybe I don't understand your initial question (sorry I don't speak very well English).
I'm going to take a look at your projects, @lindyhopchris...
@pablorsk Here is PSR7 sample
{
"name": "neomerx/responses",
"require": {
"zendframework/zend-diactoros": "^1.4",
"neomerx/json-api": "^1.0"
}
}
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Neomerx\JsonApi\Contracts\Encoder\EncoderInterface;
use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface;
use Neomerx\JsonApi\Contracts\Http\Headers\MediaTypeInterface;
use Neomerx\JsonApi\Contracts\Http\Headers\SupportedExtensionsInterface;
use Neomerx\JsonApi\Contracts\Http\ResponsesInterface;
use Neomerx\JsonApi\Contracts\Schema\ContainerInterface;
use Neomerx\JsonApi\Encoder\EncoderOptions;
use Neomerx\JsonApi\Factories\Factory;
use Neomerx\JsonApi\Http\Headers\MediaType;
use Neomerx\JsonApi\Http\Headers\SupportedExtensions;
use Neomerx\JsonApi\Http\Responses;
use Psr\Http\Message\StreamInterface;
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\InjectContentTypeTrait;
use Zend\Diactoros\Stream;
class JsonApiResponse extends Response
{
const HTTP_UNPROCESSABLE_ENTITY = 422;
use InjectContentTypeTrait;
public function __construct(string $content = null, int $status = 200, array $headers = [])
{
$headers = $this->injectContentType(MediaTypeInterface::JSON_API_MEDIA_TYPE, $headers);
parent::__construct($this->createBody($content), $status, $headers);
}
protected function createBody(string $content = null): StreamInterface
{
$body = new Stream('php://temp', 'wb+');
if ($content !== null) {
$body->write($content);
$body->rewind();
}
return $body;
}
}
class AppResponses extends Responses
{
private $parameters;
private $encoder;
private $outputMediaType;
private $extensions;
private $schemes;
private $urlPrefix;
public function __construct(
MediaTypeInterface $outputMediaType,
SupportedExtensionsInterface $extensions,
EncoderInterface $encoder,
ContainerInterface $schemes,
EncodingParametersInterface $parameters = null,
string $urlPrefix = null
) {
$this->extensions = $extensions;
$this->encoder = $encoder;
$this->outputMediaType = $outputMediaType;
$this->schemes = $schemes;
$this->urlPrefix = $urlPrefix;
$this->parameters = $parameters;
}
protected function createResponse($content, $statusCode, array $headers)
{
return new JsonApiResponse($content, $statusCode, $headers);
}
protected function getEncoder()
{
return $this->encoder;
}
protected function getUrlPrefix()
{
return $this->urlPrefix;
}
protected function getEncodingParameters()
{
return $this->parameters;
}
protected function getSchemaContainer()
{
return $this->schemes;
}
protected function getSupportedExtensions()
{
return $this->extensions;
}
protected function getMediaType()
{
return $this->outputMediaType;
}
public static function instance(
array $schemas,
EncoderOptions $encodeOptions = null,
EncodingParametersInterface $parameters = null,
string $urlPrefix = null
): self {
$factory = new Factory();
$schemasContainer = $factory->createContainer($schemas);
$encoder = $factory->createEncoder($schemasContainer, $encodeOptions);
$responses = new static(
new MediaType(MediaTypeInterface::JSON_API_TYPE, MediaTypeInterface::JSON_API_SUB_TYPE),
new SupportedExtensions(),
$encoder,
$schemasContainer,
$parameters,
$urlPrefix
);
return $responses;
}
}
$responses = AppResponses::instance([
// model / schema mapping
]);
@neomerx I haven't optimized it for performance yet - it's mainly about integrating your package and my additions in a "Laravel" way, and also making sure it's all usable with other Laravel features - e.g. broadcasting, views etc.
The performance of it is totally acceptable for the projects we're using it on at the moment, but it certainly needs optimizing. But will do that when I've settled the package (it's not yet on 1.0 and there's some bits that I'm going to re-write before 1.0).
So no problem you looking at performance, but with the proviso that I know it needs to be optimized (so the results may not be great at this stage).
@lindyhopchris I'm expecting it would be significantly about how Laravel itself works with routing, authentication, authorization, controllers, models, validation (partly) and other things you can't change.
@neomerx will you be creating some sort of comparison chart? If so, it might be worth adding this CakePHP implementation: https://github.com/FriendsOfCake/crud-json-api. I can help setting up the system/instructions if need be.
@bravo-kernel we can actually work together on it :smile: I already have 3 comparisons for limoncello, lumen and slim and ok to accept cake-json-api
:wink:
https://github.com/limoncello-php/framework/tree/master/docs/bench
To compare apples to apples let's have some common ground. Both limoncello and cloudcreativity implements similar messaging app (basic boards/sites, posts and comments + users). I'm thinking about 2 scenarios: reading with filters (index) and creation (check validation). If all demos support CORS, Authentication, Authorization I'd like to include them as well.
So it would be like Request -> CORS -> Authorization -> Routing -> Controller -> Filtering/Validation/API -> Response Haven't I miss anyting?
Looks good to me mr. @neomerx and... basically all out-of-the-box for Cake.
I will see if I can find some time to set up a plain app, let's take it from there.
@bravo-kernel and it's some basic out-of-the-box limoncello :wink:
No doubt and I am sure limoncello will be more/better featured ;)
@neomerx for now I would be most comfortable whooping up a simple example for/using https://github.com/limoncello-php/framework/blob/master/docs/Performance.md.
Additions could be made later, given time or specs. I am a bit fore-warned about creating a full-fledged API (having learned from https://github.com/gothinkster/realworld)
all sounds good, particularly as it'll give me performance stats to work off when I do get round to optimizing it. I'm prepared to be low down on the stats to start with though ;-)
A database/model would be a nice thing to start with
My bad, seems there already is: https://github.com/cloudcreativity/demo-laravel-json-api/tree/5.4/database/migrations
I've updated the stats for Hello World (limoncello vs lumen vs slim). For the last 3 months limoncello and slim look slightly better (which could be explained by changes in my environment: newer kernel, newer PHP, etc). Lumen looks noticeably worse in absolute RPS numbers which is an unpleasant surprise. I'm a bit worried about coming 5.5 which currently looks like minor changes in syntax, with not enough focus on performance, where the biggest efforts were spent on adding Redis monitor app (Horizon).
@neomerx, your example worked perfectly on Laraval + PSR-7. Now, I'll work on a migration from Laravel Routing to PSR-7 Zend\Diactoros\Response of Multinexo.
About ts-angular-jsonapi, if you have an JSON-API online server demo, I need one for a demo propose.
Thanks a lot for your clear and dedicated example for us.
@pablorsk Thanks for your support. Diactoros do not implement routing so you need something else. Limoncello uses nikic/fast-route for it. I would recommend you start from Core Limoncello component which combines routing (fast route) + HTTP (Diactoros) + container (can use any but pimp is default). It's really compact, elegant and will save you a ton of time. Here is an example.
As for online server demo I'm a bit confused. You may have the demo server on your computer in less than 60 seconds. I posted step-by-step how to above. Let me know if you need any help with it.
@bravo-kernel @lindyhopchris In case you're curious about limoncello performance for real app (tens of resources, authentication, authorization, CORS). Reading a list of resources with 2 included relationship is tested locally (Nginx, PHP-FPM, MySQL (Percona)) on consumer grade PC. about 1180 RPS with less than 80% CPU usage (I think 1400-1500 can be squeezed if tuned properly)
Running 15s test @ http://localhost/api/v1/ABC?include=DEF,GHI
12 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 80.85ms 20.56ms 141.55ms 79.75%
Req/Sec 98.87 27.02 161.00 59.39%
17802 requests in 15.10s, 83.78MB read
Requests/sec: 1179.11
Transfer/sec: 5.55MB
CPU Usage
Hi again @neomerx, your example was great for my start with PSR-7 + Neomerx/JsonApi on Laravel. I publish an JsonApi server demo (buided for my online Json-Api front-end demo) :tada:
As you see, I only publish GET
requests. PUT
is not sure if it's ok, because I work directly with $request->getParsedBody()
for fill the models. I think your library can check structure, or relationships, etc; and give me a procesed data, rigth? Or I need work directly, for example, with $request->getParsedBody()['data']['attributes']
? :confused:
PS: I try to migrate this project with PSR-7 to Lumen, but is not possible adapt it to PSR-7 like Laravel. :disappointed:
@pablorsk I probably visited it at not the best time and the app didn't respond to my clicks :smile:
If you start using Limoncello (PSR, CORS, OAuth 2 and other goodies) I can advise and will be very happy to do help.
Sorry @neomerx, I forget enable CORS on production :P Now its working (just for get resources and collections). Also, you can use the filter like screenshot :point_right: http://reyesoft.github.io/ts-angular-jsonapi/
Thanks for your recomendation with Limoncello, is very good work. It was checked one year ago with my team. But, on own company we are using Laravel/Lumen in old projects. We can't migrate to Limoncello rigth now :(.
I will try this week use your library for parse PUT/POST data and put into models and save.
I'm triying use
\Neomerx\JsonApi\Http\Responses
on Laravel + PSR-7, but I can't obtain an instance of$responses
.My code (all together only of explanation porporsues):
More information on the wiki, but only says We can extend, but not found any practical example.
How can I set
$response
if I have PSR-7$request
?