Giter Club home page Giter Club logo

zf-rest's Introduction

ZF REST

Repository abandoned 2019-12-31

This repository has moved to laminas-api-tools/api-tools-rest.

Build Status Coverage Status

Introduction

This module provides structure and code for quickly implementing RESTful APIs that use JSON as a transport.

It allows you to create RESTful JSON APIs that use the following standards:

Requirements

Please see the composer.json file.

Installation

Run the following composer command:

$ composer require zfcampus/zf-rest

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

"require": {
    "zfcampus/zf-rest": "^1.3"
}

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' => [
        /* ... */
        'ZF\Rest',
    ],
    /* ... */
];

zf-component-installer

If you use zf-component-installer, that plugin will install zf-rest as a module for you.

Configuration

User Configuration

The top-level key used to configure this module is zf-rest.

Key: Controller Service Name

Each key under zf-rest is a controller service name, and the value is an array with one or more of the following keys.

Sub-key: collection_http_methods

An array of HTTP methods that are allowed when making requests to a collection.

Sub-key: entity_http_methods

An array of HTTP methods that are allowed when making requests for entities.

Sub-key: collection_name

The name of the embedded property in the representation denoting the collection.

Sub-key: collection_query_whitelist (optional)

An array of query string arguments to whitelist for collection requests and when generating links to collections. These parameters will be passed to the resource class' fetchAll() method. Any of these parameters present in the request will also be used when generating links to the collection.

Examples of query string arguments you may want to whitelist include "sort", "filter", etc.

Starting in 1.5.0: if a input filter exists for the GET HTTP method, its keys will be merged with those from configuration.

Sub-key: controller_class (optional)

An alternate controller class to use when creating the controller service; it must extend ZF\Rest\RestController. Only use this if you are altering the workflow present in the RestController.

Sub-key: identifier (optional)

The name of event identifier for controller. It allows multiple instances of controller to react to different sets of shared events.

Sub-key: resource_identifiers (optional)

The name or an array of names of event identifier/s for resource.

Sub-key: entity_class

The class to be used for representing an entity. Primarily useful for introspection (for example in the Apigility Admin UI).

Sub-key: route_name

The route name associated with this REST service. This is utilized when links need to be generated in the response.

Sub-key: route_identifier_name

The parameter name for the identifier in the route specification.

Sub-key: listener

The resource class that will be dispatched to handle any collection or entity requests.

Sub-key: page_size

The number of entities to return per "page" of a collection. This is only used if the collection returned is a Zend\Paginator\Paginator instance or derivative.

Sub-key: max_page_size (optional)

The maximum number of entities to return per "page" of a collection. This is tested against the page_size_param. This parameter can be set to help prevent denial of service attacks against your API.

Sub-key: min_page_size (optional)

The minimum number of entities to return per "page" of a collection. This is tested against the page_size_param.

Sub-key: page_size_param (optional)

The name of a query string argument that will set a per-request page size. Not set by default; we recommend having additional logic to ensure a ceiling for the page size as well, to prevent denial of service attacks on your API.

User configuration example:

'AddressBook\\V1\\Rest\\Contact\\Controller' => [
    'listener' => 'AddressBook\\V1\\Rest\\Contact\\ContactResource',
    'route_name' => 'address-book.rest.contact',
    'route_identifier_name' => 'contact_id',
    'collection_name' => 'contact',
    'entity_http_methods' => [
        0 => 'GET',
        1 => 'PATCH',
        2 => 'PUT',
        3 => 'DELETE',
    ],
    'collection_http_methods' => [
        0 => 'GET',
        1 => 'POST',
    ],
    'collection_query_whitelist' => [],
    'page_size' => 25,
    'page_size_param' => null,
    'entity_class' => 'AddressBook\\V1\\Rest\\Contact\\ContactEntity',
    'collection_class' => 'AddressBook\\V1\\Rest\\Contact\\ContactCollection',
    'service_name' => 'Contact',
],

System Configuration

The zf-rest module provides the following configuration to ensure it operates properly in a Zend Framework application.

'service_manager' => [
    'invokables' => [
        'ZF\Rest\RestParametersListener' => 'ZF\Rest\Listener\RestParametersListener',
    ],
    'factories' => [
        'ZF\Rest\OptionsListener' => 'ZF\Rest\Factory\OptionsListenerFactory',
    ],
],

'controllers' => [
    'abstract_factories' => [
        'ZF\Rest\Factory\RestControllerFactory',
    ],
],

'view_manager' => [
    // Enable this in your application configuration in order to get full
    // exception stack traces in your API-Problem responses.
    'display_exceptions' => false,
],

ZF2 Events

Listeners

ZF\Rest\Listener\OptionsListener

This listener is registered to the MvcEvent::EVENT_ROUTE event with a priority of -100. It serves two purposes:

  • If a request is made to either a REST entity or collection with a method they do not support, it will return a 405 Method not allowed response, with a populated Allow header indicating which request methods may be used.
  • For OPTIONS requests, it will respond with a 200 OK response and a populated Allow header indicating which request methods may be used.

