Open weierophinney opened 4 years ago
@kukoman is this still an issue? Maybe your paginator and not the page_size was set to 10?
Originally posted by @TomHAnderson at https://github.com/zfcampus/zf-content-negotiation/issues/24#issuecomment-66343819
yep, still the same behavior
to better explain whats going on:
// my EquipmentResource::fetchAll method
public function fetchAll($params = array())
{
$adapter = new ArrayAdapter($this->getEquipmentService()->fetchAll($params));
$collection = new EquipmentCollection($adapter);
return $collection;
}
// zf-rest config:
'Mcm\\V1\\Rest\\Equipment\\Controller' => array(
'listener' => 'Mcm\\V1\\Rest\\Equipment\\EquipmentResource',
'route_name' => 'mcm.rest.equipment',
'route_identifier_name' => 'equipment_id',
'collection_name' => 'equipment',
'entity_http_methods' => array(
0 => 'GET',
1 => 'PATCH',
2 => 'PUT',
3 => 'DELETE',
),
'collection_http_methods' => array(
0 => 'GET',
1 => 'POST',
),
'collection_query_whitelist' => array(
0 => 'orderBy',
1 => 'query',
2 => 'filter',
),
'page_size' => 100,
'page_size_param' => 100,
'entity_class' => 'Mcm\\V1\\Rest\\Equipment\\EquipmentEntity',
'collection_class' => 'Mcm\\V1\\Rest\\Equipment\\EquipmentCollection',
'service_name' => 'Equipment',
),
and still you get only 10 results
if I change it from JSON to HAL it works as expected
Originally posted by @kukoman at https://github.com/zfcampus/zf-content-negotiation/issues/24#issuecomment-66449804
You could set it in the collection class. This is because the collection extends from Paginator
class SomeCollection extends Paginator
{
protected static $defaultItemCountPerPage = 10;
}
Originally posted by @agustincl at https://github.com/zfcampus/zf-content-negotiation/issues/24#issuecomment-113256370
The root cause is because the JsonModel
does not do pagination; all it does is serialize the value presented using json_encode()
. When presented with any iterator, json_encode()
returns an object, with the keys being the indexes, and the value at that index of the iterator. This is true with paginators as well.
The page_size
and page_size_param
values are only used by zf-hal. As such, you will need to update your controller to inject the page and page size prior to returning the paginator:
$collection->setCurrentPageNumber($request->getQuery('page', 1));
$collection->setItemCountPerPage(25); // you might want to inject this value from configuration
We may add pagination support to zf-content-negotiation's JsonModel
in the future, but for now, the above is how to handle it.
Originally posted by @weierophinney at https://github.com/zfcampus/zf-content-negotiation/issues/24#issuecomment-231189750
For those who still need pagination in JsonModel I 've realized a view strategy that follows the principles of the HalJson view strategy. The classes shown here are probably not the best way how to archive pagination attributes in the JsonModel / json content negotiation, but they work pretty well.
First of all, a view strategy that only takes effect when it comes to our own view renderer, that will handle the pagination attributes.
<?php
declare(strict_types=1);
namespace Application\View\Strategy;
use Application\View\Renderer\JsonRenderer;
use Laminas\ApiTools\ApiProblem\View\ApiProblemModel;
use Laminas\ApiTools\ContentNegotiation\JsonModel;
use Laminas\View\Strategy\JsonStrategy as LaminasJsonStrategy;
use Laminas\View\ViewEvent;
class JsonStrategy extends LaminasJsonStrategy
{
public function __construct(JsonRenderer $renderer)
{
$this->renderer = $renderer;
}
public function selectRenderer(ViewEvent $event)
{
$model = $event->getModel();
if (!$model instanceof JsonModel) {
return;
}
$this->renderer->setViewEvent($event);
return $this->renderer;
}
public function injectResponse(ViewEvent $event)
{
$renderer = $event->getRenderer();
if ($renderer !== $this->renderer) {
return;
}
$result = $event->getResult();
if (!is_string($result)) {
return;
}
$model = $event->getModel();
$response = $event->getResponse();
$response->setContent($result);
$headers = $response->getHeaders();
$headers->addHeaderLine(
'content-type',
$model instanceof ApiProblemModel ? 'application/problem+json' : 'application/json'
);
}
}
The factory for the JsonStrategy
class.
<?php
declare(strict_types=1);
namespace Application\View\Strategy\Factory;
use Application\View\Renderer\JsonRenderer;
use Application\View\Strategy\JsonStrategy;
use Psr\Container\ContainerInterface;
class JsonStrategyFactory
{
public function __invoke(ContainerInterface $container): JsonStrategy
{
$renderer = $container->get(JsonRenderer::class);
return new JsonStrategy($renderer);
}
}
As you might have seen we need a renderer instance, that handles the pagination attributes. The renderer does basicly the same as the HalJsonRenderer
. It 's pretty basic, because wie do not need all that hal link stuff.
<?php
declare(strict_types=1);
namespace Application\View\Renderer;
use Application\View\Helper\JsonViewHelper;
use Laminas\ApiTools\ApiProblem\ApiProblem;
use Laminas\ApiTools\ApiProblem\View\ApiProblemModel;
use Laminas\ApiTools\ApiProblem\View\ApiProblemRenderer;
use Laminas\ApiTools\ContentNegotiation\JsonModel;
use Laminas\ApiTools\Hal\Collection;
use Laminas\View\HelperPluginManager;
use Laminas\View\Renderer\JsonRenderer as LaminasJsonRenderer;
use Laminas\View\ViewEvent;
class JsonRenderer extends LaminasJsonRenderer
{
protected ApiProblemRenderer $apiProblemRenderer;
protected HelperPluginManager $helpers;
protected ViewEvent $viewEvent;
public function __construct(ApiProblemRenderer $apiProblemRenderer)
{
$this->apiProblemRenderer = $apiProblemRenderer;
}
public function getHelperPluginManager(): HelperPluginManager
{
if (!$this->helpers instanceof HelperPluginManager) {
$this->setHelperPluginManager(new HelperPluginManager());
}
return $this->helpers;
}
public function setHelperPluginManager(HelperPluginManager $helpers): void
{
$this->helpers = $helpers;
}
public function getViewEvent(): ViewEvent
{
return $this->viewEvent;
}
public function setViewEvent(ViewEvent $event): void
{
$this->viewEvent = $event;
}
public function render($nameOrModel, $values = null)
{
if (!$nameOrModel instanceof JsonModel) {
return parent::render($nameOrModel, $values);
}
$payload = $nameOrModel->getVariable('payload');
if ($payload instanceof Collection) {
$helper = $this->getHelperPluginManager()->get(JsonViewHelper::class);
$payload = $helper->renderCollection($payload);
if ($payload instanceof ApiProblem) {
$this->renderApiProblem($payload);
}
return parent::render($payload);
}
return parent::render($nameOrModel, $values);
}
protected function renderApiProblem(ApiProblem $problem): string
{
$model = new ApiProblemModel($problem);
$event = $this->getViewEvent();
if ($event) {
$event->setModel($model);
}
return $this->apiProblemRenderer->render($model);
}
}
As you can see the renderer has a dependency to a view helper and the ApiProblemRenderer
class. Therefore we need another factory that creates a renderer instance.
<?php
declare(strict_types=1);
namespace Application\View\Renderer\Factory;
use Application\View\Renderer\JsonRenderer;
use Laminas\ApiTools\ApiProblem\View\ApiProblemRenderer;
use Psr\Container\ContainerInterface;
class JsonRendererFactory
{
public function __invoke(ContainerInterface $container): JsonRenderer
{
$helpers = $container->get('ViewHelperManager');
$apiProblemRenderer = $container->get(ApiProblemRenderer::class);
$renderer = new JsonRenderer($apiProblemRenderer);
$renderer->setHelperPluginManager($helpers);
return $renderer;
}
}
The renderer class uses a view helper, that renders a collection to a json string. Basicly this view helper does all the basic things, that the HAL view helper does without wiring links and handle single entites. This view helper class is just for rendering collections as JSON string. Beside that the attributes that we know from HAL are set. Theoretically, one could do without the event manager. However, since I need it in my application, it is included here.
<?php
declare(strict_types=1);
namespace Application\View\Helper;
use ArrayObject;
use Countable;
use Laminas\ApiTools\ApiProblem\ApiProblem;
use Laminas\ApiTools\Hal\Collection;
use Laminas\EventManager\EventManagerAwareInterface;
use Laminas\EventManager\EventManagerAwareTrait;
use Laminas\Mvc\Controller\Plugin\PluginInterface;
use Laminas\Paginator\Paginator;
use Laminas\Stdlib\DispatchableInterface;
use Laminas\View\Helper\AbstractHelper;
class JsonViewHelper extends AbstractHelper implements PluginInterface, EventManagerAwareInterface
{
use EventManagerAwareTrait;
protected DispatchableInterface $controller;
public function getController()
{
return $this->controller;
}
public function setController(DispatchableInterface $controller)
{
$this->controller = $controller;
}
public function renderCollection(Collection $halCollection): array
{
$this->getEventManager()->trigger(
__FUNCTION__ . '.pre',
$this,
[ 'collection' => $halCollection ]
);
$payload = $halCollection->getAttributes();
$collection = $halCollection->getCollection();
$payload[$halCollection->getCollectionName()] = $this->extractCollection($halCollection);
if ($collection instanceof Paginator) {
$payload['page_count'] = $payload['page_count'] ?? $collection->count();
$payload['total_items'] = $payload['total_items'] ?? $collection->getTotalItemCount();
$payload['page_size'] = $payload['page_size'] ?? $halCollection->getPageSize();
$payload['page'] = $payload['page_count'] > 0 ? $halCollection->getPage() : 0;
} elseif (is_array($collection) || $collection instanceof Countable) {
$payload['total_items'] = $payload['total_items'] ?? count($collection);
}
$payload = new ArrayObject($payload);
$this->getEventManager()->trigger(
__FUNCTION__ . '.post',
$this,
[ 'payload' => $payload, 'collection' => $halCollection ]
);
return $payload->getArrayCopy();
}
protected function extractCollection(Collection $halCollection): array
{
$collection = [];
$eventManager = $this->getEventManager();
foreach ($halCollection->getCollection() as $entity) {
$eventParams = new ArrayObject([
'collection' => $halCollection,
'entity' => $entity,
'resource' => $entity,
]);
$eventManager->trigger('renderCollection.entity', $this, $eventParams);
$collection[] = $entity;
}
return $collection;
}
}
All dependencies are done in the factory. Factories FTW!
<?php
declare(strict_types=1);
namespace Application\View\Helper\Factory;
use Application\View\Helper\JsonViewHelper;
use Psr\Container\ContainerInterface;
class JsonViewHelperFactory
{
public function __invoke(ContainerInterface $container): JsonViewHelper
{
$helper = new JsonViewHelper();
if ($container->has('EventManager')) {
$helper->setEventManager($container->get('EventManager'));
}
return $helper;
}
}
As @froschdesign said, the view strategy does not have to be hooked into the module class. It is sufficient to make the view strategy accessible in the configuration as we check for the right JsonModel
class in the strategy itself.
'view_manager' => [
'strategies' => [
JsonStrategy::class,
],
],
That 's all.
@ezkimo
Since we deal with events we have to plug in the above shown in the
Module
class.
Register your strategy via the configuration and the module extension is not needed:
'view_manager' => [
'strategies' => [
MyViewStrategy::class,
],
],
@froschdesign
This is not possible, as long as the view strategy is only applicable to the JsonModel
class of the content-negotiation module. If the strategy were not bound to the JsonModule
class, the way via the configuration would of course be the preferred way. Or am I wrong?
@ezkimo
This is not possible, as long as the view strategy is only applicable to the
JsonModel
class of the content-negotiation module.
See at your own strategy:
use Laminas\ApiTools\ContentNegotiation\JsonModel;
// …
public function selectRenderer(ViewEvent $event)
{
$model = $event->getModel();
if (!$model instanceof JsonModel) {
return;
}
$this->renderer->setViewEvent($event);
return $this->renderer;
}
@froschdesign Dang! Friday! It was a long week. You 're absolutely right. I 'll edit the comment. Thanks for advice.
Hi; if i use "Content Negotiation Selector": Json; the paginator is not working correctly and i always get 10 rows in collection, if i change it to HalJson it works as expecting
example:
Originally posted by @kukoman at https://github.com/zfcampus/zf-content-negotiation/issues/24