Code Monkey home page Code Monkey logo

zend-expressive-hal's Introduction

Hypertext Application Language (HAL) for PSR-7 Applications

Repository abandoned 2019-12-31

This repository has moved to mezzio/mezzio-hal.

Build Status Coverage Status

This library provides utilities for modeling HAL resources with links and generating PSR-7 responses representing both JSON and XML serializations of them.

Installation

Run the following to install this library:

$ composer require zendframework/zend-expressive-hal

Documentation

Documentation is in the doc tree, and can be compiled using mkdocs:

$ mkdocs build

You may also browse the documentation online.

zend-expressive-hal's People

Contributors

alextech avatar ezimuel avatar geerteltink avatar grizzm0 avatar internalsystemerror avatar lansoweb avatar marcguyer avatar maspeng avatar matthiaskuehneellerhold avatar michalbundyra avatar sasezaki avatar sunspikes avatar weierophinney 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zend-expressive-hal's Issues

Extracting a collection of objects where one contains a NULL value causes XML renderer to error

Trying to extract a collection of objects where one contains a NULL value, works fine for JSON, but when trying to output XML generates:
Encountered non-primitive type "NULL" when serializing Zend\Expressive\Hal\HalResource instance; unable to serialize

Code to reproduce the issue

ConfigProvider.php

namespace App;

class ConfigProvider
{
    /**
     * Get configuration
     *
     * @return array
     */
    public function __invoke(): array
    {
        return [
            'dependencies'     => [
                'factories' => [
                    TestPageHandler::class => TestPageHandlerFactory::class,
                ],
            ],
            MetadataMap::class => [
                [
                    '__class__'      => RouteBasedResourceMetadata::class,
                    'resource_class' => TestEntity::class,
                    'route'          => 'test',
                    'extractor'      => Reflection::class,
                ],
                [
                    '__class__'           => RouteBasedCollectionMetadata::class,
                    'collection_class'    => TestCollection::class,
                    'collection_relation' => 'test',
                    'route'               => 'tests',
                ],
            ],
            'routes'           => [
                'tests' => [
                    'path'            => '/test',
                    'middleware'      => TestPageHandler::class,
                    'allowed_methods' => ['GET'],
                ],
                'test'  => [
                    'path'            => '/test/:id',
                    'middleware'      => TestPageHandler::class,
                    'allowed_methods' => ['GET'],
                ],
            ],
        ];
    }
}

TestEntity.php

namespace App;

class TestEntity
{

    /**
     * @var int
     */
    protected $id;

    /**
     * @var string
     */
    protected $name;

    /**
     * @var string|null
     */
    protected $description;

    /**
     * Get id
     *
     * @return int
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * Set id
     *
     * @param int $id
     *
     * @return void
     */
    public function setId(int $id): void
    {
        $this->id = $id;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return void
     */
    public function setName(string $name): void
    {
        $this->name = $name;
    }

    /**
     * Get description
     *
     * @return null|string
     */
    public function getDescription(): ?string
    {
        return $this->description;
    }

    /**
     * Set description
     *
     * @param null|string $description
     *
     * @return void
     */
    public function setDescription(?string $description): void
    {
        $this->description = $description;
    }
}

TestPageCollection.php

namespace App;

use Zend\Paginator\Paginator;

class TestCollection extends Paginator
{

}

TestPageHandler.php

namespace App;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Expressive\Hal\HalResponseFactory;
use Zend\Expressive\Hal\ResourceGenerator;
use Zend\Paginator\Adapter\ArrayAdapter;

class TestPageHandler implements RequestHandlerInterface
{

    /**
     * @var ResourceGenerator
     */
    protected $resourceGenerator;

    /**
     * @var HalResponseFactory
     */
    protected $responseFactory;

    /**
     * @var TestEntity[]
     */
    protected $testEntities;

    /**
     * Constructor
     *
     * @param ResourceGenerator  $resourceGenerator
     * @param HalResponseFactory $responseFactory
     */
    public function __construct(ResourceGenerator $resourceGenerator, HalResponseFactory $responseFactory)
    {
        $this->resourceGenerator = $resourceGenerator;
        $this->responseFactory   = $responseFactory;
    }