ZF\Rest\Listener\RestParametersListener

This listener is attached to the shared dispatch event at priority 100. The listener maps query string arguments from the request to the Resource object composed in the RestController, as well as injects the RouteMatch.

ZF2 Services

Models

ZF\Rest\AbstractResourceListener

This abstract class is the base implementation of a Resource listener. Since dispatching of zf-rest based REST services is event driven, a listener must be constructed to listen for events triggered from ZF\Rest\Resource (which is called from the RestController). The following methods are called during dispatch(), depending on the HTTP method:

  • create($data) - Triggered by a POST request to a resource collection.
  • delete($id) - Triggered by a DELETE request to a resource entity.
  • deleteList($data) - Triggered by a DELETE request to a resource collection.
  • fetch($id) - Triggered by a GET request to a resource entity.
  • fetchAll($params = []) - Triggered by a GET request to a resource collection.
  • patch($id, $data) - Triggered by a PATCH request to resource entity.
  • patchList($data) - Triggered by a PATCH request to a resource collection.
  • update($id, $data) - Triggered by a PUT request to a resource entity.
  • replaceList($data) - Triggered by a PUT request to a resource collection.

ZF\Rest\Resource

The Resource object handles dispatching business logic for REST requests. It composes an EventManager instance in order to delegate operations to attached listeners. Additionally, it composes request information, such as the Request, RouteMatch, and MvcEvent objects, in order to seed the ResourceEvent it creates and passes to listeners when triggering events.

Controller

ZF\Rest\RestController

This is the base controller implementation used when a controller service name matches a configured REST service. All REST services managed by zf-rest will use this controller (though separate instances of it), unless they specify a controller_class option. Instances are created via the ZF\Rest\Factory\RestControllerFactory abstract factory.

The RestController calls the appropriate method in ZF\Rest\Resource based on the requested HTTP method. It returns HAL payloads on success, and API Problem responses on error.

zf-rest's People

Contributors

acabala avatar adamculp avatar artdevgame avatar axalian avatar e-belair avatar ezimuel avatar maciek-wozniak avatar maldoran avatar michaelgooden avatar michaelmoussa avatar michalbundyra avatar neeckeloo avatar olavocneto avatar pearson-lucas-dev avatar ralphschindler avatar samsonasik avatar sasezaki avatar stevleibelt avatar telkins avatar tomhanderson avatar weierophinney avatar wilt avatar

Stargazers

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

Watchers

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

zf-rest's Issues

RestController to call *.post events before calling createHalEntity

I have a listener that I want to inject values into the entity. I only want to do this on getList.post, get.post and create.post. The issue I am having is that createHalEntity will extract the returned entity. Once the Hal\Extractor\EntityExtractor call extract(), it will cache the results. What I want to inject will then have to listen on renderEntity.post and put it directly into the payload. My listener now needs to listen to 2 events in order to get this data passed along.

Too demostrate:

class PermissionListener
{
    /**
     * @var array
     */
    protected $listeners = [];

    /**
     * @param SharedEventManagerInterface $events
     */
    public function attachShared(SharedEventManagerInterface $events)
    {
        $this->listeners[] = $events->attach('ZF\Hal\Plugin\Hal', 'renderEntity.post', [$this, 'onRender']);
        $this->listeners[] = $events->attach('ZF\Rest\RestController', ['create.post', 'get.post'], [$this, 'injectScope']);
    }

    /**
     * @param SharedEventManagerInterface $manager
     */
    public function detachShared(SharedEventManagerInterface $manager)
    {
        $manager->detach('ZF\Hal\Plugin\Hal', $this->listener[0]);
        $manager->detach('ZF\Rest\RestController', $this->listener[1]);
    }

    /**
     * @param EventInterface $event
     */
    public function onRender(EventInterface $event)
    {
        // Should never be able to load a scope object
        if (!$this->getAuthenticationService()->hasIdentity()) {
            return;
        }

        $entity  = $event->getParam('entity');
        if (!$entity instanceof Entity) {
            return;
        }

        if (!$entity->entity instanceof ScopeAwareInterface) {
            return;
        }

        $role    = $this->getRole($entity);
        $payload = $event->getParam('payload');
        $payload['scope'] =$this->rbac->getScopeForEntity($role, $entity->entity->getEntityType());
    }

    /**
     * @param EventInterface $event
     */
    public function injectScope(EventInterface $event)
    {
        // Should never be able to load a scope object
        if (!$this->getAuthenticationService()->hasIdentity()) {
            return;
        }

        $entity  = $event->getParam('entity');
        if (!$entity instanceof Entity) {
            return;
        }

        if (!$entity->entity instanceof ScopeAwareInterface) {
            return;
        }

        $role    = $this->getRole($entity);
        $this->entity->setScope($this->rbac->getScopeForEntity($role, $entity->entity->getEntityType()));
    }
}

The issue with this approach is when I render collection of entities, I now need to loop through all the payloads and perform the same check that the entity is ScopeAware. It seems kinda silly to remove the caching in the EntityExtractor since that could be called multiple times on the same object, however it would be nice to be able to transform the entity before it get's extracted

