5.1 - Docblock index templates

I’m migrating a project from Cake 4 to 5.1, and I’m getting deprecation warnings about calling isEmpty() on the paginated results.

Warning: Calling `Cake\ORM\ResultSet` methods, such as `isEmpty()`, on PaginatedResultSet is deprecated. You must call `items()` first (for example, `items()->isEmpty()`)

So I add a call to ->items() before ->isEmpty(), which makes the deprecation warning disappear, but my IDE (PHP Storm) gives me warning about potentially polymorphic call, since it doesn’t know what items() returns, except a Traversable instance.

The bake plugin only gives docblock as @var iterable<\App\Model\Entity\Product> $products.

How can I docblock the template variable, so it supports calls to isEmpty, and the entity items returns on a loop?

I’ve tried this, but it doesn’t give me the proper typehints:
@var \Cake\Datasource\Paging\PaginatedResultSet<\Cake\ORM\ResultSet<\App\Model\Entity\Product>> $products

Example code simplified:

ProductsController.php

public function index(): void
{
    $products = $this->paginate($this->Products);
    $this->set(compact('products'));
}

index.php

<?php
/**
 * @var \App\View\AppView $this
 * @var \Cake\Datasource\Paging\PaginatedResultSet<\Cake\ORM\ResultSet<\App\Model\Entity\Product>> $products
 */
?>
<?php if ($products->items()->isEmpty()): ?>
    Empty result
<?php else: ?>
    Products:
    <?php foreach ($products->items() as $products): ?>
        <?= h($product->title) ?>
    <?php endforeach ?>
<?php endif ?>

$products->items() always returns just a simple Traversable as you can see here

Technically in runtime its of course a Cake\ORM\ResultSet object, but static analysis doesn’t know that because the PaginatedInterface could be implemented in another way as well.

And that type is not a generic, therefore you can’t define it via PHPDoc like you tried.

Your simplest solution would be to force-overwrite the type like so:

/** @var \Cake\ORM\ResultSet<\App\Model\Entity\Product> $products */
$products = $products->items();

I was afraid you’d say that :sweat_smile: It’s ugly code, if you ask me. Can’t even call ->items() in the controller, 'cause then the PaginatorHelper can’t create links or give information about page count, etc.

Yea, the PaginatorHelper needs the PaginatedResultSet to function. Already discussing that within the core how this could be improved.

1 Like

Instead of if ($products->items()->isEmpty()) use if (!$products->getParam('count')) and the IDE / static analyzer won’t whine about anything.

Or if (!$products->count()).

Instead of if ($products->items()->isEmpty()) use if (!$products->getParam('count')) and the IDE / static analyzer won’t whine about anything.

Or if (!$products->count()) .

Good idea. Nice little workaround. My docblock will then be:

<?php
/**
 * @var \App\View\AppView $this
 * @var \Cake\Datasource\Paging\PaginatedResultSet|iterable<\App\Model\Entity\Product> $products
 */
?>