    /**
     * @inheritdoc
     */
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $this->generateTestEntities();
        $id = $request->getAttribute('id', false);
        if ($id) {
            $resource = $this->getTestEntity($id);
        } else {
            $resource = $this->getAllTestEntities();
        }

        return $this->responseFactory->createResponse($request,
            $this->resourceGenerator->fromObject($resource, $request));
    }

    /**
     * Get test entity
     *
     * @param $id
     *
     * @return TestEntity
     */
    public function getTestEntity(int $id): TestEntity
    {
        return $this->testEntities[$id];
    }

    /**
     * Get all test entities
     *
     * @return TestCollection
     */
    public function getAllTestEntities(): TestCollection
    {
        return new TestCollection(new ArrayAdapter($this->testEntities));
    }

    /**
     * Generate test entities
     *
     * @return void
     */
    public function generateTestEntities(): void
    {
        $this->testEntities = [];
        foreach (range(1, 10) as $id) {
            $entity = new TestEntity;
            $entity->setId($id);
            $entity->setName('test' . $id);
            $this->testEntities[$id] = $entity;
        }
    }
}

TestPageHandlerFactory.php

namespace App;

use Interop\Container\ContainerInterface;
use Zend\Expressive\Hal\HalResponseFactory;
use Zend\Expressive\Hal\ResourceGenerator;
use Zend\ServiceManager\Factory\FactoryInterface;

class TestPageHandlerFactory implements FactoryInterface
{

    /**
     * @inheritdoc
     */
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null): TestPageHandler
    {
        return new $requestedName(
            $container->get(ResourceGenerator::class),
            $container->get(HalResponseFactory::class)
        );
    }
}

Expected results

Valid JSON representing the collection when doing a GET on /test using JSON content negotiation.
Valid XML representing the collection when doing a GET on /test using XML content negotiation.

Actual results

Valid JSON representing the collection when doing a GET on /test using JSON content negotiation.

{
    "_total_items": 10,
    "_page": 1,
    "_page_count": 1,
    "_links": {
        "self": {
            "href": "http://localhost/test?page=1"
        }
    },
    "_embedded": {
        "test": [
            {
                "id": 1,
                "name": "test1",
                "description": null,
                "_links": {
                    "self": {
                        "href": "http://localhost/test/1"
                    }
                }
            },
            {
                "id": 2,
                "name": "test2",
                "description": null,
                "_links": {
                    "self": {
                        "href": "http://localhost/test/2"
                    }
                }
            },
            {
                "id": 3,
                "name": "test3",
                "description": null,
                "_links": {
                    "self": {
                        "href": "http://localhost/test/3"
                    }
                }
            },
            {
                "id": 4,
                "name": "test4",
                "description": null,
                "_links": {
                    "self": {
                        "href": "http://localhost/test/4"
                    }
                }
            },
            {
                "id": 5,
                "name": "test5",
                "description": null,
                "_links": {
                    "self": {
                        "href": "http://localhost/test/5"
                    }
                }
            },
            {
                "id": 6,
                "name": "test6",
                "description": null,
                "_links": {
                    "self": {
                        "href": "http://localhost/test/6"
                    }
                }
            },
            {
                "id": 7,
                "name": "test7",
                "description": null,
                "_links": {
                    "self": {
                        "href": "http://localhost/test/7"
                    }
                }
            },
            {
                "id": 8,
                "name": "test8",
                "description": null,
                "_links": {
                    "self": {
                        "href": "http://localhost/test/8"
                    }
                }
            },
            {
                "id": 9,
                "name": "test9",
                "description": null,
                "_links": {
                    "self": {
                        "href": "http://localhost/test/9"
                    }
                }
            },
            {
                "id": 10,
                "name": "test10",
                "description": null,
                "_links": {
                    "self": {
                        "href": "http://localhost/test/10"
                    }
                }
            }
        ]
    }
}

When doing a GET on /test using XML content negotiation, the following error is thrown:

Zend\Expressive\Hal\Exception\InvalidResourceValueException thrown with message "Encountered non-primitive type "NULL" when serializing Zend\Expressive\Hal\HalResource instance; unable to serialize"

Stacktrace:
#42 Zend\Expressive\Hal\Exception\InvalidResourceValueException in /var/www/vendor/zendframework/zend-expressive-hal/src/Exception/InvalidResourceValueException.php:22
#41 Zend\Expressive\Hal\Exception\InvalidResourceValueException:fromValue in /var/www/vendor/zendframework/zend-expressive-hal/src/Renderer/XmlRenderer.php:129
#40 Zend\Expressive\Hal\Renderer\XmlRenderer:createResourceElement in /var/www/vendor/zendframework/zend-expressive-hal/src/Renderer/XmlRenderer.php:146
#39 Zend\Expressive\Hal\Renderer\XmlRenderer:createNodeTree in /var/www/vendor/zendframework/zend-expressive-hal/src/Renderer/XmlRenderer.php:78
#38 Zend\Expressive\Hal\Renderer\XmlRenderer:createResourceNode in /var/www/vendor/zendframework/zend-expressive-hal/src/Renderer/XmlRenderer.php:73
#37 Zend\Expressive\Hal\Renderer\XmlRenderer:createResourceNode in /var/www/vendor/zendframework/zend-expressive-hal/src/Renderer/XmlRenderer.php:29
#36 Zend\Expressive\Hal\Renderer\XmlRenderer:render in /var/www/vendor/zendframework/zend-expressive-hal/src/HalResponseFactory.php:78
#35 Zend\Expressive\Hal\HalResponseFactory:createResponse in /var/www/src/App/src/Handler/TestPageHandler.php:46
#34 App\Handler\TestPageHandler:handle in /var/www/vendor/zendframework/zend-stratigility/src/Middleware/RequestHandlerMiddleware.php:53
#33 Zend\Stratigility\Middleware\RequestHandlerMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#32 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-expressive-router/src/Route.php:100
#31 Zend\Expressive\Router\Route:process in /var/www/vendor/zendframework/zend-expressive-router/src/RouteResult.php:110
#30 Zend\Expressive\Router\RouteResult:process in /var/www/vendor/zendframework/zend-expressive-router/src/Middleware/DispatchMiddleware.php:35
#29 Zend\Expressive\Router\Middleware\DispatchMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#28 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#27 Zend\Stratigility\MiddlewarePipe:handle in /var/www/src/CcApi/src/RouterOptionsToAttributesMiddleware.php:32
#26 Cc\Api\RouterOptionsToAttributesMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#25 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#24 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-helpers/src/UrlHelperMiddleware.php:45
#23 Zend\Expressive\Helper\UrlHelperMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#22 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#21 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-router/src/Middleware/MethodNotAllowedMiddleware.php:51
#20 Zend\Expressive\Router\Middleware\MethodNotAllowedMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#19 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#18 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-router/src/Middleware/ImplicitOptionsMiddleware.php:70
#17 Zend\Expressive\Router\Middleware\ImplicitOptionsMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#16 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#15 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-router/src/Middleware/ImplicitHeadMiddleware.php:84
#14 Zend\Expressive\Router\Middleware\ImplicitHeadMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#13 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#12 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-router/src/Middleware/RouteMiddleware.php:54
#11 Zend\Expressive\Router\Middleware\RouteMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#10 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#9 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-expressive-helpers/src/ServerUrlMiddleware.php:37
#8 Zend\Expressive\Helper\ServerUrlMiddleware:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#7 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#6 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-stratigility/src/Middleware/ErrorHandler.php:143
#5 Zend\Stratigility\Middleware\ErrorHandler:process in /var/www/vendor/zendframework/zend-expressive/src/Middleware/LazyLoadingMiddleware.php:46
#4 Zend\Expressive\Middleware\LazyLoadingMiddleware:process in /var/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php:78
#3 Zend\Stratigility\MiddlewarePipe:handle in /var/www/vendor/zendframework/zend-httphandlerrunner/src/RequestHandlerRunner.php:95
#2 Zend\HttpHandlerRunner\RequestHandlerRunner:run in /var/www/vendor/zendframework/zend-expressive/src/Application.php:81
#1 Zend\Expressive\Application:run in /var/www/public/index.php:21
#0 {closure} in /var/www/public/index.php:22