Rest Controller shouldn't wrap the data in a specific replesentational format

At the moment the REST controller wraps whatever data is getting in a HalEntity (or Collection). This interferes very badly with the very concept of REST since the controller is already returning an specific representation and taking away the possibility to do content negotiation. For example:

  /**
     * Return single entity
     *
     * @todo   Remove 'resource' from get.post event for 1.0.0
     * @param  int|string $id
     * @return Response|ApiProblem|ApiProblemResponse|HalEntity
     */
    public function get($id)
    {
        $events = $this->getEventManager();
        $events->trigger('get.pre', $this, ['id' => $id]);

        try {
            $entity = $this->getResource()->fetch($id);
        } catch (Throwable $e) {
            return $this->createApiProblemFromException($e);
        } catch (Exception $e) {
            return $this->createApiProblemFromException($e);
        }

        $entity = $entity ?: new ApiProblem(404, 'Entity not found.');

        if ($this->isPreparedResponse($entity)) {
            return $entity;
        }

        // Here is the problem! What happen if I want to return another representation
        // of the same entity?
        $halEntity = $this->createHalEntity($entity);

        $events->trigger('get.post', $this, [
            'id'       => $id,
            'entity'   => $halEntity,
            'resource' => $halEntity,
        ]);

        return $halEntity;
    }

These issue is the conceptional problem description whose some side effects are e.g #98 or #89

Update zf-rest RestController to support resources with $id = 0

I have some resources with identifier $id = 0. This is currently not handled correctly by the ZF\Rest\RestController.

This is related to the getIdentifier method on line 694 in ZF\Rest\RestController which should explicitly check if($id !== null) in the if clause instead of the if($id). Right now the method returns false if $id is 0 and the controller wrongly interprets the entity with $id = 0 as a collection.

Link to ZF\Rest\RestController:getIdentifier: https://github.com/zfcampus/zf-rest/blob/master/src/RestController.php#L694

Also check related issue in zf-hal: zfcampus/zf-hal#44

BC break in 1.0.4 - caused by missing EventManager

Hi,

as posted in the Google Group, the release 1.0.4 introduced a BC break, caused by this change:
https://github.com/zfcampus/zf-rest/pull/45/files#diff-27d39cef7a1867425e9edef6ad8e0c9eL146

See https://groups.google.com/a/zend.com/forum/#!topic/apigility-users/Opau-60Z334

A fix would be to re-add the missing line:
$controller->setEventManager($events);

This is also a potential Unit-Test: checking if the Controller events are fired.

This is somewhat linked to #47 but I don't understand the problem behind it and why this one line was removed in first place, so I didn't want to sent in a simple pull-request re-adding this one missing line.

add Content-Location header to POST responses

Quote from the RFC 7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content:

For a 201 (Created) response to a state-changing method, a
Content-Location field-value that is identical to the Location
field-value indicates that this payload is a current
representation of the newly created resource.

ZF-Rest already makes specific assumptions about the response, as for example if a HAL-Entity is returned and the HAL entity has a self link, this link is added to the location header to retrieve the resource there.

According to the RFC quoted above, we should also add the Content-Location header to indicate to the clients, the response from the POST is the same response the client would get if it would follow the location header.

display_exceptions in the config - do we need it?

In my development I stoped getting exception errors. I checked the Application and verified that:

'display_exceptions' => true,

Later I discovered that zf-rest override my setting at:
https://github.com/zfcampus/zf-rest/blob/master/config/module.config.php#L53

My solution was easy, I moved Application (and my module) to the end of the stack and behaviour returned to the expected one.
My question is. Do we really need it in the zf-rest configuration?

create() logic for casting $data needs to accommodate multiple items

As reported on the mailing list, create() casts $data to an object if it is an array. This is useful to ensure that any changes a listener makes to $data will be reflected in the event for later listeners. However, it breaks one critical situation: when a user posts multiple entities at once (which will be an array of either arrays or objects).

The logic for this needs to be updated to accommodate both use cases. One possibility is to cast to an ArrayObject, but I'm unsure if that will address the "changes made in one listener will affect later listeners" use case (in the event that an item in the ArrayObject is an array, and that item is changed).

Incompatibility with ZF 2.4

This change forced me to require this dependency of this package directly in my composer.json file instead of letting Composer figure out the best version.

Looks like Composer was resolving to Zend Framework 2.4 and therefore was choosing the previous version of this package (1.0.3), which is the one with a compatibility issue with ZF 2.4. It couldn't choose version 1.0.4 because its incompatible with ZF 2.4. So kind of a catch-22, unless I'm missing something?

Like I said, the only way I could fix this is by explicitly requiring this package version 1.0.4, which made Composer uninstall ZF 2.4 and install version 2.3.7.

I didn't test this, but I suspect that a clean install of Apigility through composer will duplicate the issue I had at the time of this writing.

Getting data as array instead of stdClass

  • I was not able to find an open or closed issue matching what I'm seeing.
  • This is not a question. (Questions should be asked on chat (Signup here) or our forums.)

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

Code to reproduce the issue

