zfcampus / zf-hal Goto Github PK
View Code? Open in Web Editor NEWLicense: BSD 3-Clause "New" or "Revised" License
License: BSD 3-Clause "New" or "Revised" License
/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.
I have some resources with identifier $id = 0. This is currently not rendering correctly inside ZF\Hal\Plugin\Hal
.
This is related to the createEntity
method on line 751 in ZF\Hal\Plugin\Hal
which should explicitly check if ($id === false)
in the if clause on line 763 instead of the if($id)
. Right now the method wrongly returns an ApiProblem
if the entity has $id = 0
.
Link to ZF\Hal\Plugin\Hal:createEntity
: https://github.com/zfcampus/zf-hal/blob/master/src/Plugin/Hal.php#L751
Also check related issue in zf-hal: zfcampus/zf-rest#35
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.
HAL will loop into error when resources have self or circular references.
I started work on this by limiting the depth to render a given resource to a specified number but I think a better approach is to give a boolean to render reference details for a resource.
I still want the _embedded [ $key ] [ _links ] [ self ]
for all embedded resources but I don't want the resource details which will prevent recursion. So I think a boolean config default and metadata map variable should be added which specifies "display details of embedded references?" for a given resource.
What's your view?
Transcript from #apigility freenode.
[16:14] <matuszeman> Hi there. I'm failing in implementing a functionality in renderEntity listener. It looks like that an entity available from hal object is array instead of entity object. Any help?
[16:16] == lynxpardinus [[email protected]] has joined #apigility
[16:24] == shafox [[email protected]] has joined #apigility
[16:24] == MANCHUCK [[email protected]] has joined #apigility
[16:34] <weierophinney> matuszeman: that's usually the case when fetching database records; unless you have a hydrating resultset setup, you'll get arrays by default.
[16:39] <matuszeman> weierophinney: resource returns entity object but that's it's converted into array here: ZF\Hal\Plugin\Hal::createEntityFromMetadata() - line 700
[16:42] <weierophinney> matuszeman: hm... renderEntity() actually triggers the renderEntity event _before_ calling createEntityFromMetadata(). So, if you're getting an array passed to the listener, that means that you're deriving the entity earlier than that.
[16:42] <weierophinney> matuszeman: is this a REST or RPC service?
[16:42] <matuszeman> rest
[16:44] <weierophinney> what operation? GET, POST, PATCH, etc.?
[16:44] <matuszeman> get ... collections are ok.
[16:45] <weierophinney> aha -- RestController::get() calls Hal::createEntity(), which in turn calls Hal::createEntityFromMetadata()
[16:46] <weierophinney> which is why you have an array by the time you get to renderEntity()
[16:46] <matuszeman> yep.
[16:46] <weierophinney> matuszeman: I'm thinking perhaps we should trigger renderEntity from createEntity() as well.
[16:46] <weierophinney> Could you write a feature request for that in the zf-hal tracker, and write up what you're trying to do that prompts it?
[16:47] <matuszeman> what's the URL?
[16:50] <matuszeman> I've also noticed that we trigger get.post event with both entity and resource params being same object.. is it for BC?
[16:51] <matuszeman> weierophinney: .... in restful controller.
[16:51] <weierophinney> matuszeman: yes.
[16:51] <weierophinney> matuszeman: technically, we likely should have removed those for 1.0, but I didn't want to break apps for anyone who stuck with us throughout the beta process.
[16:53] <matuszeman> so what's intended behaviour? where should we get entity object from? Hal entity only?
[16:53] <matuszeman> I'd like to quick-fix it somehow so I can start using it that way.
[16:54] <weierophinney> matuszeman: intended behavior should likely be anytime we are trying to render or create an entity, we should trigger renderEntity.
[16:55] <weierophinney> matuszeman: I'm actually not sure that createEntity is doing the right thing. I'm thinking it should not do the "createEntityFromMetadata()" call.
[16:55] <weierophinney> and instead should just pass what it was given into the Hal Entity or Collection.
[16:55] <weierophinney> matuszeman: removing that call from createEntity() would actually solve your problem, too, as then renderEntity would operate on the original object.
[16:56] <matuszeman> it was my impression also.. but I'm pretty new to apigility so I wasn't sure.
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?
Disregard: completely my issue and inferred the hal plugin was checking something it wasn't.
When we request a collection and we set an invalid (non existing) route for the child resources in our metadata_map
the rendering fails and returns a ZF\Hal\Exception\CircularReferenceException
.
It is hard to debug since the real cause of the issue lies in the fromLink
method on line 698 where the rendering of the url from the $linkDefinition
config fails.
The problem lies in DefaultRenderingStrategy at line 105 where it only catches errors in events of type MvcEvent::EVENT_RENDER_ERROR
.
On failure Url
helper returns an exception of type Zend\Mvc\Router\Exception\RuntimeException
but this exception is not caught since $e->getName()
equals MvcEvent::EVENT_RENDER
('render') and not MvcEvent::EVENT_RENDER_ERROR
('render.error').
After failure the Hal
plugin tries to render the same Collection
resource again and then the CircularReferenceException
occurs. Somehow the MvcEvent::EVENT_RENDER_ERROR
triggered on line 113 never results in output.
I don't know how to properly solve this. Maybe a discussion here could help me understand on how to deal with this issue...
I've been looking around and there doesn't seem to be much support for "curies" in this project.
At first I thought it would be OK to just create a new Link with rel "curies" and then the rest of the info, but it turns out that the reserved "curies" rel is meant to be an array / collection of elements instead of a single link.
Have I missed a way to specify curies? And if not: would you be interested (short-term) in a PR?
I have created a resource that when fetched GET /resource/{id}
returns a response (on success/found) with the following template:
{
"name": "Name",
"code" "Code",
"_embedded" : {
"foos" : [{
"name" : "bar1",
"value" : "value1",
},
{
"name" : "bar2",
"value" : "value2",
}]
}
}
fetch($id)
fetch($id){
$resource = ... // query resource
$resourceEntity = new ResourceEntity();
$resourceEntity->exchangeArray($resource);
$foos = ... // query foos
$fooAdapter = new Adapter\ArrayAdapter($foos);
$fooCollection = new Foo\FooCollection($fooAdapter);
$resourceEntity->foos = $fooCollection;
return $resourceEntity;
}
My problem is that when the fooCollection consists of more than 10 entities only 10 entities gets rendered in response.
default page size
of the FooCollection
in the module.config.php
but the setting is equal to 25 in my case.Paginator
to be set to -1, since by doing so (I think) will not paginate the result."foos"
property. I showed all the results but it did not render each foo
entities as a HAL
entityThe renderEntity method of the \ZF\Hal\Plugin\Hal plugin triggers an event on the first line of that method. It would be very nice if an event is also triggered add the bottom of that method, just before the entity is returned.
That way I can modifiy some data in the array before it's returned.
/**
* Render an individual entity
*
* Creates a hash representation of the Entity. The entity is first
* converted to an array, and its associated links are injected as the
* "_links" member. If any members of the entity are themselves
* Entity objects, they are extracted into an "_embedded" hash.
*
* @param Entity $halEntity
* @return array
*/
public function renderEntity(Entity $halEntity, $renderEntity = true)
{
$this->getEventManager()->trigger(__FUNCTION__, $this, array('entity' => $halEntity));
//...
// Add something like this:
$this->getEventManager()->trigger(__FUNCTION__ . '.post', $this, array('entity' => $entity));
return $entity;
}
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.
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.
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?
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?
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
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.
There is a weak correlation between
config[zf-rest][controllerName][identifier_name]
which refers to the identifier parameter name used for routes and
config[zf-hal][metadata_map][entityName][identifier_name]
which refers to the entity primary key.
When these values are not equal the parameters passed to marshalSelfLinkFromMetadata are invalid because the identifier name passed to the self linking function is the entity primary key and not the route parameter name.
Incorrect identifier name is fetched: https://github.com/zfcampus/zf-hal/blob/master/src/ZF/Hal/Plugin/Hal.php#L607
A good reason for these parameters to not be equal is when you use 'id' consistently for your resource primary keys and to avoid putting 'id' into scope when dispatching controller actions to resources from RPC calls where it would be "bad" to have a generic "id" in scope when dispatching calls to resource controllers because it directs them to return only a matching record. So, by making the route parameter different than the entity id you can avoid this.
"Self" links are forced in \ZF\Hal\Plugin\Hal::createEntityFromMetadata()
, around line 764 (toward the end of the function):
$entity = new Entity($data, $id);
$links = $entity->getLinks();
$this->marshalMetadataLinks($metadata, $links);
if (!$links->has('self')) {
$link = $this->marshalSelfLinkFromMetadata($metadata, $object, $id, $metadata->getRouteIdentifierName());
$links->add($link);
}
What's interesting is that links for collections are indeed not required in method createCollectionFromMetadata()
, around line 867:
if (!$links->has('self')
// this extra condition avoids the "self" link unless a url or route are specified
&& ($metadata->hasUrl() || $metadata->hasRoute())
) {
$link = $this->marshalSelfLinkFromMetadata($metadata, $object);
$links->add($link);
}
In the HAL specification "self" links are not "required". I have a use-case with Apigility where I want to have the HAL mapping configured for an entity (so that it gets extracted) but while providing NO "self" links (simply because there's no REST service for that entity).
Would it be ok if we make the condition in createEntityFromMetadata()
similar to the condition in createCollectionFromMetadata()
?
The method marshalMetadataLinks
in the Hal
plugin is only a proxy method to the marshalMetadataLinks
method in the ResourceFactory
class. This method seems not to be used at all inside the Hal
plugin itself (or any other classes). I think this method can/should be removed. It is only confusing that it is (still) available in theHal
plugin.
When query parameters are appended to endpoint URIs, they aren't included in the generated Links. For example:
Let's say we GET a collection at http://domain.com/resource. If the request contains a filter to the collection via a query parameter, e.g. http://domain.com/resource?foo=bar, the generated Links will not include the query parameters of the request, and will be of the format http://domain.com/resource?page=x.
I'm not sure if this is by design, but it causes issues with discoverability.
Currently, the entity rendering makes no difference if it is embedded or not.
Could it be possible to allow to treat embedded entities differently ?
E.g: I have an entity User
, and I want filter this one differently when it is embedded (to remove more properties and make it lighter).
The filtering is the responsibility of the hydrator, but it does not know when the entity is embedded. Whereas the Hal
plugin knows it. So I think it should be passed from the Hal
plugin to the hydrator. But I fear it leads to bad design. So maybe there is another way ?
5 minutes later:
A better design, could be to allow to provide a factory to the hydrator
key under metadata_map
. In this factory, the context would be injected, it would allow to create a custom hydrator.
https://github.com/zfcampus/zf-hal/blob/master/src/Plugin/Hal.php#L1284
The function exposes private and protected properties of an object. This should really use get_object_vars instead which will properly only grab the publicly declared properties of a class.
Recently I was playing with the lib and I faced a problem when an entity has an embedded collection. To be more specific I did play with Apigility and Doctrine, and the problem came when I received an entity with ManyToMany relation. I did a quick fix https://github.com/npetrovski/zf-hal/commit/d58145cb372e0c62dbcbcd3e9e448c07f7aa0a40 but I wondering if I miss something?
Resolution as mentioned below:
we should likely make it possible to create the Hal\Entity without needing to serialize it first
Transcript from IRC:
<djule5> weierophinney: I want to return plain json representations, but it seems that Apigility is dependent on the Hal module.
<weierophinney> djule5: you can choose a different content negotiation selector for all API services, and while HalJson is the default for REST, you can also select just Json, which will return bare JSON. THAT SAID: we do not do any auto-extraction of objects to JSON for you; you will need to define that logic yourself, or ensure you return JsonSerializable objects.
<djule5> weierophinney: Yes, that's basically what I've been doing. I also tried deleting the zf-hal config array, and it seemed to work. However, going back to apigility-admin and saving a service, it creates back the zf-hal array with the default values for the service and then calling the service returns me a "Zend\Stdlib\Hydrator\ArraySerializable::extract expects the provided object to implement getArrayCopy()". Do I need to have zf-hal config if none of my services return HalJson?
<weierophinney> djule5: the admin API creates that information, even if you're not using it.
<weierophinney> djule5: you _could_ potentially use that information yourself to do the object extraction, too.
<djule5> weierophinney: Following our discussion yesterday, would you recommend not using the admin interface when not using HalJson (if I don't want the zf-hal config)? Or could zf-apigility-admin take in account the chosen Content Negotiation Selector (i.e. Json) and not regenerate the zf-hal/metadata_map accordingly?
<weierophinney> djule5: the admin will continue generating the zf-hal information, even if ultimately it won't be used. I'd use the admin interface, personally; lot less to remember and worry about in the configuration.
<djule5> weierophinney: the problem is that it's actually used, the Hal plugin gets called and tries to extract the entity (in ZF\Hal\Plugin\Hal::convertEntityToArray) using the hydrator set by the admin interface (i.e. ArraySerializable) which I do not implement the required methods on my Entity. I would expect my Entity left untouched by Hal and forwarded to ZF\ContentNegotiation\JsonModel to be serialized, or maybe I'm missing something?
<weierophinney> djule5: run a `composer update` in your project. :) We actually fixed that behavior in zf-hal for this latest release, so that it retains the original entity in the ZF\Hal\Entity object now.
<weierophinney> djule5: and you're right -- we _are_ casting to a ZF\Hal\Entity/ZF\Hal\Collection.
<weierophinney> djule5: however, we have some logic in ZF\ContentNegotiation\JsonModel to pull the original entity/collection out of the HAL objects.
<weierophinney> djule5: so with the upgrade, it should now work.
<djule5> weierophinney: interesting... I actually ran the update yesterday, and I'm testing the behavior right now and it's still doing it (500 with "Zend\Stdlib\Hydrator\ArraySerializable::extract expects the provided object to implement getArrayCopy()")
<weierophinney> djule5: ah -- that makes sense. The logic when creating the HAL entity does use the hydrator to cast to array in order to be able to extract the identifier (as a Hal\Entity object requires both the original entity AND an identifier)
<weierophinney> djule5: can you file an issue for that, please? I'll see if I can find a way to create a flag for disabling that.
<weierophinney> djule5: what it does is serialize, extract the identifier, but then injects the original object in to the Hal\Entity. (We cache the seriailized version for when serialization happens later)
<weierophinney> (if it ever happens)
<djule5> weierophinney: yes ok I see what you mean... I do think however that I was getting the same behavior before that fix you're referring to (I'm looking at the commit diff on github)
<djule5> weierophinney: Hal is just responsing accordingly to the metadata map config... hence why I was thinking probably it should just not be there in the first place
<weierophinney> djule5: the problem is that we do the Hal\{Entity|Collection} creation in zf-hal. We don't want to do a ton of conditional logic in there based on the selected view model -- that's the whole point of having zf-content-negotiation in the first place. That said, we should likely make it possible to create the Hal\Entity without needing to serialize it first. File the issue, and I'll work on a fix; I have a few ideas.
It may be useful to add "total" and "per_page" properties to paginated collections, like the following:
{
"_links": { /* ... */ },
"_embedded": { /* ... */ },
"total": 137,
"per_page": 25
}
The primary question I have is: should this behavior be toggle-able, or just included by default?
Also, for non-paginated collections, we may want to provide a "total"; again, the question is whether or not this should be toggle-able.
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:
false
We can either:
{
"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"
}
}
}
{
"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"
}
}
}
{
"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.
This commit has created a circular dependancy, the viewHelperFactory depends on the controllerPluginFactory and vice-versa.
Resulting in a Zend\ServiceManager\Exception\CircularDependencyFoundException :
Circular dependency for LazyServiceLoader was found for instance Hal
Shouldn't the Plugin/Hal.php object itself be managed via the ServiceManager and returned from the view and controller plugin factories ?
When a user does a POST request to my service I put the payload on a queue and handle the entity persistance later on.
In this case I don't have an identifier for the entity available on creation and the RestController will throw an 422 error No entity identifier present following entity creation
. In the commit history I see something has changed to support null
identifiers [https://github.com/zfcampus/zf-hal/commit/55a7abfb36248a325341d3cb501118781a1ba484]. Imo the ApiProblem in RestController [https://github.com/zfcampus/zf-rest/blob/master/src/RestController.php#L326] should also be removed to handle this case correctly.
-1 would mean that we want all items in first page.
Obviously, whoever sets -1 as page size, will know what it implies.
The use case scenario is the implementation of static/small lists using REST.
GET /roles
Sometimes it doesn't make sense paginating collections under 50 items for example, if they have a really small payload, and we had to show all of the items in 1 page anyway. At the moment I'm just setting a high enough default value, but it looks hacky.
Not sure if this is Hal related, Rest related, Paginator\Adapter\DbSelect related or something else, but, if you have special characters in fields (i.e., foreign languages) in the database and attempt to return those values through a REST resource's fetch() or fetchAll(), you will get a completely empty response. Headers indicate status code 200, but zero content. If I remove the rows with special characters, or request a record without special characters, the API works as expected. This occurs wether returning a Collection or an Entity. The SQL data can be downloaded here: http://spectravp.com/locale_countries.sql
Of note: The DB charset is all UTF-8; the DB Adapter is configured for UTF-8. However, if I specifically encode the data in question in the entity with mb_convert_encoding(), the API response is normal and as expected.
Add configuration option for serverUrl view-helper in hal.
Hi folks,
I wrote a caching plugin and likely ran into an edge case, which under normal circumstances might not get triggered.
What it does: save the full ViewModel (HalJsonModel) onResponse (listener on MvcEvent::EVENT_FINISH) and inject it (if available in the cache) via onDispatch (listener on MvcEvent::EVENT_DISPATCH) and stopPropagation.
For some reason, the collection links will be reinjected after I set the cached Model into the MvcEvent/Reponse.
Normal HAL result:
{
"_links": {
"self": {
"href": "https://myapi.dev/books?page=1"
},
"first": {
"href": "https://myapi.dev/books"
},
"last": {
"href": "https://myapi.dev/books?page=1"
}
},
"_embedded": {
"books": [
{
With my caching plugin:
{
"_links": {
"self": {
"href": "https://myapi.dev/books?page=1"
},
"first": [
{
"href": "https://myapi.dev/books"
},
{
"href": "https://myapi.dev/books"
}
],
"last": [
{
"href": "https://myapi.dev/books?page=1"
},
{
"href": "https://myapi.dev/books?page=1"
}
],
},
"_embedded": {
"books": [
{
From my reaserch this is triggered here:
https://github.com/zfcampus/zf-hal/blob/master/src/Plugin/Hal.php#L554
Or further down the road:
https://github.com/zfcampus/zf-hal/blob/master/src/Link/PaginationInjector.php#L73
https://github.com/zfcampus/zf-hal/blob/master/src/Link/PaginationInjector.php#L80
Now my question:
Why do the "first" and "last" links don't use the "overwrite" flag, like "self" already utilizes?
Beside the fact that there can only be one "first" page ;-) it feels wrong, that a JSON object is suddenly turning into an array.
Any ideas how to prevent that behaviour or could that be fixed in zf-hal directly?
Regards
Kevin
When I instantiate a \ZF\Hal\Entity with the entity and entityId, and return the Entity as the payload, the _links property is empty.
However if I create the entity using the ZF\Hal\Plugin\Hal::createEntity and explicitly pass in the route then the links are properly generated.
From my understanding thought this step should not be necessary? This same issue was raised in the google group.
I can't see any misconfiguration in my metadata_map
or route info.
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.
Related to: zendframework/zend-mvc#205
It's a common practice to attach listeners to event managers in a Delegator Factory.
With zend-mvc ^3.0 and zend-eventmanager ^3.0 it's a potentially BC break.
Can we consider to replace the EventManagerAwareInterface
with EventsCapableAwareInterface
in the project?
In this particular case, in my project I have a Listener attached to ZF\Hal\Plugin\Hal
event manager that injects links into the rendered entity.
I created metadata
for an entity
and added additional links in this metadata. In the rendered result these links are not included. I think the cause lies in https://github.com/zfcampus/zf-hal/blob/master/src/Plugin/Hal.php#L758
The $halEntity
is properly created through the createEntityFromMetadata
method (including my links and all), but in the next line it is directly overwritten by $halEntity = new Entity($entity, $halEntity->id);
. I don't understand why this line of code is there. Should it not simply be deleted? It totally overwrites the previously created result of createEntityFromMetadata
and makes the whole first case redundant.
I invite you again to take a look at what seems to be a critical bug. #146
exception 'Zend\ServiceManager\Exception\ServiceNotCreatedException' with message 'While attempting to create hal(alias: Hal) an invalid factory was registered for this instance type.'
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
Line 295 in 7c164ef
In our case its has the following consequences
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
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.
I get 500 status code when in request page param is lower than 1. For example: /resource?page=0
I think that's client problem so status code should be 400. What are you think?
https://github.com/zfcampus/zf-hal/blob/master/src/Collection.php#L282
I am trying to change the Entity Identifier of a Resource in the Admin UI to something different (ie. a name
property instead of the default id
), so that I can modify the identifier in the __self
link of Entities. I Change the Entity Identifier in the Admin UI from id
to name
, when I request for the Entity every thing looks fine, but when I tried to request for a Collection the embedded Entities are rendered with the their __self
still using the default identifier.
I have already verified that the entity_identifier_name
key in the zf-hal.metadata_map
has been updated.
namespace App\V1\Rest\Vehicle;
class VehicleEntity
{
public $id;
public $name;
public $group_id;
public $truck_type;
public $tons;
public $cbm;
public function getArrayCopy() {
return array(
'id' => $this->id,
'name' => $this->name,
'group_id' => $this->group_id,
'truck_type' => $this->truck_type,
'tons' => $this->tons,
'cbm' => $this->cbm,
);
}
public function exchangeArray(array $array) {
$this->id = $array['id'];
$this->name = $array['name'];
$this->group_id = $array['group_id'];
$this->truck_type = $array['truck_type'];
$this->tons = $array['tons'];
$this->cbm = $array['cbm'];
}
}
'zf-hal' => array(
'renderer' => array(
'render_embedded_entities' => false,
),
'metadata_map' => array(
'App\\V1\\Rest\\Vehicle\\VehicleEntity' => array(
'entity_identifier_name' => 'name',
'route_name' => 'kookaburra.rest.vehicle',
'route_identifier_name' => 'truck_id',
'hydrator' => 'Zend\\Stdlib\\Hydrator\\ObjectProperty',
),
'App\\V1\\Rest\\Vehicle\\VehicleCollection' => array(
'entity_identifier_name' => 'name',
'route_name' => 'kookaburra.rest.vehicle',
'route_identifier_name' => 'name',
'is_collection' => true,
),
),
),
Here's how I supply data to the collection:
public function fetchAll($params = array())
{
$select = new Sql\Select('vehicle');
$select->columns(array('name', 'group_id', 'truck_type', 'tons', 'cbm' ));
$paginatorAdapter = new DbSelect($select, $this->adapter);
$collection = new VehicleCollection($paginatorAdapter);
return $collection;
}
Route matches: /vehicle[/:truck_id]
Route identifier name: truck_id
Entity identifier name: name
Is this an expected behavior or am I just missing something?
I recently opened zfcampus/zf-apigility#95. I'm not sure if it belongs here or on apigility issue tracker.
Please close as appropriate.
Hey there,
currently using the ZF\Hal\Metadata\Metadata
to grab the hydrator defined in module config related to a specific entity.
As I wanted to inject the hydrator to my Zend\Stdlib\Hydrator\HydratorAwareInterface
which requires the Zend\Stdlib\Hydrator\HydratorInterface
, I get some IDE warnings which is quite annoying.
Since the Zend\Stdlib\Hydrator\HydratorInterface
implements the Zend\Stdlib\Extractor\ExtractionInterface
, there should be no problem to switch to the correct Interface.
If there is any problem, you should rename the method as of #128 since this naming is quite confusing.
Currently the HalViewHelper factory has a hardcoded dependency on ZF\Hal\Plugin\Hal. This dependency should be pulled from the service locator, so it can be overriden.
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?
A number of people on the mailing list and on IRC have noted that when using the ContentNegotiation\ViewModel
with controllers that will be returning HAL, they must set the terminate
flag to true
to prevent attempts to render within a layout.
This is due to the fact that while the terminate
flag is true
by default in ZF\Hal\View\HalJsonModel
, it's not true by default in ZF\ContentNegotiation\ViewModel
(and should not be!) -- which means that the value of the flag in the original view model is used when casting to HalJsonModel
.
If in the createEntity
method the $entity
is available in the MetadataMap
it will be changed into a hal Entity
in the first case of the switch using the createEntityFromMetadata
method:
https://github.com/zfcampus/zf-hal/blob/master/src/Plugin/Hal.php#L765
During this process the data is extracted from the entity by using the convertEntityToArray
method:
https://github.com/zfcampus/zf-hal/blob/master/src/Plugin/Hal.php#L708
The result is stored in a $generatedEntity
variable. Instead of using this generated entity directly another instance of hal Entity
is created like this:
$halEntity = new Entity($entity, $generatedEntity->id);
Since in the first param $entity
is an object the convertEntityToArray
will be called once more when later rendering the entity in the renderEntity
method. So we will be hydrating the same entity twice:
https://github.com/zfcampus/zf-hal/blob/master/src/Plugin/Hal.php#L500
Why is this? Why not stick with the $generatedEntity that holds the array?
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"
}
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.