Giter Site home page Giter Site logo

laminas-api-tools / api-tools-hal Goto Github PK

View Code? Open in Web Editor NEW
6.0 10.0 12.0 2.41 MB

Laminas Module providing Hypermedia Application Language assets and rendering

Home Page: https://api-tools.getlaminas.org/documentation

License: BSD 3-Clause "New" or "Revised" License

PHP 100.00%
hacktoberfest

api-tools-hal's Introduction

Laminas HAL

Build Status

🇷🇺 Русским гражданам

Мы, участники Laminas, родились и живем в разных странах. У многих из нас есть друзья, родственники и коллеги как в России, так и в Украине. Некоторые из нас родились в России. Некоторые из нас живут в России. У некоторых бабушки и дедушки сражались с фашистами во Второй мировой войне. Здесь никто не поддерживает фашизм.

У одного из нас есть украинская родственница, которая спаслась из дома вместе с сыном. Поезд задержался из-за бомбежки на дороге впереди. У нас есть друзья, которые прячутся в бомбоубежищах. Мы с тревогой ждем весточки от них после воздушных налетов, которые беспорядочно наносят удары и попадают по больницам, школам, детским садам и домам. Мы не берем это из каких-либо СМИ. Мы наблюдаем это напрямую.

Вы доверяете нам достаточно, чтоб использовать наши программы, и мы просим вас довериться нам вновь. Мы нуждаемся в помощи. Выходите и протестуйте против этой бесполезной войны. Остановите кровопролитие. Скажите "Нет войне!"

🇺🇸 To Citizens of Russia

We at Laminas come from all over the world. Many of us have friends, family and colleagues in both Russia and Ukraine. Some of us were born in Russia. Some of us currently live in Russia. Some have grandparents who fought Nazis in World War II. Nobody here supports fascism.

One team member has a Ukrainian relative who fled her home with her son. The train was delayed due to bombing on the road ahead. We have friends who are hiding in bomb shelters. We anxiously follow up on them after the air raids, which indiscriminately fire at hospitals, schools, kindergartens and houses. We're not taking this from any media. These are our actual experiences.

You trust us enough to use our software. We ask that you trust us to say the truth on this. We need your help. Go out and protest this unnecessary war. Stop the bloodshed. Say "stop the war!"

Introduction

This module provides the ability to generate Hypermedia Application Language JSON representations.

Requirements

Please see the composer.json file.

Installation

Run the following composer command:

$ composer require laminas-api-tools/api-tools-hal

Alternately, manually add the following to your composer.json, in the require section:

"require": {
    "laminas-api-tools/api-tools-hal": "^1.4"
}

And then run composer update to ensure the module is installed.

Finally, add the module name to your project's config/application.config.php under the modules key:

return [
    /* ... */
    'modules' => [
        /* ... */
        'Laminas\ApiTools\Hal',
    ],
    /* ... */
];

laminas-component-installer

If you use laminas-component-installer, that plugin will install api-tools-hal as a module for you.

Configuration

User Configuration

This module utilizes the top level key api-tools-hal for user configuration.

Key: renderer

This is a configuration array used to configure the api-tools-hal Hal view helper/controller plugin. It consists of the following keys:

  • default_hydrator - when present, this named hydrator service will be used as the default hydrator by the Hal plugin when no hydrator is configured for an entity class.
  • render_embedded_entities - boolean, default true, to render full embedded entities in HAL responses; if false, embedded entities will contain only their relational links.
  • render_embedded_collections - boolean, default is true, to render collections in HAL responses; if false, only a collection's relational links will be rendered.
  • hydrators - a map of entity class names to hydrator service names that the Hal plugin can use when hydrating entities.

Key: metadata_map

The metadata map is used to hint to the Hal plugin how it should render objects of specific class types. When the Hal plugin encounters an object found in the metadata map, it will use the configuration for that class when creating a representation; this information typically indicates how to generate relational links, how to serialize the object, and whether or not it represents a collection.

Each class in the metadata map may contain one or more of the following configuration keys:

  • entity_identifier_name - name of the class property (after serialization) used for the identifier.
  • route_name - a reference to the route name used to generate self relational links for the collection or entity.
  • route_identifier_name - the identifier name used in the route that will represent the entity identifier in the URI path. This is often different than the entity_identifier_name as each variable segment in a route must have a unique name.
  • hydrator - the hydrator service name to use when serializing an entity.
  • is_collection - boolean; set to true when the class represents a collection.
  • links - an array of configuration for constructing relational links; see below for the structure of links.
  • entity_route_name - route name for embedded entities of a collection.
  • route_params - an array of route parameters to use for link generation.
  • route_options - an array of options to pass to the router during link generation.
  • url - specific URL to use with this resource, if not using a route.
  • max_depth - integer; limit to what nesting level entities and collections are rendered; if the limit is reached, only self links will be rendered. default value is null, which means no limit: if unlimited circular references are detected, an exception will be thrown to avoid infinite loops.
  • force_self_link - boolean; set whether a self-referencing link should be automatically generated for the entity. Defaults to true (since its recommended).

The links property is an array of arrays, each with the following structure:

[
    'rel'   => 'link relation',
    'url'   => 'string absolute URI to use', // OR
    'route' => [
        'name'    => 'route name for this link',
        'params'  => [ /* any route params to use for link generation */ .,
        'options' => [ /* any options to pass to the router */ .,
    .,
.,
// repeat as needed for any additional relational links

Key: options

The options key is used to configure general options of the Hal plugin. For now we have only one option available who contains the following configuration key:

  • use_proxy - boolean; set to true when you are using a proxy (for using HTTP_X_FORWARDED_PROTO, HTTP_X_FORWARDED_HOST, and HTTP_X_FORWARDED_PORT instead of SSL_HTTPS, HTTP_HOST, SERVER_PORT)

System Configuration

The following configuration is present to ensure the proper functioning of this module in a Laminas-based application.

// Creates a "HalJson" selector for use with laminas-api-tools/api-tools-content-negotiation
'api-tools-content-negotiation' => [
    'selectors' => [
        'HalJson' => [
            'Laminas\ApiTools\Hal\View\HalJsonModel' => [
                'application/json',
                'application/*+json',
            ],
        ],
    ],
],

Laminas Events

Events

Laminas\ApiTools\Hal\Plugin\Hal Event Manager

The Laminas\ApiTools\Hal\Plugin\Hal triggers several events during its lifecycle. From the EventManager instance composed into the HAL plugin, you may attach to the following events:

  • renderCollection
  • renderCollection.post
  • renderEntity
  • renderEntity.post
  • createLink
  • renderCollection.entity
  • getIdFromEntity

As an example, you could listen to the renderEntity event as follows (the following is done within a Module class for a Laminas module and/or Laminas API Tools API module):

class Module
{
    public function onBootstrap($e)
    {
        $app = $e->getTarget();
        $services = $app->getServiceManager();
        $helpers  = $services->get('ViewHelperManager');
        $hal      = $helpers->get('Hal');

        // The HAL plugin's EventManager instance does not compose a SharedEventManager,
        // so you must attach directly to it.
        $hal->getEventManager()->attach('renderEntity', [$this, 'onRenderEntity']);
    }

    public function onRenderEntity($e)
    {
        $entity = $e->getParam('entity');
        if (! $entity->getEntity() instanceof SomeTypeIHaveDefined) {
            // do nothing
            return;
        }

        // Add a "describedBy" relational link
        $entity->getLinks()->add(\Laminas\ApiTools\Hal\Link\Link::factory([
            'rel' => 'describedBy',
            'route' => [
                'name' => 'my/api/docs',
            ],
        ]));
    }
}

Notes on individual events:

  • renderCollection defines one parameter, collection, which is the Laminas\ApiTools\Hal\Collection being rendered.
  • renderCollection.post defines two parameters: collection, which is the Laminas\ApiTools\Hal\Collection being rendered, and payload, an ArrayObject representation of the collection, including the page count, size, and total items, and links.
  • renderEntity defines one parameter, entity, which is the Laminas\ApiTools\Hal\Entity being rendered.
  • renderEntity.post defines two parameters: entity, which is the Laminas\ApiTools\Hal\Entity being rendered, and payload, an ArrayObject representation of the entity, including links.
  • createLink defines the following event parameters:
    • route, the route name to use when generating the link, if any.
    • id, the entity identifier value to use when generating the link, if any.
    • entity, the entity for which the link is being generated, if any.
    • params, any additional routing parameters to use when generating the link.
  • renderCollection.entity defines the following event parameters:
    • collection, the Laminas\ApiTools\Hal\Collection to which the entity belongs.
    • entity, the current entity being rendered; this may or may not be a Laminas\ApiTools\Hal\Entity.
    • route, the route name for the current entity.
    • routeParams, route parameters to use when generating links for the current entity.
    • routeOptions, route options to use when generating links for the current entity.
  • getIdFromEntity defines one parameter, entity, which is an array or object from which an identifier needs to be extracted.
  • fromLink.pre (since 1.5.0) defines one parameter, linkDefinition, which is a Laminas\ApiTools\Hal\Link\Link instance. This is generally useful from Laminas\ApiTools\Rest\RestController::create(), when you may want to manipulate the self relational link for purposes of generating the Link header.

Listeners

Laminas\ApiTools\Hal\Module::onRender

This listener is attached to MvcEvent::EVENT_RENDER at priority 100. If the controller service result is a HalJsonModel, this listener attaches the Laminas\ApiTools\Hal\JsonStrategy to the view at priority 200.

Laminas Services

Models

Laminas\ApiTools\Hal\Collection

Collection is responsible for modeling general collections as HAL collections, and composing relational links.

Laminas\ApiTools\Hal\Entity

Entity is responsible for modeling general purpose entities and plain objects as HAL entities, and composing relational links.

Laminas\ApiTools\Hal\Link\Link

Link is responsible for modeling a relational link. The Link class also has a static factory() method that can take an array of information as an argument to produce valid Link instances.

Laminas\ApiTools\Hal\Link\LinkCollection

LinkCollection is a model responsible for aggregating a collection of Link instances.

Laminas\ApiTools\Hal\Metadata\Metadata

Metadata is responsible for collecting all the necessary dependencies, hydrators and other information necessary to create HAL entities, links, or collections.

Laminas\ApiTools\Hal\Metadata\MetadataMap

The MetadataMap aggregates an array of class name keyed Metadata instances to be used in producing HAL entities, links, or collections.

Extractors

Laminas\ApiTools\Hal\Extractor\LinkExtractor

LinkExtractor is responsible for extracting a link representation from Link instance.

Laminas\ApiTools\Hal\Extractor\LinkCollectionExtractor

LinkCollectionExtractor is responsible for extracting a collection of Link instances. It also composes a LinkExtractor for extracting individual links.

Controller Plugins

Laminas\ApiTools\Hal\Plugin\Hal (a.k.a. "Hal")

This class operates both as a view helper and as a controller plugin. It is responsible for providing controllers the facilities to generate HAL data models, as well as rendering relational links and HAL data structures.

View Layer

Laminas\ApiTools\Hal\View\HalJsonModel

HalJsonModel is a view model that when used as the result of a controller service response signifies to the api-tools-hal module that the data within the model should be utilized to produce a JSON HAL representation.

Laminas\ApiTools\Hal\View\HalJsonRenderer

HalJsonRenderer is a view renderer responsible for rendering HalJsonModel instances. In turn, this renderer will call upon the Hal plugin/view helper in order to transform the model content (an Entity or Collection) into a HAL representation.

Laminas\ApiTools\Hal\View\HalJsonStrategy

HalJsonStrategy is responsible for selecting HalJsonRenderer when it identifies a HalJsonModel as the controller service response.

api-tools-hal's People

Contributors

acabala avatar artdevgame avatar bartbrinkman avatar boukeversteegh avatar evandotpro avatar ezimuel avatar geerteltink avatar gsomoza avatar javabudd avatar localheinz avatar maks3w avatar michaelgooden avatar michalbundyra avatar neeckeloo avatar nyholm avatar oxodesign avatar ppaulis avatar ralphschindler avatar remybreuils avatar rogervila avatar samsonasik avatar snapshotpl avatar steverhoades avatar telkins avatar thomasvargiu avatar tjlytle avatar tomhanderson avatar vixriihi avatar weierophinney avatar wilt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

api-tools-hal's Issues

Collection Name in response of POST_COLLECTION falls back to default value "items"

Bug Report

Q A
Version(s) 1.9.0

Summary

When creating multiple entities in a POST_COLLECTION request, the name of the collection in the HAL response is the default name items instead of the Collection Name defined in the Interface.

Current behavior

Example:

{
  "_links": {
    "self": {
      "href": "..."
    },
    "first": {
      "href": "..."
    },
    "last": {
      "href": "..."
    }
  },
  "_embedded": {
    "items": [ <========================= Uses default value "items" instead of Collection Name
      {
        "id": "..."
      }
    ]
  },
  "page_count": 1,
  "page_size": 30,
  "total_items": 3,
  "page": 1
}

Looking at the module.config.php file, the collection_name is present in the api-tools-rest section and it's used for GET collection calls :

'api-tools-rest' => array(
    'tms\\V1\\Rest\\MyController\\Controller' => array(
            'listener' => '...',
            'route_name' => '...',
            'route_identifier_name' => '...',
            'collection_name' => 'myCollection',

However the POST collection call doesn't use this key/value pair.

If, on the other hand, I manually add the collection_name to the Metadata Map, then the collection name is set correctly:

'api-tools-hal' => array(
        'metadata_map' => array(
            'tms\\V1\\Rest\\MyResource\\MyResourceCollection' => array(
                'entity_identifier_name' => 'id',
                'route_name' => '...',
                'route_identifier_name' => '...',
                'is_collection' => true,
                'collection_name' => 'myCollection'
            ),

How to reproduce

Create multiple entities in a single POST call.

Expected behavior

I would expect the collection name of the POST_COLLECTION response to match the collection_name from the api-tools-rest section of the module.config.php file.

Thanks and best regards,
Pascal

Hydration in combination with an abstract factory doesn't work

I've a problem to use the zf-hal hydrator in combination with an abstract factory. This is my module configuration:

public function getConfig()
{
    return [
        'hydrators' => [
            'abstract_factories' => [
                AbstractHydratorFactory::class,
            ]
        ],
        'service_manager' => [
            'factories' => [
                ModuleOptions::class => ModuleOptionsFactory::class,
            ],
        ],
        'zf-hal' => [
            'renderer' => [
                'default_hydrator' => 'reflection'
            ],
        ]
    ];
}

My abstract factory looks like this:

class AbstractHydratorFactory implements AbstractFactoryInterface
{
    public function canCreate(ContainerInterface $container, $requestedName)
    {
        $moduleOptions = $container->get(ModuleOptions::class);
        $configuration = $moduleOptions->getClass();
        return isset($configuration[$requestedName]['property_name_translation']);
    }

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $moduleOptions = $container->get(ModuleOptions::class);
        $configuration = $moduleOptions->getClass();
        $hydrator = $container->get($moduleOptions->getHydrator());
        $hydrator->setNamingStrategy(
            new ArrayMapNamingStrategy(
                $configuration[$requestedName]['property_name_translation']
            )
        );
        return $hydrator;
    }
}

To test my module I created some unit tests. One of them is:

class HalEntityHydratorTest extends TestCase
{
    protected $moduleLoader;

    protected function setUp()
    {
        $this->moduleLoader = new ModuleLoader([
            'modules' => [
                'Zend\Hydrator',
                'Zend\Router',
                'ZF\Hal',
                'MyHalHydratorModule',
                'MyHalHydratorModuleTest\Integration\Hydrator\HalEntityHydratorTest',
            ],
            'module_listener_options' => [],
        ]);

        $this->moduleLoader->getApplication()->bootstrap();
    }

    public function testHalRendererWithHalEntities()
    {
        $halPlugin = $this->moduleLoader->getServiceManager()->get('ViewHelperManager')->get('Hal');

        $rootTestEntity = new RootTestEntity();
        $childTestEntity = new ChildTestEntity();

        $rootTestEntity->setChildEntity(new Entity($childTestEntity));
        $rootTestEntity->setUnkownChildEntity(new Entity(new UnkownChildTestEntity()));

        $expectedArray = [
            '_embedded' => [
                'phpunit:test-entity' => [
                    '_links' => [],
                ],
                'unkownChildEntity' => [
                    'unkownChildTestProperty' => 'phpunit',
                    '_links' => [],
                ],
            ],
            '_links' => [],
        ];

        $this->assertSame($expectedArray, $halPlugin->renderEntity(new Entity($rootTestEntity)));
    }
}

These are my test entities:

class ChildTestEntity
{
}

class UnkownChildTestEntity
{
    protected $unkownChildTestProperty = 'phpunit';
}

class RootTestEntity
{
    protected $childEntity;
    protected $unkownChildEntity;

    public function setUnkownChildEntity($unkownChildEntity)
    {
        $this->unkownChildEntity = $unkownChildEntity;
    }

    public function setChildEntity($childEntity)
    {
        $this->childEntity = $childEntity;
    }
}

And then it could be good to know what my test module configuration looks like:

public function getConfig()
{
    return [
        'zf-hal' => [
            'metadata_map' => [
                ChildTestEntity::class => [
                    'force_self_link' => false,
                ],
                UnkownChildTestEntity::class => [
                    'force_self_link' => false,
                ],
            ],
        ],
        'my-hal-hydrator-module' => [
            'class' => [
                RootTestEntity::class => [
                    'property_name_translation' => [
                        'childEntity' => 'phpunit:test-entity',
                    ],
                ],
            ],
        ],
    ];
}

Ok, enough sourcecode. What happens now?
I run my test suite and the test above fails because of the arrays are different. That's why the first key of the result array is still 'childEntity' and not 'phpunit:test-entity' as expected.
So I think the property replacement has not take place but I don't know why.


Originally posted by @kschroeer at zfcampus/zf-hal#171

Support for PHP 8.3 is required

We would like to port our projects to PHP 8.3. We use Laminas + API tools to develop our REST APIs.
Unfortunately, we are currently unable to build an application using Composer because the packages are fixed to 8.0.x, 8.1.x or 8.2.x.
Can you already give a time horizon as to when the API tools will be available for PHP 8.3?

Rename `hydrators` key to `extractors`

Would it not be better to rename the hydrators key to extractors. In the whole zf-hal (or any other related) module the hydrators are never used for hydration but merely for extraction. So the name of this key is only confusing IMO.

I understand this can be a BC issue, but maybe it can be changed in version 2.0?


Originally posted by @Wilt at zfcampus/zf-hal#128

Hal Link not supporting templated links

I noticed that templated links are still not supported in ZF-Hal. We are already using templated links for a long time inside our customized version of this library, what is the reason that templated links are not yet supported?

Is it because of issues with rendering the curly brackets or braces so { and } ?
If that is the case, we simply solved this issue by adding a decodeCurlyBrackets to the LinkExtractor class:

if($object->isTemplated()){
    self::decodeCurlyBrackets($representation['href']);
}

And inside the decoder method:

/**
 * We need to decode curly brackets that are used for link templating.
 * They are encoded automatically by the url view helper.
 *
 * @param $string
 * @return string
 */
private static function decodeCurlyBrackets(&$string)
{
    $string = str_replace('%7B', '{' , $string);
    $string = str_replace('%7D', '}' , $string);
    return $string;
}

Could we add such a feature which is an essential part of the Hal standard?


Originally posted by @Wilt at zfcampus/zf-hal#174

Hinting of embedded resources

I've been struggling with a problem for some time regarding how relationship are handled on entities, specifically when embedded into a resource. The problem (and I assume it is a fairly common one) is how to provide relationships in a sane and dependable way without returning the entire (or most of the) database in embedded resources (and dealing with looping recursion problems) or always needing to fetch relationships. HAL spec is a bit fuzzy on how _embedded resources are supplied which makes it harder to create a dependable abstract consumers. For example, HAL Clients don't know if the non-rendered entity is actually rendered or not (or is just a link) or whether it is a full or partial rendering. The first case is an easier problem to solve abstractly and a few suggestions are below.

For the sake of example lets assume the following:

  • We have a table of people and each person has a relationship for their best friend and their gender.
    ** person --> one-to-many (bestFriend) --> person
  • We have render_embedded_entities set to false

We can either:

  1. Depend on clients to inspect the payload and if the only property is the _links object, the client should understand to fetch that resource also when needed. This is how it is currently working
{
    "id": "123",
    "name": "Jane Doe",
   "gender": "female",
    "_embedded": {
        "bestFriend": {
            "_links": {
                "self": {
                    "href": "http://abc.tld/api/person/456"
                }
            }
        }
    },
    "_links": {
        "self": {
            "href": "http://abc.tld/api/person/123"
        }
    }
}
  1. Providing a 'profile' parameter which can be used as a hint
    Link Only:
{
    "id": "123",
    "name": "Jane Doe",
    "gender": "female",
    "_embedded": {
        "bestFriend": {
            "_profile": "link",
            "_links": {
                "self": {
                    "href": "http://abc.tld/api/person/456"
                }
            }
        }
    },
    "_links": {
        "self": {
            "href": "http://abc.tld/api/person/123"
        }
    }
}

Partial Resource:

{
    "id": "123",
    "name": "Jane Doe",
    "gender": "female",
    "_embedded": {
        "bestFriend": {
            "name": "John Smith",
            "_profile": "partial",
            "_links": {
                "self": {
                    "href": "http://abc.tld/api/person/456"
                }
            }
        }
    },
    "_links": {
        "self": {
            "href": "http://abc.tld/api/person/123"
        }
    }
}
  1. Provide the embedded entities as _links on the resource instead of embedding the entity that contains only the link (removes any ambiguity).
{
    "id": "123",
    "name": "Jane Doe",
    "gender": "female",
    "_links": {
        "self": {
            "href": "http://abc.tld/api/person/123"
        },
        "bestFriend": {
            "href": "http: //abc.tld/api/person/456"
        }
    }
}

I've created PR #118 to show an example of how we can enabling linking insteading of embedding and would love to get feedback on how others are solving this kind of issue, and/or if I should focus time on building out one of the above potential solutions.


Originally posted by @nobesnickr at zfcampus/zf-hal#118

Problem with get_object_vars and ArrayObject

Bug Report

Q A
Version(s) 1.6.0

Summary

I upgraded from php5.6 to php7.4 and I found that when I use an Entity with ArrayObject and Hal want to extract it with get_object_vars I get an empty array.

Php reference
Same Problem in zend-framework in another package

Current behavior

get_object_vars returns empty array when using on an ArrayObject

How to reproduce

Expected behavior

Return an array with ArrayObject properties and values.

The solution could be add?

if ($entity instanceof ArrayObject) {
       return  $entity->getArrayCopy()
}

The 'page_size' parameter should allow 0 values

Hi, we use PhlyRestfully and now Apigility since several years ;-).

We often need to call a Web Service to count a number of entities. The HAL standard and zf-hal allow to get a count very easily using the total_items property of the fetched HAL collection.

For example...

GET https://myserver.com/comments?page=1&page_size=1
{
  "_links": { ... },
  "_embedded": {
    "comments": [ ... ]
  },
  "page_count": 45,
  "page_size": 1,
  "total_items": 45,
  "page": 1
}

Then on the client side its very simple (sample in JS / jQuery here).

$.get(url, function(payload) { console.log(payload.total_items); });

It works, but I think this is "sub-optimal" because the library forbid page_size values equal to 0 (see https://github.com/zfcampus/zf-hal/blob/7c164efe64365f343589cafd9307d91274140b55/src/Collection.php#L295).

In our case its has the following consequences

  • This forces an additional request in our database to always fetch at least one entity ;
  • It increases the size of the returned payload.

So, why the page_size parameter cannot be equal to 0 ? If their are good reasons what the best practices to count entities efficiently with REST / HAL ? If their are no good reasons could it be possible to authorize page_size 0 values ?

Thanks


Originally posted by @bgaillard at zfcampus/zf-hal#164

Different output from onRenderEntity and onRenderCollection when adding additional Entities in Events

As stated by Matus Zeman @matuszeman here https://groups.google.com/a/zend.com/forum/#!topic/apigility-users/zck43pReklw there seems to be a bug in the Hal helper when rendering single entities.

Quoted from the google group:
"I believe there is a bug in Hal helper (https://github.com/zfcampus/zf-hal/blob/master/src/Plugin/Hal.php#L1140) where convertEntityToArray() method caches converted entities so when you set new entities or links into entity using 'renderEntity' event convertEntityToArray() returns original entity anyway ignoring everything - https://github.com/zfcampus/zf-hal/blob/master/src/Plugin/Hal.php#L495"

Code for single Entity (http://192.168.33.101/events/2182)

    public function onRenderEntity($e)
    {
        $halEntity = $e->getParam('entity');
        $version = $this->isEntityOfInterest($halEntity->entity, 'Events', 1);

        if (false !== $version) {
            if($halEntity->entity instanceof EventsEntity) {
                $repository = $this->sm->get('Umid\V1\Rest\Unternehmen\UnternehmenResource');
                $item = $repository->fetch($halEntity->entity['events_location_Id']);
                $halEntity->entity['unternehmen'] = $item;
            }
            $this->injectEventsEntityLinks($halEntity, $version);
            return;
        }
    }

Dump of $halEntity

object(ZF\Hal\Entity)[637]
  protected 'id' => string '2182' (length=4)
  protected 'links' => 
    object(ZF\Hal\Link\LinkCollection)[635]
      protected 'links' => 
        array (size=1)
          'self' => 
            object(ZF\Hal\Link\Link)[636]
              protected 'props' => 
                array (size=0)
                  empty
              protected 'relation' => string 'self' (length=4)
              protected 'route' => string 'umid.rest.events' (length=16)
              protected 'routeOptions' => 
                array (size=0)
                  empty
              protected 'routeParams' => 
                array (size=1)
                  'events_id' => string '2182' (length=4)
              protected 'url' => null
  protected 'entity' => 
    object(Umid\V1\Rest\Events\EventsEntity)[502]
      public 'Id' => string '2182' (length=4)
      ...
      public 'unternehmen' => 
        object(Umid\V1\Rest\Unternehmen\UnternehmenEntity)[696]
          public 'Id' => string '12608' (length=5)
          public 'land_Id' => string '1' (length=1)
          public 'bundesland_Id' => string '0' (length=1)
          ...

Output - embedded entity is missing

{
    Id: "2182"
    events_location_Id: "12608"
    location_Id: null
    unternehmen_Id: "12608"
    sehenswuerdigkeit_Id: "0"
    veranstalter_Id: "12608"
    ...
    -_links: {
        -self: {
            href: "http:\/\/192.168.33.101\/events\/2182"
        }
    }
}

Code for Collection (http://192.168.33.101/events)

    public function onRenderCollectionEntity($e)
    {
        $entity = $e->getParam('entity');
        $version = $this->isEntityOfInterest($entity, 'Events', 1);

        if (false !== $version) {
            $halEntity = new HalEntity($entity, $entity['Id']);
            if($halEntity instanceof EventsEntity) {
                $repository = $this->sm->get('Umid\V1\Rest\Unternehmen\UnternehmenResource');
                $item = $repository->fetch($halEntity['events_location_Id']);
                $halEntity['unternehmen'] = $item;
            }
            $this->injectEventsEntityLinks($halEntity, $version);
            $e->setParam('entity', $halEntity);
            return;
        }
    }

Dump of $halEntity

object(ZF\Hal\Entity)[685]
  protected 'id' => string '2182' (length=4)
  protected 'links' => 
    object(ZF\Hal\Link\LinkCollection)[687]
      protected 'links' => 
        array (size=1)
          'self' => 
            object(ZF\Hal\Link\Link)[690]
              protected 'props' => 
                array (size=0)
                  empty
              protected 'relation' => string 'self' (length=4)
              protected 'route' => string 'umid.rest.events' (length=16)
              protected 'routeOptions' => 
                array (size=0)
                  empty
              protected 'routeParams' => 
                array (size=1)
                  'events_id' => string '2182' (length=4)
              protected 'url' => null
  protected 'entity' => 
    object(Umid\V1\Rest\Events\EventsEntity)[667]
      public 'Id' => string '2182' (length=4)
      ...
      public 'unternehmen' => 
        object(Umid\V1\Rest\Unternehmen\UnternehmenEntity)[716]
          public 'Id' => string '12608' (length=5)
          public 'land_Id' => string '1' (length=1)
          public 'bundesland_Id' => string '0' (length=1)
          ...

Output - with embedded entity

{
    Id: "2182"
    events_location_Id: "12608"
    location_Id: null
    unternehmen_Id: "12608"
    sehenswuerdigkeit_Id: "0"
    veranstalter_Id: "12608"
    ...
    -_embedded: {
        -unternehmen: {
            Id: "12608"
            land_Id: "1"
            bundesland_Id: "0"
            landkreis_Id: "0"
            region_Id: "0"
            ...
            -_links: {
                -self: {
                    href: "http:\/\/192.168.33.101\/unternehmen\/12608"
                }
            }
        }
    }
    -_links: {
        -self: {
            href: "http:\/\/192.168.33.101\/events\/2182"
        }
    }
}

Originally posted by @Alanin at zfcampus/zf-hal#53

Page query param added to collection self link

When I request a collection route while using pagination I get the links rendered as follows:

    "_links": {
        "self": {
            "href": "http://hostname/api/v1/users?page=1"
        },
        "first": {
            "href": "http://hostname/api/v1/users"
        },
        "last": {
            "href": "http://hostname/api/v1/users?page=2"
        }
    },

The url I requested was http://hostname/api/v1/users. The self link in the response gets a pagination param while the first page doesn't. Shouldn't it be the other way around?
So like this:

    "_links": {
        "self": {
            "href": "http://hostname/api/v1/users"
        },
        "first": {
            "href": "http://hostname/api/v1/users?page=1"
        },
        "last": {
            "href": "http://hostname/api/v1/users?page=2"
        }
    },

On the client I would like to dynamically create links to my other pages by adding a page query parameter including a page number to the collection self link. Right now I cannot do this because the self link already holds a page param. I would expect the self link to be a base collection link.


Originally posted by @Wilt at zfcampus/zf-hal#90

Error in latest version (1.4.2)

/var/www/vendor/zfcampus/zf-hal/src/Entity.php at line : 55
E_USER_DEPRECATED : Direct property access to ZF\Hal\Entity::$entity is deprecated, use getters instead.

Originally posted by @developer-devPHP at zfcampus/zf-hal#154

Invalid page size parameter causes a 500 Internal Server Error

If you configure a page size parameter, values that are not positive integers (or -1) will result in a 500 Internal Server Error and a stack trace. The exceptions that trigger this (vendor/zfcampus/zf-hal/src/Collection.php line 302 and 310) indicate the problem is one of validation rather than something going wonky on the server. IMHO, this should not be a 500, but could either be a 400 or 422 or it could "fail safe" back to using the configured default and not causing an error at all.


Originally posted by @dstockto at zfcampus/zf-hal#141

entityLinks from Collection are not set in the embeded entity.

If I set entity links within collection using $collection->setEntityLinks($LinkColection) these are not rendered.
Within Hal plugin exctractCollection() method if the $entity has metadataMap then its created from metadataMap inclusive links.

if (is_object($entity) && $metadataMap->has($entity)) { $entity = $this->getResourceFactory()->createEntityFromMetadata($entity, $metadataMap->get($entity)); }

After that there is only the call for rendering the $entity.
if ($entity instanceof Entity) { // Depth does not increment at this level $collection[] = $this->renderEntity($entity, $this->getRenderCollections(), $depth, $maxDepth); continue; }

Probably there shall be a call to $entity->setLink($halCollection->getEntityLinks()) before the rendering.


Originally posted by @vlx73 at zfcampus/zf-hal#175

Would be nice to allow multiple identifiers for a route

I think this would be a useful feature but I'm also open to feedback on how I might be doing it wrong or should be doing something differently. I have a route which includes 3 identifiers. My route essentially looks like

/api/apiname/resource/:resourceId[/subtype1/:subtype1/subtype2/:subtype2]

I don't really want to make another resource which was just resource id and subtype 1, and I don't think putting in 2 optional parts in the route is a good way to go. It doesn't appear that I can set any more than a single string as the identifier. In the example above, the HAL links for the entity won't include both subtype1 and subtype2, but does contain up to :resourceId.

If route identifier were allowed to be an array, it seems that the above would be possible, but considering the number of places that this touches, I wanted to get some feedback on it before going forward with a change or PR.

To me, it seems this could be a good idea and I don't see a problem with being able to support it, but would appreciate feedback.


Originally posted by @dstockto at zfcampus/zf-hal#36

PHP 8.0 support

Feature Request

Q A
New Feature yes

Summary

To be prepared for the december release of PHP 8.0, this repository has some additional TODOs to be tested against the new major version.

In order to make this repository compatible, one has to follow these steps:

  • Modify composer.json to provide support for PHP 8.0 by adding the constraint ~8.0.0
  • Modify composer.json to drop support for PHP less than 7.3
  • Modify composer.json to implement phpunit 9.3 which supports PHP 7.3+
  • Modify .travis.yml to ignore platform requirements when installing composer dependencies (simply add --ignore-platform-reqs to COMPOSER_ARGS env variable)
  • Modify .travis.yml to add PHP 8.0 to the matrix (NOTE: Do not allow failures as PHP 8.0 has a feature freeze since 2020-08-04!)
  • Modify source code in case there are incompatibilities with PHP 8.0

Hal::setEntityExtractor dependency and EntityExtractor extractEntity method is marked private

I tried to add some custom logic to an EntityExtractor. First I thought of making a custom class implementing the Zend\Stdlib\Extractor\ExtractionInterface, but since the Hal::setEntityExtractor method is dependent on EntityExtractor I was unable to set my custom class. Would it not be enough to check for ExtractionInterface in the setter method?

Then when I moved to extending the existing ZF\Hal\Extractor\EntityExtractor class it turned out to be impossible to overwrite the extractEntity method since the method is marked private. Would it be possible to make it to a protected method so it becomes possible to redefine the method in my custom (extended) class.


Originally posted by @Wilt at zfcampus/zf-hal#116

Psalm integration

Feature Request

Q A
QA yes

Summary

As decided during the Technical-Steering-Committee Meeting on August 3rd, 2020, Laminas wants to implement vimeo/psalm in all packages.

Implementing psalm is quite easy.

Required

  • Create a psalm.xml in the project root
  • Copy and paste the contents from this psalm.xml.dist
  • Run $ composer require --dev vimeo/psalm
  • Run $ vendor/bin/psalm --set-baseline=psalm-baseline.xml
  • Add a composer script static-analysis with the command psalm --shepherd --stats
  • Add a new line to script: in .travis.yml: - if [[ $TEST_COVERAGE == 'true' ]]; then composer static-analysis ; fi
  • Remove phpstan from the project (phpstan.neon.dist, .travis.yml entry, composer.json require-dev and scripts)
Optional
  • Fix as many psalm errors as possible.

Is it possible to hint at the HTTP method with this library?

I'm not sure if I've managed this before or not, thought I had but can't seem to find the configuration... Is it possible to return a HTTP method inside a link? I want to achieve something similar to Paypal's HATEOAS implementation, where the HTTP method is returned along with a link.

My metadata map currently looks like this:

    'Users\\V1\\Rest\\User\\UserEntity' => array(
        'entity_identifier_name' => 'id',
        'route_name' => 'users.rest.user',
        'route_identifier_name' => 'user_id',
        'hydrator' => 'Zend\\Hydrator\\ClassMethods',
        'links' => array(
            'role' => array(
                'rel' => 'create-role',
                'route' => array(
                    'name'    => 'users.rest.roles',
                    'options' => array(
                        'method' => 'GET'
                    ),
                ),
            )
        ),
    ),

Though it is currently unsuccessful, I've tried 'params' as well but to no avail.


Originally posted by @danbroooks at zfcampus/zf-hal#136

Entity identifier other than 'id' does not generate self link in collection

If I set 'entity_identifier_name' => 'code' then the links to the entities are not generated when fetching the collection. The link is generated when fetching an entity only.

Following the code at https://groups.google.com/a/zend.com/forum/#!msg/apigility-users/p4BWDl9oiIM/YnitOfkEAfUJ will now provide an entry for all properties in the entity. Obviously not a good response since these properties are not set when fetching a collection.

I have tried poking around in the code, and in
ZF\Hal\Plugin\Hal::setEventManager() it seems to be hardcoded looking for 'id' (lines 168/169). If I hack this to 'code' then it populates just fine.

In the meantime, I have hooked into the 'getIdFromEntity' listener and made an ugly hack to try to extract this key.


Originally posted by @vorgas at zfcampus/zf-hal#166

Dynamic route params for collections using the metadata_map

When using the metadata_map to create further links, there is a problem with the route parameters.

Following config:

    'zf-hal' => [
        'metadata_map' => [
            'List\\List\\List' => [
                'route_name' => 'lists.rest.list',
                'route_identifier_name' => 'listId',
                'entity_identifier_name' => 'id',
                'links' => [
                    [
                        'rel' => 'reference-list',
                        'route'=> [
                            'name' => 'lists.rest.referencelist',
                        ],
                    ],
                ],
            ],
        ],
    ],
    'router' => [
        'routes' => [
            'lists.rest.list' => [
                'type' => 'segment',
                'options' => [
                    'route' =>  '/rest/list[/:listId]',
                ],
            ],
            'lists.rest.referencelist' => [
                'type' => 'segment',
                'options' => [
                    'route' =>  '/rest/list/:listId/reference[/:referenceId]',
                ],
            ],
        ],
    ],

If you query a single resource from lists.rest.list (/rest/list/1) the links will generated perfectly.
But if you query the collection from lists.rest.list (/rest/list) you will receive an error.

The reason is, that in this case the route param listId is unknown.
I thought, that the param should be set based on the mapping configuration (route_identifier_name and entity_identifier_name).

Is there an error in the configuration?
Or is there a bug in the mapping?


Originally posted by @FrankGiesecke at zfcampus/zf-hal#140

Allow hydrators to set `_embedded`

I have an odd case where the entity (hal resource) has a property name that is also (sometimes) the name of a link and embedded object. Basically a message can be sent by a user (which a link and embedded object exist for), or a non-user where some basic information had been gathered to identify them (but no link or embedded resource because they're not an user).

Quick example:

{
    "sender": {
        "name": "Test User",
        "number": "15556667878"
    },
    "_links": {
        "sender": {
            "href": "http://example.com/users/12345"
        }
    },
    "_embedded": {
        "sender": {
            "id": "12345",
            "name": "Test User",
            "number": "15556667878"
        }
    }
}

Because the hydrator can send back a link collection, which is merged into the entities links during render, it's possible to have both a link and an entity property with the same name. However, it's not possible to have both a entity property and an embedded entity with the same name, since both are passed from the hydrator using a simple array.

My thought is to add support for hydrators to return an _embedded key (optionally), and anything found there will be treated as embedded entities.

If I put this together as a PR, any opposition to merging it in?


Originally posted by @tjlytle at zfcampus/zf-hal#52

Support for read-only resources that are loaded from cache

We have quite some resources in our application that are read-only. Those resources will render the same for each request which means once the hal/json response is rendered it could simply be stored in a cache and on the next request we skip a lot of overhead by returning the cached hal/json.

I could imagine a read-only field in the controller config which defaults to false and a cache service in the Hal plugin to support such functionality.

Is there any interest in a pull request for something like this for read-only resources.


Originally posted by @Wilt at zfcampus/zf-hal#117

createEntityFromMetadata not use entityExtractor result

Using Apigility, I configured a service with Reflection Hydrator (in my case a custom hydrator which transforms the array properties keys into camel-case).

The problem is that every elaboration on the object made by the hydrator are unused: I notice in ResourceFactory.php there is:

55)    $data = $this->entityExtractor->extract($object);
       [...]
72)    $halEntity = new Entity($object, $id);

where $object is the original object.

If I pass $data to the Entity constructor, as I expected, everything works.
Did I misunderstand the meaning of this lines?


Originally posted by @DanielBertocci at zfcampus/zf-hal#143

Api problem message as json

When I make request on non existing resource page /users?page=1234567 I receive:

{
  "type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
  "title": "Conflict",
  "status": 409,
  "detail": "Invalid page provided"
}

As you see it a api problem. However in Content-Type you can find application/json instead of application/problem+json


Originally posted by @snapshotpl at zfcampus/zf-hal#121

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.