Expected results

Actual results

I'm using zf-rest for building my RESTful web apps. I have the following configurations:

'zf-rest' => [
  Resource\FeedbackResource::class => [
    'listener' => Resource\FeedbackResource::class,
    'route_name' => 'api/rest/feedback',
    'entity_http_methods' => [
    ],
    'collection_http_methods' => [
      'POST',
    ],
  ],
],
'zf-content-validation' => [
  Resource\FeedbackResource::class => [
    'use_raw_data' => false,
    'allows_only_fields_in_filter' => true,
    'POST' => Resource\FeedbackResource::class . '\\Validator',
  ],
],
'input_filter_specs' => [
  Resource\FeedbackResource::class . '\\Validator' => [
    Resource\FeedbackResource::PARAM_NAME => $inputFilterSpecForStrings,
    Resource\FeedbackResource::PARAM_EMAIL => $inputFilterSpecForStrings,
  ],
],

Then I created the resource with the corresponding method:

class FeedbackResource extends AbstractResourceListener
{
  public function create($data)
  {
    // do something
  }
}

I posted a json string to the endpoint and everything works fine so far. But what I'm wondering about is that I will get $data as an object with the json data as attributes. I expected to get an assoziative array. Is this possible?

RestController for singleton resources without an identifier

I'd suggest introducing two kinds of RestControllers. One for singleton resources and one for collection resources. The RestCollectionController would be the same as the current RestController but the RestSingletonController would allow reaching resources without an identifier in the route.
For example if a user resource has only one address resource it would be accessible on:
api/v1/users/[user_id]/address. Right now I cannot do this because a GET http request without an $id in routeMatch maps to the getList method which results in returning a collection. What I want is a singleton UserAddress resource with route api/v1/users/address and the only necessary identifier is that of the User (user_id).
In the RestSingletonController we could map GET http requests without an $id in the routeMatch to the get method. The getList, deleteList, updateList and patchList methods would not exist in this RestSingletonController. By setting controller_class of type RestSingletonController or RestCollectionController it would be possible to decide for which resources we want to use which controller type.
I tested similar solution in PhlyRestfully and it worked pretty good. I can make a pull request or explain more details if there is interest in this kind of solution...
I am also available for a discussion, because it is great to be part of such a cool project.

Apigility CORS without zfr-cors - simplier is better?

OK, I get stuck with zfr-cors configuration, so I get mad and put this:

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Authorization, WWW-Authenticate, Origin, X-Requested-With, Content-Type, Accept');

in Apigility /public/index.php, above this line:

Zend\Mvc\Application::init($appConfig)->run();

So, it "seems" to me I resolved CORS issue with Apigility (confirmed by AngularJS front end developer), but I'm sure you guys have some comments on this.

Thanks.

`collection_query_whitelist` default behaviour?

Not sure if this is an issue or a question or maybe a documentation enhancement request.

I just found out that if I don't set any collection_query_whitelist in my controller config that all parameters are allowed. I somehow assumed the opposite, that if no parameter is set all parameters will be filtered from $data.

So I expected setting no whitelist would result in the same behaviour as collection_query_whitelist => array()

Is this the correct default behaviour?
If yes, may I suggest adding a comment to the controller config documentation here that omitting the parameter will result in white listing all parameters.

Misleading _links provided

Description

Assume the following configuration:

    ...
    'api\\V1\\Rest\\Status\\Controller' => array(
        ...
        'entity_http_methods' => array(),
        'collection_http_methods' => array(
            0 => 'GET',
        ),
        ...
    ),
    ...

As you can see, entity_http_methods is empty and no entity requests are allowed. This functions as expected except the response when requesting a collection yields:

...
_embedded: {
    status: [
        {
            ...
            _links: {
                self: {
                    href: "http://api/status/1/"
                }
            }
        }
    ]
}

The provided _links is misleading as entity calls are not enabled.

Modules involved:

  • zf-rest
  • zf-hal

201 status code only set if self link present

  • [X ] I was not able to find an open or closed issue matching what I'm seeing.
  • [X ] This is not a question. (Questions should be asked on chat (Signup here) or our forums.)

When creating an entity, a response status code of 201 only gets set if the entity has a self link.

Disabling self links causes POST requests to return a 200 status code, even though an entity is still created.

RestController.php

if ($halEntity->getLinks()->has('self')) {
    $plugin = $this->plugin('Hal');
    $link = $halEntity->getLinks()->get('self');
    $self = $plugin->fromLink($link);
    $url = $self['href'];
    $response = $this->getResponse();
    $response->setStatusCode(201);
    $response->getHeaders()->addHeaderLine('Location', $url);
    $response->getHeaders()->addHeaderLine('Content-Location', $url);
}

Code to reproduce the issue

disable self links on any endpoint

'force_self_link' => false,

Expected results

An entity has been created, so a status code of 201 should be returned

Actual results

A status code of 200 is returned

Content negotiation for errors (404, 405, ...)

Will be nice if the response of a global error thrown by the library respect the media type provided by Accept header.