Bad return type for RouteBasedResourceMetadata::setRouteParams()

Bad return type for Zend\Expressive\Hal\Metadata\RouteBasedResourceMetadata::setRouteParams() method.

Code to reproduce the issue

$metadataMap  = $this->resourceGenerator->getMetadataMap();
$metadata     = $metadataMap->get(Item::class);
$routesParams = array_merge($metadata->getRouteParams(), ['categoryId' => $categoryId]);
$metadata->setRouteParams($routesParams);

Expected results

No error

Actual results

throws TypeError :

Return value of Zend\\Expressive\\Hal\\Metadata\\RouteBasedResourceMetadata::setRouteParams() must be of the type array, none returned

Extend `RouteBasedCollectionStrategy`

I am trying to modify the default fields which appear in a collection response. In particular I'm filtering and paginating my collection and, beside _total_items I would like to have also other fields like _total_items_without_filters and _total_items_with filters_but_without_pagination.

The fact is that inside RouteBasedCollectionStrategy, the creation of the resource happens in the private method extractIterator which is called by the private method extractCollection, which requires also the presence of the method extractPaginator.

All of this said, to add a new field to the resource I must extends the RouteBasedCollectionStrategy class, copy and paste all the above mentioned methods, just to edit the line $data = ['_total_items' => $count];

It would be nice if just the creation of the $data could be exposed, at least in a protected method, so that modifying it could become easier

No support for DateTime object in XmlRenderer

XmlRenderer::createResourceElement() does not have support for PHPs DateTime object. As this works natively in json_encode() I feel like XmlRenderer also should have support for it to be consistent between the two.

Exception:
Encountered non-primitive type "DateTime" when serializing Zend\Expressive\Hal\Exception\HalResource instance; unable to serialize

Generate a templated Link from Route

Provide a narrative description of what you are trying to accomplish.

As already explained in the zend-expressive-hal post
I'm trying to generate a templated Link as defined by the PSR-13.

Code to reproduce the issue

This is my actual routing configuration in the config/routes.php file:

$app->get('/api/test/{id}',App\Handler\TestHandler::class,'api.test.get');

The code I use to generate the link:

/* Code */
 $linkGenerator=$this->resourceGenerator
        ->getLinkGenerator();
$linkGenerator->templatedFromRoute(
                    'test',
                    $request,
                    'api.test.get'
                );
/* Code */

Expected results

The json response that I expect is something similar to this:

{
    "id": 1,
    "name": "Test",
    "_links": {
        "test": {
            "href": "http://localhost/expressive-test/public/api/test/{id}",
            "templated": true
        }
    }
}

Actual results

This is the error that I obtain:

Zend\Expressive\Router\Exception\RuntimeException raised in file D:\Sites\expressive-test\vendor\zendframework\zend-expressive-fastroute\src\FastRouteRouter.php line 299:
Message: Route `api.test.get` expects at least parameter values for [id], but received []

Option to avoid structure equivalence check

I suspect that sometimes it would be nice to have the option to skip the check provided by the HalResource::compareResources method.

This is because sometimes resources could have optional fields, or because an embedded resource makes sense only in certain cases

HalResource::validateElementName() with numbered array

While trying to create a resource from a numbered array an exception is thrown due to empty($name) check on zero index on this line.

Code to reproduce the issue

$array = [
    ['foo' => 'bar'],
];
$resource->embed('foobar', $resourceGenerator->fromArray($array));

Expected results

The resource should be generated just fine.

Actual results

Exception is thrown: $name provided to Zend\Expressive\Hal\HalResource cannot be empty

Link with `null` attribute

Creating a Link with a null attribute, I get an InvalidArgumentException. I'm not 100% sure about it but I guess that having null attributes should be allowed in Hal links

