Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/.env

/.idea/

/.phpunit.result.cache
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Data Transfer Object classes generation from swagger spec
- JsonApiResponseFactory::createResponse() `meta` and `links` arguments
- CacheableDispatcherFactoryProxy
- Union types support for `data.relationships` DTO: use suitable type for input data structure

## [0.0.13] - 2021-01-27
### Added
Expand Down
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
FROM php:8-cli
ARG PHP_VERSION

FROM php:${PHP_VERSION}-cli

RUN apt-get update \
&& apt-get install -y \
Expand Down
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
PATH := $(shell pwd)/bin:$(PATH)
$(shell cp -n dev.env .env)
include .env

build:
docker build -t free-elephants/json-api-php-toolkit .
docker build --build-arg PHP_VERSION=${PHP_VERSION} -t free-elephants/json-api-php-toolkit .

install: build
composer install
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ Available in [/docs](/docs).
1. [Serialize doctrine entities](/docs/doctrine.md)
1. [DTO from psr server request](/docs/dto-psr7.md)
1. [Validation](/docs/validation.md)

## Development

All dev env is dockerized. Your can use make receipts and `bin/` scripts without locally installed php, composer.

For run tests with different php version change `PHP_VERSION` value in .env and rebuild image with `make build`.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"suggest": {
"doctrine/orm": "^2.7",
"free-elephants/di": "*",
"laminas/laminas-httphandlerrunner": "*"
"laminas/laminas-httphandlerrunner": "*",
"nyholm/psr7": "^1.2"
},
"autoload": {
"psr-4": {
Expand Down
1 change: 1 addition & 0 deletions dev.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PHP_VERSION=8.0
2 changes: 2 additions & 0 deletions docs/dto-psr7.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
Full typed objects from request or response body.

See example in [test](/tests/FreeElephants/JsonApiToolkit/DTO/DocumentTest.php).

Union types support for relationships in PHP 8.
5 changes: 5 additions & 0 deletions docs/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,8 @@ JSON

(new DispatcherFactory(null, new Parsers\JsonFileParser()))->buildDispatcher('swagger.json');
```

## About performance

Parse large swagger files have performance issues. Use `FreeElephants\JsonApiToolkit\Routing\FastRoute\CacheableDispatcherFactoryProxy` for production usage: it based on psr/cache component compare swagger file hash.

7 changes: 5 additions & 2 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
<ini name="error_reporting" value="E_ALL"/>
</php>
<testsuites>
<testsuite name="JsonApi Toolkit Tests">
<directory>./tests</directory>
<testsuite name="8.0">
<directory suffix="TestPHP8.php" phpVersion="8.0">./tests</directory>
</testsuite>
<testsuite name="7.4">
<directory suffix="Test.php" phpVersion="7.4">./tests</directory>
</testsuite>
</testsuites>
<logging/>
Expand Down
1 change: 0 additions & 1 deletion src/FreeElephants/JsonApiToolkit/DTO/AbstractDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace FreeElephants\JsonApiToolkit\DTO;

use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
* @property AbstractResourceObject|mixed $data
Expand Down
18 changes: 15 additions & 3 deletions src/FreeElephants/JsonApiToolkit/DTO/AbstractResourceObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace FreeElephants\JsonApiToolkit\DTO;

use FreeElephants\JsonApiToolkit\DTO\Reflection\SuitableRelationshipsTypeDetector;

/**
* @property AbstractAttributes $attributes
* @property AbstractAttributes $attributes
* @property AbstractRelationships $relationships
*/
class AbstractResourceObject
Expand All @@ -25,10 +27,20 @@ public function __construct(array $data)
}

if (property_exists($this, 'relationships')) {
$relationshipsData = $data['relationships'];
$concreteClass = new \ReflectionClass($this);
$relationshipsProperty = $concreteClass->getProperty('relationships');
$relationshipsClass = $relationshipsProperty->getType()->getName();
$this->relationships = new $relationshipsClass($data['relationships']);
$reflectionType = $relationshipsProperty->getType();

// handle php 8 union types
if (class_exists(\ReflectionUnionType::class) && $reflectionType instanceof \ReflectionUnionType) {
$relationshipsClass = (new SuitableRelationshipsTypeDetector())->detect($reflectionType, $relationshipsData);
} else {
$relationshipsClass = $reflectionType->getName();
}

$relationshipsDto = new $relationshipsClass($relationshipsData);
$this->relationships = $relationshipsDto;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace FreeElephants\JsonApiToolkit\DTO\Reflection;

class SuitableRelationshipsTypeDetector
{

public function detect(\ReflectionUnionType $propertyUnionType, array $data): ?string
{
foreach ($propertyUnionType->getTypes() as $type) {
$candidateType = $type->getName();
$candidateClass = new \ReflectionClass($candidateType);
$isCandidate = false;
foreach ($data as $propertyName => $propertyData) {
if (!$candidateClass->hasProperty($propertyName)) {
break;
}
$isCandidate = true;
}
if ($isCandidate) {
break;
}
}

return $candidateType;
}
}
2 changes: 1 addition & 1 deletion tests/FreeElephants/JsonApiToolkit/AbstractTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

class AbstractTestCase extends TestCase
{
protected const FIXTERE_PATH = __DIR__ . '/../../_fixtures';
protected const FIXTURE_PATH = __DIR__ . '/../../_fixtures';
}
10 changes: 10 additions & 0 deletions tests/FreeElephants/JsonApiToolkit/DTO/Example/Attributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace FreeElephants\JsonApiToolkit\DTO\Example;

use FreeElephants\JsonApiToolkit\DTO\AbstractAttributes;

class Attributes extends AbstractAttributes
{
public string $foo;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace FreeElephants\JsonApiToolkit\DTO\Example;

use FreeElephants\JsonApiToolkit\DTO\AbstractRelationships;
use FreeElephants\JsonApiToolkit\DTO\RelationshipToOne;

class OneRelationships extends AbstractRelationships
{
public RelationshipToOne $one;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace FreeElephants\JsonApiToolkit\DTO\Example;

use FreeElephants\JsonApiToolkit\DTO\AbstractRelationships;
use FreeElephants\JsonApiToolkit\DTO\RelationshipToOne;

class TwoRelationships extends AbstractRelationships
{
public RelationshipToOne $two;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace FreeElephants\JsonApiToolkit\DTO\Reflection;

use FreeElephants\JsonApiToolkit\AbstractTestCase;
use FreeElephants\JsonApiToolkit\DTO\AbstractAttributes;
use FreeElephants\JsonApiToolkit\DTO\AbstractRelationships;
use FreeElephants\JsonApiToolkit\DTO\AbstractResourceObject;
use FreeElephants\JsonApiToolkit\DTO\RelationshipToOne;
use ReflectionProperty;

class SuitableRelationshipsTypeDetectorTestPHP8 extends AbstractTestCase
{

public function testDetect()
{
$detector = new SuitableRelationshipsTypeDetector();
$relationshipsProperty = new ReflectionProperty(ResourceWithUnionTypedRelationships::class, 'relationships');
$relationshipsPropertyUnionType = $relationshipsProperty->getType();
$detectedOne = $detector->detect($relationshipsPropertyUnionType, [
'fuzz' => [
'data' => [
'id' => 'fuzz',
'type' => 'fuzz',
],
],
]);
$this->assertSame(FuzzRelationships::class, $detectedOne);

$detectedTwo = $detector->detect($relationshipsPropertyUnionType, [
'bar' => [
'data' => [
'id' => 'bazz',
'type' => 'bazz',
],
],
]);
$this->assertSame(BarRelationships::class, $detectedTwo);
}
}

class ResourceWithUnionTypedRelationships extends AbstractResourceObject
{
public BazzAttributes $attributes;
public FuzzRelationships|BarRelationships $relationships;
}

class BazzAttributes extends AbstractAttributes
{
public string $bazz;
}

class FuzzRelationships extends AbstractRelationships
{
public RelationshipToOne $fuzz;
}

class BarRelationships extends AbstractRelationships
{
public RelationshipToOne $bar;
}
38 changes: 38 additions & 0 deletions tests/FreeElephants/JsonApiToolkit/DTO/ResourceObjectTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace FreeElephants\JsonApiToolkit\DTO;

use FreeElephants\JsonApiToolkit\AbstractTestCase;

class ResourceObjectTest extends AbstractTestCase
{
public function testRelationshipTypes()
{
$resourceObject = new class([
'id' => 'id',
'type' => 'type',
'attributes' => [
'foo' => 'bar',
],
'relationships' => [
'one' => [
'data' => [
'type' => 'one',
'id' => 'one',
],
],
],
]) extends AbstractResourceObject {
public Example\Attributes $attributes;
public Example\OneRelationships $relationships;
};

$this->assertInstanceOf(Example\OneRelationships::class, $resourceObject->relationships);
$this->assertSame('one', $resourceObject->relationships->one->data->type);
}
}

class Attributes extends AbstractAttributes
{
public string $foo;
}
33 changes: 33 additions & 0 deletions tests/FreeElephants/JsonApiToolkit/DTO/ResourceObjectTestPHP8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace FreeElephants\JsonApiToolkit\DTO;

use FreeElephants\JsonApiToolkit\AbstractTestCase;

class ResourceObjectTestPHP8 extends AbstractTestCase
{
public function testUnionTypes()
{
$resourceObject = new class([
'id' => 'id',
'type' => 'type',
'attributes' => [
'foo' => 'bar',
],
'relationships' => [
'one' => [
'data' => [
'type' => 'one',
'id' => 'one',
],
],
],
]) extends AbstractResourceObject{
public Example\Attributes $attributes;
public Example\OneRelationships|Example\TwoRelationships $relationships;
};

$this->assertSame('one', $resourceObject->relationships->one->data->type);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class AttributesClassBuilderTest extends AbstractTestCase

public function testCollect()
{
$openapi = (new YamlFileParser())->parse(self::FIXTERE_PATH . '/json-api-simple-attributes-mapping-example.yml');
$openapi = (new YamlFileParser())->parse(self::FIXTURE_PATH . '/json-api-simple-attributes-mapping-example.yml');
$collector = new AttributesClassBuilder();
$attributesClass = $collector->buildClass($openapi, 'articles');
$expectedAttributesSourceCode = <<<PHP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class AttributesSchemaBuilderTest extends AbstractTestCase

public function testBuild()
{
$openapi = (new YamlFileParser())->parse(self::FIXTERE_PATH . '/json-api-simple-attributes-mapping-example.yml');
$openapi = (new YamlFileParser())->parse(self::FIXTURE_PATH . '/json-api-simple-attributes-mapping-example.yml');
$attributesSchemaBuilder = new AttributesSchemaBuilder();
$schema = $attributesSchemaBuilder->build($openapi, 'articles');
$this->assertTrue($schema->hasAttribute('title'));
Expand All @@ -20,7 +20,7 @@ public function testBuild()
public function testBuildFromAllOf()
{
$this->markTestIncomplete();
$openapi = (new YamlFileParser())->parse(self::FIXTERE_PATH . '/json-api.yml');
$openapi = (new YamlFileParser())->parse(self::FIXTURE_PATH . '/json-api.yml');
$attributesSchemaBuilder = new AttributesSchemaBuilder();
$schema = $attributesSchemaBuilder->build($openapi, 'people');
$this->assertTrue($schema->hasAttribute('first-name'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class DataTransferObjectClassSourceCodeGeneratorTest extends AbstractTestCase
public function testGenerate()
{
$generator = new DataTransferObjectClassSourceCodeGenerator();
$openapi = (new YamlFileParser())->parse(self::FIXTERE_PATH . '/json-api.yml');
$openapi = (new YamlFileParser())->parse(self::FIXTURE_PATH . '/json-api.yml');

$set = $generator->generate($openapi, 'articles');
$expectedDocumentSourceCode = <<<PHP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class SwaggerSpecificationRequestValidatorTest extends AbstractHttpTestCase
{

private const SWAGGER_FILE = self::FIXTERE_PATH . '/swagger-example-for-request-validation.yml';
private const SWAGGER_FILE = self::FIXTURE_PATH . '/swagger-example-for-request-validation.yml';

public function testProcessSuccess()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class JsonFileParserTest extends AbstractTestCase
public function testParse()
{
$parser = new JsonFileParser();
$openapi = $parser->parse(self::FIXTERE_PATH . '/petstore.json');
$openapi = $parser->parse(self::FIXTURE_PATH . '/petstore.json');
$this->assertSame('Swagger Petstore', $openapi->info->title);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class YamlFileParserTest extends AbstractTestCase
public function testParse()
{
$parser = new YamlFileParser();
$openapi = $parser->parse(self::FIXTERE_PATH . '/petstore.yaml');
$openapi = $parser->parse(self::FIXTURE_PATH . '/petstore.yaml');
$this->assertSame('Swagger Petstore', $openapi->info->title);
}
}