This is when error is thrown to a browser (Accept: text/html) error is rendered like now but if it's thrown to the API client (Accept: application/*json) return application/problem+json response.

RestController::Fetch when throwable is caught createApiProblemFromException is called which takes an exception as input

RestController::Fetch when throwable is caught createApiProblemFromException is called which takes an exception as input

This is causing an uncaught type error:
Argument 1 passed to ZF\Rest\RestController::createApiProblemFromException() must be an instance of Exception, instance of Error given, called in /media/sf_NetBeansProjects/backend/vendor/zfcampus/zf-rest/src/RestController.php on line 498

Stack trace:
#0 /media/sf_NetBeansProjects/backend/vendor/zfcampus/zf-rest/src/RestController.php(498): ZF\Rest\RestController->createApiProblemFromException(Object(Error))
#1 /media/sf_NetBeansProjects/backend/vendor/zendframework/zend-mvc/src/Controller/AbstractRestfulController.php(366): ZF\Rest\RestController->get('1')
#2 /media/sf_NetBeansProjects/backend/vendor/zfcampus/zf-rest/src/RestController.php(333): Zend\Mvc\Controller\AbstractRestfulController->onDispatch(Object(Zend\Mvc\MvcEvent))
#3 [internal function]: ZF\Rest\RestController->onDispatch(Object(Zend\Mvc\MvcEvent))
#4 /media/sf_NetBeansProjects/backend/vendor/zendframework/zend-eventmanager/src/EventManager.php(490): call_user_func(Array, Object(Zend\Mvc\MvcEvent))
#5 /media/sf_NetBeansProjects/backend/vendor/zendframework/zend-eventmanager/src/EventManager.php(263): Zend\EventManager\EventManager->triggerListeners('dispatch', Object(Zend\Mvc\MvcEvent), Object(Closure))
#6 /media/sf_NetBeansProjects/backend/vendor/zendframework/zend-mvc/src/Controller/AbstractController.php(118): Zend\EventManager\EventManager->triggerEventUntil(Object(Closure), Object(Zend\Mvc\MvcEvent))
#7 /media/sf_NetBeansProjects/backend/vendor/zendframework/zend-mvc/src/Controller/AbstractRestfulController.php(300): Zend\Mvc\Controller\AbstractController->dispatch(Object(ZF\ContentNegotiation\Request), Object(Zend\Http\PhpEnvironment\Response))
#8 /media/sf_NetBeansProjects/backend/vendor/zendframework/zend-mvc/src/DispatchListener.php(118): Zend\Mvc\Controller\AbstractRestfulController->dispatch(Object(ZF\ContentNegotiation\Request), Object(Zend\Http\PhpEnvironment\Response))
#9 [internal function]: Zend\Mvc\DispatchListener->onDispatch(Object(Zend\Mvc\MvcEvent))
#10 /media/sf_NetBeansProjects/backend/vendor/zendframework/zend-eventmanager/src/EventManager.php(490): call_user_func(Array, Object(Zend\Mvc\MvcEvent))
#11 /media/sf_NetBeansProjects/backend/vendor/zendframework/zend-eventmanager/src/EventManager.php(263): Zend\EventManager\EventManager->triggerListeners('dispatch', Object(Zend\Mvc\MvcEvent), Object(Closure))
#12 /media/sf_NetBeansProjects/backend/vendor/zendframework/zend-mvc/src/Application.php(340): Zend\EventManager\EventManager->triggerEventUntil(Object(Closure), Object(Zend\Mvc\MvcEvent))
#13 /media/sf_NetBeansProjects/backend/public/index.php(24): Zend\Mvc\Application->run()
#14 {main}

As the ZfRest module support 5.6 we can't change the type hint for Error. Is it a proper fix to remove the type hint, and add some type checking within the function. Which would throw an InvalidArgumentException when an unsupported argument is detected?

Can't use my own model with content negotiation

Hi!

I got an API with content negotiation. For binaries resources, the caller don't know the mime type.
Typically, if it's a element in an HTML page, I can't specify headers.

When the resource GET method is called, the server knows its type, and I can return an ImageModel or PdfModel or whatever I need instead of HalEntity or HalCollection, but the RestController encapsulate my model into a new entity and a ContentNegotiationViewModel... :-/

Rest controller should bypass contentNegotiation if I want to use my own model.

Rest Service with relationship.

How do I create a Rest Service with relationship?
I have two services.
Is one the stores. (/store[:store_id])
And the other is of the stores menu. (/menu/:store_id[:menu_id])
I want to get filtered by store_id menu.
/menu/:store_id/[: menu_id]

has a way of doing this with apigility?

Purpose of if clause in RestController create method?

What is the purpose of the if clause on line 326 in RestController.
We have singleton resources that don't have an id available through the default methods. All works fine in our solution but we get a 200 response and the Location header won't render because the if clause in the RestController::create method is not returning true. What is the purpose of this if-clause. Is it really necessary to check inside the controller class create method whether there is an id available in the resource or can this be skipped?
Would checking with something like if($entity instanceof Entity) not be sufficient?

https://github.com/zfcampus/zf-rest/blob/master/src/RestController.php#L326