Code to reproduce the issue

new Link('a', 'b', false, ['c' => null]);

Expected results

I would expect to get a Link with a c attribute with a null value

Actual results

An InvalidArgumentException with message Zend\\Expressive\\Hal\\Link expects the $value to be a PHP primitive or array of strings; received NULL is thrown

MetadataMap does not honor optional route parameters

In our expressive application we use one route definition for a whole endpoint. HAL's metadata map does not recognize optional parameters in this setup and complains about URIs it can't create for paginated list views.

Code to reproduce the issue

// PipelineAndRoutesDelegator.php
$app->route(
    '/book[/:id]',
    [
        BookAction::class
    ],
    [
        'GET', 'POST', 'PUT', 'DELETE', 'PATCH'
    ],
    'book'
);

// ConfigProvider.php
MetadataMap::class => [
    [
        '__class__' => RouteBasedResourceMetadata::class,
        'resource_class' => Book::class,
        'route' => 'book',
        'extractor' => ClassMethods::class,
    ],
    [
        '__class__' => RouteBasedCollectionMetadata::class,
        'collection_class' => BookCollection::class,
        'collection_relation' => 'book',
        'route' => 'book',
    ],
],

Expected results

{
  "_total_items": 245,
  "_page": 1,
  "_page_count": 25,
  "_links": {
    "self": {
      "href": "http://localhost:8080/book?page=1"
    }
  },

Actual results

{
  "code": 0,
  "message": "Missing parameter \"id\"",
  "trace": "#0 /…/vendor/zendframework/zend-router/src/Http/Segment.php(328): Zend\\Router\\Http\\Segment->buildPath(Array, Array, true, true, Array)\n#1
…
  {main}"
}

I temporarily resolved this by adding

'route_params' => [
    'id' => 0,
]

to the defintition of the collection. But then the generated link looks like this. It's functional, but not pretty:

"_links": {
  "self": {
    "href": "http://localhost:8080/book/0?page=1"
  }
},

populateMetadataMapFromConfig should allow classes

Provide a narrative description of what you are trying to accomplish.

Code to reproduce the issue

MetadataMap::class => [
    [
        '__class__' => RouteBasedResourceMetadata::class,
        'resource_class' => Post::class,
        'route' => 'blog.post',
        'extractor' => ClassMethods::class,
    ],
    new RouteBasedResourceMetadata(
        Post::class,
        'blog.post',
        ClassMethods::class
    ),
];

Expected results

Imho both of the above should work the same. The later is better as it's typed.

Actual results

The second config fails as only arrays are allowed.

Mapped NULL values are serialized outside of the _embedded key

Provide a narrative description of what you are trying to accomplish.

Code to reproduce the issue

User.php

namespace App;

class User
{
   /**
    * @var string
    */
   protected $id;

   /**
    * @var string
    */
   protected $name;

   /**
    * @var string
    */
   protected $email;

   /**
    * @var Avatar
    */
   protected $avatar;

   public function getName(): ?string
   {
       return $this->name;
   }

   public function setName(?string $name): User
   {
       $this->name = $name;
       return $this;
   }

   public function getEmail(): ?string
   {
       return $this->email;
   }

   public function setEmail(?string $email): User
   {
       $this->email = $email;
       return $this;
   }

   public function getAvatar(): ?Avatar
   {
       return $this->avatar;
   }

   public function setAvatar(?Avatar $avatar): User
   {
       $this->avatar = $avatar;
       return $this;
   }
}

Avatar.php

namespace App;

class Avatar
{
   /**
    * @var string|null
    */
   protected $id;

   /**
    * @var string|null
    */
   protected $url;

   public function getId(): ?string
   {
       return $this->id;
   }

   public function setId(?string $id): User
   {
       $this->id = $id;
       return $this;
   }

   public function getUrl(): ?string
   {
       return $this->url;
   }

   public function setUrl(?string $url): User
   {
       $this->url = $url;
       return $this;
   }
}

config.php

MetadataMap::class => [
    [
        '__class__' => RouteBasedResourceMetadata::class,
        'resource_class' => App\User::class,
        'route' => 'api.user',
        'extractor' => ClassMethodsHydrator::class,
    ],
    [
        '__class__' => RouteBasedResourceMetadata::class,
        'resource_class' => App\Avatar::class,
        'route' => 'api.avatar',
        'extractor' => ClassMethodsHydrator::class,
    ],
],
$renderer = new JsonRenderer();
$avatar = new Avatar();
$avatar->setId('1234');
$avatar->setUrl('https://superfastcdn.com/myavatar.png');
$user = new User();
$user->setId('5678');
$user->setName('John Doe');
$user->setEmail('[email protected]');
$user->setAvatar($avatar);

Expected results

$resource = $resourceGenerator->fromObject($user, $request);
echo $renderer->render($resource);

$user->setAvatar(null);

$resource = $resourceGenerator->fromObject($user, $request);
echo $renderer->render($resource);
{
    "name": "John Doe",
    "email": "[email protected]",
    "_embedded": {
        "avatar": {
            "id": "1234",
            "url": "https://superfastcdn.com/myavatar.png",
            "_links": {
                "self": {
                    "href": "https://api.acme.com/avatars/1234"
                }
            }
        }
    },
    "_links": {
        "self": {
            "href": "https://api.acme.com/users/5678"
        }
    }
}
{
    "name": "John Doe",
    "email": "[email protected]",
    "_embedded": {
        "avatar": null
    },
    "_links": {
        "self": {
            "href": "https://api.acme.com/users/5678"
        }
    }
}

Actual results

{
    "name": "John Doe",
    "email": "[email protected]",
    "_embedded": {
        "avatar": {
            "id": "1234",
            "url": "https://superfastcdn.com/myavatar.png",
            "_links": {
                "self": {
                    "href": "https://api.acme.com/avatars/1234"
                }
            }
        }
    },
    "_links": {
        "self": {
            "href": "https://api.acme.com/users/5678"
        }
    }
}
{
    "name": "John Doe",
    "email": "[email protected]",
    "avatar": null,
    "_links": {
        "self": {
            "href": "https://api.acme.com/users/5678"
        }
    }
}

Paginating returns _page as string

When paginating (eg. /todo?page=2) the "_page" is returned as string not int.

Code to reproduce the issue

curl -XGET http://localhost:8080/todo?page=2

Expected results

{
    "_total_items": 30,
    "_page": 2,
    "_page_count": 2,
}

Actual results

{
    "_total_items": 30,
    "_page": "2",
    "_page_count": 2,
}

Writing a PR and will submit soon.

(ignore)

(ignore; issue accidentally opened from command-line)

Empty array is always considered an embedded resource

Provide a narrative description of what you are trying to accomplish.

When creating a HalResource, if one passes an array as $data with an empty array inside, this empty array is considered a embedded collection due to this line.

Code to reproduce the issue

$halResource1 = new HalResource([
    'id' => '1',
    'roles' => [10, 11],
]);
$halResource2 = new HalResource([
    'id' => '1',
    'roles' => [],
]);
print_r($halResource1->toArray());
print_r($halResource2->toArray());

Expected results

Array ( [roles] => Array ( [0] => 10 [1] => 11 ) [id] => 1 ) 
Array ( [roles] => Array ( ) [id] => 1 ) 

Actual results

Array ( [roles] => Array ( [0] => 10 [1] => 11 ) [id] => 1 ) 
Array ( [id] => 1 [_embedded] => Array ( [roles] => Array ( ) ) ) 

Probably, we show check if it's an empty array on this line:

if (! is_array($value) || empty($value)) {
    return false;
}

But this will prevent a real hal collection with an empty list (eg. search users rest api with name 'Silva' when none was found), unless you explicitly pass the $embedded constructor parameter, not embeded in the $data.

Thoughts?

[question] Requesting an out of bounds page

It's not an issue but, with the current implementation, if you request page >2 (?page=2) and the collection has fewer pages, the generator returns the last valid page.

This behavior is intended or should we throw an OutOfBoundsException (or something like that)?

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.