if ($entity->id) {
    $self = $entity->getLinks()->get('self');
    $self = $plugin->fromLink($self);

    $response = $this->getResponse();
    $response->setStatusCode(201);
    $response->getHeaders()->addHeaderLine('Location', $self);
}

Can't get POST data

I've a problem to create a new rest entity. I've the following configuration:

$inputFilterSpecForStrings = [
    'required' => false,
    'allow_empty' => false,
    'filters' => [
        'no_tags' => [
            'name' => Filter\StripTags::class,
        ],
        'no_newlines' => [
            'name' => Filter\StripNewlines::class,
        ],
        'trim' => [
            'name' => Filter\StringTrim::class,
        ],
    ],
    'validators' => [
        'not_empty' => [
            'name' => Validator\NotEmpty::class,
        ],
    ],
];

return [
    'router' => [
        'routes' => [
            'api' => [
                'child_routes' => [
                    'rest' => [
                        'child_routes' => [
                            'search-request' => [
                                'type' => 'segment',
                                'may_terminate' => true,
                                'options' => [
                                    'route' => '/search-request',
                                    'defaults' => [
                                    'controller' => Resource\SearchRequestResource::class,
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ],
    'service_manager' => [
        'factories' => [
            Resource\SearchRequestResource::class => Resource\SearchRequestResourceFactory::class,
        ],
    ],
    'zf-rest' => [
        Resource\SearchRequestResource::class => [
            'listener' => Resource\SearchRequestResource::class,
            'entity_http_methods' => [
            ],
            'collection_http_methods' => [
                'POST',
            ],
            'route_name' => 'api/rest/search-request',
            'route_identifier_name' => NULL,
            'collection_query_whitelist' => [
                Resource\SearchRequestResource::PARAM_FILTER_LOGIN,
                Resource\SearchRequestResource::PARAM_FILTER_AUTHOR_IDS,
            ],
        ],
    ],
    'zf-content-negotiation' => [
        'controllers' => [
            Resource\SearchRequestResource::class => 'HalJson',
        ],
    ],
    'input_filter_specs' => [
        Resource\SearchRequestResource::class . '\\SearchValidator' => [
            Resource\SearchRequestResource::PARAM_FILTER_LOGIN => $inputFilterSpecForStrings,
            Resource\SearchRequestResource::PARAM_FILTER_AUTHOR_IDS => $inputFilterSpecForStrings,
        ],
    ],
];

This is my factory:

class SearchRequestResourceFactory implements FactoryInterface {
    public function createService(ServiceLocatorInterface $serviceLocator) {
        return $this($serviceLocator, SearchRequestResource::class);
    }

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null) {
        $resource = new SearchRequestResource();

        $inputFilterManager = $container->get('InputFilterManager');
        $searchInputFilter = $inputFilterManager->get(SearchRequestResource::class . '\\SearchValidator');
        $resource->setSearchInputFilter($searchInputFilter);

        return $resource;
    }
}

And my resource:

class SearchRequestResource extends AbstractResourceListener {
    const PARAM_FILTER_LOGIN = 'filter_login';
    const PARAM_FILTER_AUTHOR_IDS = 'filter_author_ids';

    private $searchInputFilter;

    public function setSearchInputFilter(InputFilterInterface $searchInputFilter) {
        $this->searchInputFilter = $searchInputFilter;
    }

    public function getSearchInputFilter() {
        return $this->searchInputFilter;
    }

    public function create($data) {
        $searchInputFilter = $this->getSearchInputFilter();

        try {
            $searchInputFilter->setData($params);
        } catch (InvalidArgumentException $e) {
            return new ApiProblem(
                Response::STATUS_CODE_422,
                'Failed Validation',
                null,
                null,
                ['validation_messages' => $e->getMessage()]
            );
        }

        if ($searchInputFilter->isValid()) {
            $filterLogin = $searchInputFilter->getValue(self::PARAM_FILTER_LOGIN);
            $filterAuthorIds = $searchInputFilter->getValue(self::PARAM_FILTER_AUTHOR_IDS);

            $searchRequestEntity = new SearchRequestEntity();
            $searchRequestEntity->setLogin($filterLogin);
            $searchRequestEntity->setAuthorIds($filterAuthorIds);

            return $searchRequestEntity->getId();
        }

        return new ApiProblem(
            Response::STATUS_CODE_422,
            'Failed Validation',
            null,
            null,
            [
                'validation_messages' => array_merge(
                    $searchInputFilter->getMessages()
                ),
            ]
        );
    }
}

And then I try to curl some data:

curl -H "Content-Type: application/json" -d '{"filter_login":"xyz"}' -X POST http://example.com/api/rest/search-request

The error I got is:

Zend\InputFilter\BaseInputFilter::setData expects an array or Traversable argument; received NULL

HTTP method filter - custom methods ?

Hi @weierophinney,

I don´t understand these lines : https://github.com/zfcampus/zf-rest/blob/master/src/ZF/Rest/RestController.php#L349-L353.

Why an other HTTP method that ¨DELETE¨ can be delete a resource ? Why a HTTP method like ¨GET¨ can be delete a resource ?

There is the same problem in the create, update, etc methods.

The code must be follow the HTTP method rules. The collectionHttpMethods and the resourceHttpMethods properties are useless no ?

Thank you

Provide PHP 7.2 support

We need to update .travis.yml to test against PHP 7.2 (and any other previous versions if not listed). During this update we can also:

  • Remove HHVM support
  • Update PHPUnit to support 5.7, 6.0, and/or 7.0
  • Update to zend-coding-standard

Upoading Files

When posting files either with PUT or POST methods, I just can't get the data from my controller and the written files are 0 bytes.
Do you have any examples on how to do this?

The request is made with POSTMAN, with Content-type:image/jpeg and uploading the file in the Binary method.
Here's my code:

public function update($id, $data)
{
    $path = 'public/img/users/'.$id.'/';

    if (!is_dir($path))
    {
        mkdir($path);
    }

    /* Open a file for writing */
    $fp = fopen($path."test.jpg", "w");

    /* Read the data 1 KB at a time
       and write to the file */

    while ($chunk = fread($data, 1024))
        fwrite($fp, $chunk);

    /* Close the streams */
    fclose($fp);
    fclose($data);

    return array('id'=> $id);
}

Content response for DELETE request

Would like to have content responses for a DELETE requests. Currently returns 204 (No Content) status code.

public function delete($id) {
    return true;
    // Would like to return a response like below on success..
    // return array('status' => 'deleted', 'id' => $id);
}

adding response object to resource

as a developer i would like to have the controllers response object in the resource, so i can add additional cache header specific for my resource

it's a feature request

Wrong response code for creating entities without id or with id = 0

Wrong response code is returned when creating entities with id = 0 or entities without an id (composite key) because of an if clause in line 384 in the RestController:

    if ($entity->id) {
        $self = $entity->getLinks()->get('self');
        $self = $plugin->fromLink($self);
        $response = $this->getResponse();
        $response->setStatusCode(201);
        $response->getHeaders()->addHeaderLine('Location', $self);
    }

What is the purpose of this if clause? Can it perhaps be replaced by the following:

if ($entity instanceof Entity){
   ...
}

replaceList called without an array should return ApiProblem

If an object is sent to a PUT on a collection URI, the result is a 500 Internal Server Error with a stack trace. Instead it should probably be a 400. The error message is good though.

Expected error:

{
  "type": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html",
  "title": "Bad Request",
  "status": 400,
  "detail": "Data provided to replaceList must contain only arrays or objects; received \"integer\""
}

The same Event Manager used for resource and controller (REST Service)

RestContollerFactory create Controller and Resource which are using the same event manager.
There is no possibility to attach some listener for Resource, because resource event identifiers are replaced always by controller event identifiers.
Furthermore there is error in method detachShared of RestParametersListener.

I've created pull request for fixing these issues: #45

Collection Query Whitelist should be passed to ResourceEvent

ZF\Rest\Resource::prepareEvent() injects the ResourceEvent it creates with query parameters, and ZF\Rest\AbstractResourceListener then pulls those and passes them to fetchAll() during dispatch(). However, currently the query parameters are never set in the Resource class, which means that the $data argument to fetchAll() is never passed.

Problem with page_size and max_page_size

I have a problem using the page_size and max_page_size of the Zend REST controller for pagination.
I have the following config.php (snippet):

'zf-rest' => [
    Resource\DocumentResource::class => [
        'listener' => Resource\DocumentResource::class,
        ...
        'page_size' => 10,
        'min_page_size' => 1,
        'max_page_size' => 250,
        'collection_query_whitelist' => [
            'page_size',
        ],
    ],
],

If I make a REST call with

?page_size=50

everything works fine and I get a valid json. But if I make the call with

?page_size=250

(or an other value greater than 50) then I get an error:

ApiProblem (416) - Page size is out of range, maximum page size is 50

Batch POST response does not include collection name

When I send a batch POST request on an "article" resource, the response misses the collection name.

Batch POST response :

 {"_embedded" : [{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}] }

But the batch PATCH request on the same resource behaves normally.

Batch PATCH response :

{"_embedded" : { "article" : [{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}] }}

delete method 422 Unprocessable Entity

public function delete($id)
    {
        $sql = new Sql($this->adapter);
        $delete = $sql->delete('consumer_friends');
        $delete->where(array('id_consumer_friend' => $id));
        $query = $sql->prepareStatementforSqlObject($delete);
        $results = $query->execute();
        return true;
    }

resource.php

public function delete($id)
    {
        $this->mapper->delete($id);
    }

can you help me please?
thank you :)

zf-content-validation does not seem to be working as expected

I have a zend2 REST-API and want to send data via post. I have the following configuration and class resource:

$inputFilterSpecForStrings = [
    'required' => false,
    'allow_empty' => false,
    'filters' => [
        'no_tags' => [
            'name' => Filter\StripTags::class,
        ],
        'no_newlines' => [
            'name' => Filter\StripNewlines::class,
        ],
        'trim' => [
            'name' => Filter\StringTrim::class,
        ],
    ],
    'validators' => [
        'not_empty' => [
            'name' => Validator\NotEmpty::class,
        ],
    ],
];

'zf-rest' => [
    Resource\StoredSearchResource::class => [
        'listener' => Resource\StoredSearchResource::class,
        'entity_http_methods' => [
        ],
        'collection_http_methods' => [
            'POST',
        ],
        'route_name' => 'api/rest/client/stored-search',
        'route_identifier_name' => NULL,
    ],
],
'zf-content-validation' => [
    Resource\StoredSearchResource::class => [
        'use_raw_data' => false,
        'allows_only_fields_in_filter' => true,
        'POST' => 'InputFilter\StoredSearchInputFilter',
    ],
],
'input_filter_specs' => [
    'InputFilter\StoredSearchInputFilter' => [
        Resource\StoredSearchResource::PARAM_SEARCH_STRING => $inputFilterSpecForStrings,
    ],
],
...

class StoredSearchResource extends AbstractResourceListener
{
    const PARAM_SEARCH_STRING = 'search_string';

    public function create($data)
    {
        $inputFilter = $this->getInputFilter();
        $searchString = $inputFilter->getValue(self::PARAM_SEARCH_STRING);
        ...
    }
}

If I test it, then I will get a http 500 error. After debugging I found out that $inputFilter is always null. It seems like the content-validation filters out my data so it doesn't reach the resource.

collection_query_white_list broken

Since I didn't get any response for a long time, I would like to bump this issue: #73

Maybe notifications are no longer sent because the issue was closed without being resolved.

JSON patch according to RFC6902

Enhancement?
I recently ran into this blog post referring to the RFC6902 standard for JSON patch. It seems this patching standard allows some additional flexibility regarding patch operations.

As an example; with this method of patching resources you can simply remove or add a member to a collection without sending the whole collection. Simply sending the new member or the member to remove is enough.

Are there any ideas of implementing patch RFC6902 style in the future?

Make route_name optional

Since the route used to display hal links for the controller is mostly the same as the routematch it should be possible to get a default routename from the mvc event.

I think this can be done by the RestControllerFactory.

Reporting complex errors

I'm playing around with zf-rest and other modules related to Apigility (great work BTW!). I'm currently stuck when trying to report detailed errors from REST resource.
I want to use InputFilter to validate an entity, then report back exactly which fields were filled incorrectly.

Take a look at my current code:

class TaskResource extends AbstractResourceListener
{
    public function create($data)
    {
        $filter = $this->inputFilter;
        $filter->setData((array)$data);
        if (!$filter->isValid()) {
            throw new \Exception("Invalid data!");            
        }
        $entity = new Task();
        $this->hydrator->hydrate($filter->getValues(), $entity);
        $this->repository->save($entity);
    }
}

Now, instead of throwing an exception when something is wrong, I'd like to somehow return all messages from input filter. Is there any standard way to do that? Or should I just "invent" my own format, like this:

    return array(
        'success' => false,
        'errors' => $filter->getMessages();
    );

Question : zf-rest "create.post" event

Hi,

In zfcampus/zf-rest/src/RestController.php, the event is trigger with only data and hal on line 404.

$events->trigger('create.post', $this, [
            'data'     => $data,
            'entity'   => $halEntity,
            'resource' => $halEntity,
        ]);

I'm not sure on the idea behind but I think that the resource key should have the resource because halEntity which is an instance of ZF\Hal\Entity and we cannot get the resource using this class.

The resource can be found on line 370 with $this->getResource()->create($data).

I didn't inspect the whole class but I think this is not the only problem.

So good/bad behavior? Should I PR?

thx

An example

Hi,

It would be great to see a simple example of defining zf-rest, zf-hal, zf-content-negotiation and zf-api-problem using the Album example familiar from other ZF2 tutorials.

Now it's near to impossible to see how to do this e.g. by looking code from Apigility skeleton etc.

RFC: Remove IdentityInterface as type hint on ZF\Rest\Resource

Currently, the setIdentity method requires the IdentityInterface (https://github.com/zfcampus/zf-rest/blob/master/src/Resource.php#L82).

This makes a lot of assumptions around what identity means to consuming applications. It makes absolutely no sense in our use case to implement the 15 methods declared in the Zend\MvcAuth\IdentityInterface and is keeping us from using the getIdentity() method on the Resource base class.

I am suggesting that we change:

    /**
     * @param null|IdentityInterface $identity
     * @return self
     */
    public function setIdentity(IdentityInterface $identity = null)
    {
        $this->identity = $identity;
        return $this;
    }

To

    /**
     * @param null|object $identity
     * @return self
     */
    public function setIdentity($identity = null)
    {
        $this->identity = $identity;
        return $this;
    }

This way, I can set our own identity object via the MvcEvent like so

$event->setParam('ZF\MvcAuth\Identity', $authService->getIdentity());

I realize that this was likely done for some other module that performs authentication/authorization if that is the case, I strongly feel those modules should enforce the interface they need instead of the Resource object.

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.