]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Add strict types for bookmarks management 1583/head
authorArthurHoaro <arthur@hoa.ro>
Fri, 2 Oct 2020 15:50:59 +0000 (17:50 +0200)
committerArthurHoaro <arthur@hoa.ro>
Tue, 13 Oct 2020 11:50:11 +0000 (13:50 +0200)
Parameters typing and using strict types overall increase the codebase
quality by enforcing the a given parameter will have the expected type.

It also removes the need to unnecessary unit tests checking methods
behavior with invalid input.

19 files changed:
application/api/ApiUtils.php
application/api/controllers/Links.php
application/bookmark/Bookmark.php
application/bookmark/BookmarkArray.php
application/bookmark/BookmarkFileService.php
application/bookmark/BookmarkFilter.php
application/bookmark/BookmarkIO.php
application/bookmark/BookmarkInitializer.php
application/bookmark/BookmarkServiceInterface.php
application/feed/FeedBuilder.php
application/front/controller/admin/ThumbnailsController.php
application/security/LoginManager.php
tests/HistoryTest.php
tests/bookmark/BookmarkArrayTest.php
tests/bookmark/BookmarkFileServiceTest.php
tests/bookmark/BookmarkTest.php
tests/front/controller/admin/ManageShaareControllerTest/DeleteBookmarkTest.php
tests/front/controller/admin/ManageShaareControllerTest/SaveBookmarkTest.php
tests/front/controller/admin/ThumbnailsControllerTest.php

index 4a6326f0f7dccc38651f6b8daf979b896cbc1c36..eb1ca9bc2b6230e944350759c33386acced2770f 100644 (file)
@@ -89,12 +89,12 @@ class ApiUtils
      * If no URL is provided, it will generate a local note URL.
      * If no title is provided, it will use the URL as title.
      *
-     * @param array  $input          Request Link.
-     * @param bool   $defaultPrivate Request Link.
+     * @param array|null  $input          Request Link.
+     * @param bool        $defaultPrivate Setting defined if a bookmark is private by default.
      *
      * @return Bookmark instance.
      */
-    public static function buildBookmarkFromRequest($input, $defaultPrivate): Bookmark
+    public static function buildBookmarkFromRequest(?array $input, bool $defaultPrivate): Bookmark
     {
         $bookmark = new Bookmark();
         $url = ! empty($input['url']) ? cleanup_url($input['url']) : '';
index 778097fdf9ac73e3f0f367afd18186ee6e705505..73a1b84e1727e1a4567e53ec9db6fe1299ce2b39 100644 (file)
@@ -96,11 +96,12 @@ class Links extends ApiController
      */
     public function getLink($request, $response, $args)
     {
-        if (!$this->bookmarkService->exists($args['id'])) {
+        $id = is_integer_mixed($args['id']) ? (int) $args['id'] : null;
+        if ($id === null || ! $this->bookmarkService->exists($id)) {
             throw new ApiLinkNotFoundException();
         }
         $index = index_url($this->ci['environment']);
-        $out = ApiUtils::formatLink($this->bookmarkService->get($args['id']), $index);
+        $out = ApiUtils::formatLink($this->bookmarkService->get($id), $index);
 
         return $response->withJson($out, 200, $this->jsonStyle);
     }
@@ -115,7 +116,7 @@ class Links extends ApiController
      */
     public function postLink($request, $response)
     {
-        $data = $request->getParsedBody();
+        $data = (array) ($request->getParsedBody() ?? []);
         $bookmark = ApiUtils::buildBookmarkFromRequest($data, $this->conf->get('privacy.default_private_links'));
         // duplicate by URL, return 409 Conflict
         if (! empty($bookmark->getUrl())
@@ -148,7 +149,8 @@ class Links extends ApiController
      */
     public function putLink($request, $response, $args)
     {
-        if (! $this->bookmarkService->exists($args['id'])) {
+        $id = is_integer_mixed($args['id']) ? (int) $args['id'] : null;
+        if ($id === null || !$this->bookmarkService->exists($id)) {
             throw new ApiLinkNotFoundException();
         }
 
@@ -159,7 +161,7 @@ class Links extends ApiController
         // duplicate URL on a different link, return 409 Conflict
         if (! empty($requestBookmark->getUrl())
             && ! empty($dup = $this->bookmarkService->findByUrl($requestBookmark->getUrl()))
-            && $dup->getId() != $args['id']
+            && $dup->getId() != $id
         ) {
             return $response->withJson(
                 ApiUtils::formatLink($dup, $index),
@@ -168,7 +170,7 @@ class Links extends ApiController
             );
         }
 
-        $responseBookmark = $this->bookmarkService->get($args['id']);
+        $responseBookmark = $this->bookmarkService->get($id);
         $responseBookmark = ApiUtils::updateLink($responseBookmark, $requestBookmark);
         $this->bookmarkService->set($responseBookmark);
 
@@ -189,10 +191,11 @@ class Links extends ApiController
      */
     public function deleteLink($request, $response, $args)
     {
-        if (! $this->bookmarkService->exists($args['id'])) {
+        $id = is_integer_mixed($args['id']) ? (int) $args['id'] : null;
+        if ($id === null || !$this->bookmarkService->exists($id)) {
             throw new ApiLinkNotFoundException();
         }
-        $bookmark = $this->bookmarkService->get($args['id']);
+        $bookmark = $this->bookmarkService->get($id);
         $this->bookmarkService->remove($bookmark);
 
         return $response->withStatus(204);
index 1beb8be2e127a2b0b905e71e256b0279f8018598..fa45d2fc04282a40e061dccfc4eaedbcdead75a1 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Shaarli\Bookmark;
 
 use DateTime;
@@ -59,25 +61,25 @@ class Bookmark
      *
      * @return $this
      */
-    public function fromArray($data)
+    public function fromArray(array $data): Bookmark
     {
-        $this->id = $data['id'];
-        $this->shortUrl = $data['shorturl'];
-        $this->url = $data['url'];
-        $this->title = $data['title'];
-        $this->description = $data['description'];
-        $this->thumbnail = isset($data['thumbnail']) ? $data['thumbnail'] : null;
-        $this->sticky = isset($data['sticky']) ? $data['sticky'] : false;
-        $this->created = $data['created'];
+        $this->id = $data['id'] ?? null;
+        $this->shortUrl = $data['shorturl'] ?? null;
+        $this->url = $data['url'] ?? null;
+        $this->title = $data['title'] ?? null;
+        $this->description = $data['description'] ?? null;
+        $this->thumbnail = $data['thumbnail'] ?? null;
+        $this->sticky = $data['sticky'] ?? false;
+        $this->created = $data['created'] ?? null;
         if (is_array($data['tags'])) {
             $this->tags = $data['tags'];
         } else {
-            $this->tags = preg_split('/\s+/', $data['tags'], -1, PREG_SPLIT_NO_EMPTY);
+            $this->tags = preg_split('/\s+/', $data['tags'] ?? '', -1, PREG_SPLIT_NO_EMPTY);
         }
         if (! empty($data['updated'])) {
             $this->updated = $data['updated'];
         }
-        $this->private = $data['private'] ? true : false;
+        $this->private = ($data['private'] ?? false) ? true : false;
 
         return $this;
     }
@@ -95,13 +97,12 @@ class Bookmark
      *
      * @throws InvalidBookmarkException
      */
-    public function validate()
+    public function validate(): void
     {
         if ($this->id === null
             || ! is_int($this->id)
             || empty($this->shortUrl)
             || empty($this->created)
-            || ! $this->created instanceof DateTimeInterface
         ) {
             throw new InvalidBookmarkException($this);
         }
@@ -119,11 +120,11 @@ class Bookmark
      *   - created: with the current datetime
      *   - shortUrl: with a generated small hash from the date and the given ID
      *
-     * @param int $id
+     * @param int|null $id
      *
      * @return Bookmark
      */
-    public function setId($id)
+    public function setId(?int $id): Bookmark
     {
         $this->id = $id;
         if (empty($this->created)) {
@@ -139,9 +140,9 @@ class Bookmark
     /**
      * Get the Id.
      *
-     * @return int
+     * @return int|null
      */
-    public function getId()
+    public function getId(): ?int
     {
         return $this->id;
     }
@@ -149,9 +150,9 @@ class Bookmark
     /**
      * Get the ShortUrl.
      *
-     * @return string
+     * @return string|null
      */
-    public function getShortUrl()
+    public function getShortUrl(): ?string
     {
         return $this->shortUrl;
     }
@@ -159,9 +160,9 @@ class Bookmark
     /**
      * Get the Url.
      *
-     * @return string
+     * @return string|null
      */
-    public function getUrl()
+    public function getUrl(): ?string
     {
         return $this->url;
     }
@@ -171,7 +172,7 @@ class Bookmark
      *
      * @return string
      */
-    public function getTitle()
+    public function getTitle(): ?string
     {
         return $this->title;
     }
@@ -181,7 +182,7 @@ class Bookmark
      *
      * @return string
      */
-    public function getDescription()
+    public function getDescription(): string
     {
         return ! empty($this->description) ? $this->description : '';
     }
@@ -191,7 +192,7 @@ class Bookmark
      *
      * @return DateTimeInterface
      */
-    public function getCreated()
+    public function getCreated(): ?DateTimeInterface
     {
         return $this->created;
     }
@@ -201,7 +202,7 @@ class Bookmark
      *
      * @return DateTimeInterface
      */
-    public function getUpdated()
+    public function getUpdated(): ?DateTimeInterface
     {
         return $this->updated;
     }
@@ -209,11 +210,11 @@ class Bookmark
     /**
      * Set the ShortUrl.
      *
-     * @param string $shortUrl
+     * @param string|null $shortUrl
      *
      * @return Bookmark
      */
-    public function setShortUrl($shortUrl)
+    public function setShortUrl(?string $shortUrl): Bookmark
     {
         $this->shortUrl = $shortUrl;
 
@@ -223,14 +224,14 @@ class Bookmark
     /**
      * Set the Url.
      *
-     * @param string $url
-     * @param array  $allowedProtocols
+     * @param string|null $url
+     * @param string[]    $allowedProtocols
      *
      * @return Bookmark
      */
-    public function setUrl($url, $allowedProtocols = [])
+    public function setUrl(?string $url, array $allowedProtocols = []): Bookmark
     {
-        $url = trim($url);
+        $url = $url !== null ? trim($url) : '';
         if (! empty($url)) {
             $url = whitelist_protocols($url, $allowedProtocols);
         }
@@ -242,13 +243,13 @@ class Bookmark
     /**
      * Set the Title.
      *
-     * @param string $title
+     * @param string|null $title
      *
      * @return Bookmark
      */
-    public function setTitle($title)
+    public function setTitle(?string $title): Bookmark
     {
-        $this->title = trim($title);
+        $this->title = $title !== null ? trim($title) : '';
 
         return $this;
     }
@@ -256,11 +257,11 @@ class Bookmark
     /**
      * Set the Description.
      *
-     * @param string $description
+     * @param string|null $description
      *
      * @return Bookmark
      */
-    public function setDescription($description)
+    public function setDescription(?string $description): Bookmark
     {
         $this->description = $description;
 
@@ -271,11 +272,11 @@ class Bookmark
      * Set the Created.
      * Note: you shouldn't set this manually except for special cases (like bookmark import)
      *
-     * @param DateTimeInterface $created
+     * @param DateTimeInterface|null $created
      *
      * @return Bookmark
      */
-    public function setCreated($created)
+    public function setCreated(?DateTimeInterface $created): Bookmark
     {
         $this->created = $created;
 
@@ -285,11 +286,11 @@ class Bookmark
     /**
      * Set the Updated.
      *
-     * @param DateTimeInterface $updated
+     * @param DateTimeInterface|null $updated
      *
      * @return Bookmark
      */
-    public function setUpdated($updated)
+    public function setUpdated(?DateTimeInterface $updated): Bookmark
     {
         $this->updated = $updated;
 
@@ -301,7 +302,7 @@ class Bookmark
      *
      * @return bool
      */
-    public function isPrivate()
+    public function isPrivate(): bool
     {
         return $this->private ? true : false;
     }
@@ -309,11 +310,11 @@ class Bookmark
     /**
      * Set the Private.
      *
-     * @param bool $private
+     * @param bool|null $private
      *
      * @return Bookmark
      */
-    public function setPrivate($private)
+    public function setPrivate(?bool $private): Bookmark
     {
         $this->private = $private ? true : false;
 
@@ -323,9 +324,9 @@ class Bookmark
     /**
      * Get the Tags.
      *
-     * @return array
+     * @return string[]
      */
-    public function getTags()
+    public function getTags(): array
     {
         return is_array($this->tags) ? $this->tags : [];
     }
@@ -333,13 +334,13 @@ class Bookmark
     /**
      * Set the Tags.
      *
-     * @param array $tags
+     * @param string[]|null $tags
      *
      * @return Bookmark
      */
-    public function setTags($tags)
+    public function setTags(?array $tags): Bookmark
     {
-        $this->setTagsString(implode(' ', $tags));
+        $this->setTagsString(implode(' ', $tags ?? []));
 
         return $this;
     }
@@ -357,11 +358,11 @@ class Bookmark
     /**
      * Set the Thumbnail.
      *
-     * @param string|bool $thumbnail Thumbnail's URL - false if no thumbnail could be found
+     * @param string|bool|null $thumbnail Thumbnail's URL - false if no thumbnail could be found
      *
      * @return Bookmark
      */
-    public function setThumbnail($thumbnail)
+    public function setThumbnail($thumbnail): Bookmark
     {
         $this->thumbnail = $thumbnail;
 
@@ -373,7 +374,7 @@ class Bookmark
      *
      * @return bool
      */
-    public function isSticky()
+    public function isSticky(): bool
     {
         return $this->sticky ? true : false;
     }
@@ -381,11 +382,11 @@ class Bookmark
     /**
      * Set the Sticky.
      *
-     * @param bool $sticky
+     * @param bool|null $sticky
      *
      * @return Bookmark
      */
-    public function setSticky($sticky)
+    public function setSticky(?bool $sticky): Bookmark
     {
         $this->sticky = $sticky ? true : false;
 
@@ -395,7 +396,7 @@ class Bookmark
     /**
      * @return string Bookmark's tags as a string, separated by a space
      */
-    public function getTagsString()
+    public function getTagsString(): string
     {
         return implode(' ', $this->getTags());
     }
@@ -403,7 +404,7 @@ class Bookmark
     /**
      * @return bool
      */
-    public function isNote()
+    public function isNote(): bool
     {
         // We check empty value to get a valid result if the link has not been saved yet
         return empty($this->url) || startsWith($this->url, '/shaare/') || $this->url[0] === '?';
@@ -416,14 +417,14 @@ class Bookmark
      *   - multiple spaces will be removed
      *   - trailing dash in tags will be removed
      *
-     * @param string $tags
+     * @param string|null $tags
      *
      * @return $this
      */
-    public function setTagsString($tags)
+    public function setTagsString(?string $tags): Bookmark
     {
         // Remove first '-' char in tags.
-        $tags = preg_replace('/(^| )\-/', '$1', $tags);
+        $tags = preg_replace('/(^| )\-/', '$1', $tags ?? '');
         // Explode all tags separted by spaces or commas
         $tags = preg_split('/[\s,]+/', $tags);
         // Remove eventual empty values
@@ -440,7 +441,7 @@ class Bookmark
      * @param string $fromTag
      * @param string $toTag
      */
-    public function renameTag($fromTag, $toTag)
+    public function renameTag(string $fromTag, string $toTag): void
     {
         if (($pos = array_search($fromTag, $this->tags)) !== false) {
             $this->tags[$pos] = trim($toTag);
@@ -452,7 +453,7 @@ class Bookmark
      *
      * @param string $tag
      */
-    public function deleteTag($tag)
+    public function deleteTag(string $tag): void
     {
         if (($pos = array_search($tag, $this->tags)) !== false) {
             unset($this->tags[$pos]);
index 3bd5eb20f64bebe26d6306ba3590190eb90ec55b..67bb3b73d55fbcca2f68c3a651f76fb873f140a3 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Shaarli\Bookmark;
 
 use Shaarli\Bookmark\Exception\InvalidBookmarkException;
@@ -187,13 +189,13 @@ class BookmarkArray implements \Iterator, \Countable, \ArrayAccess
     /**
      * Returns a bookmark offset in bookmarks array from its unique ID.
      *
-     * @param int $id Persistent ID of a bookmark.
+     * @param int|null $id Persistent ID of a bookmark.
      *
      * @return int Real offset in local array, or null if doesn't exist.
      */
-    protected function getBookmarkOffset($id)
+    protected function getBookmarkOffset(?int $id): ?int
     {
-        if (isset($this->ids[$id])) {
+        if ($id !== null && isset($this->ids[$id])) {
             return $this->ids[$id];
         }
         return null;
@@ -205,7 +207,7 @@ class BookmarkArray implements \Iterator, \Countable, \ArrayAccess
      *
      * @return int next ID.
      */
-    public function getNextId()
+    public function getNextId(): int
     {
         if (!empty($this->ids)) {
             return max(array_keys($this->ids)) + 1;
@@ -214,11 +216,11 @@ class BookmarkArray implements \Iterator, \Countable, \ArrayAccess
     }
 
     /**
-     * @param $url
+     * @param string $url
      *
      * @return Bookmark|null
      */
-    public function getByUrl($url)
+    public function getByUrl(string $url): ?Bookmark
     {
         if (! empty($url)
             && isset($this->urls[$url])
index 1ba0071277a26684808ae337716231d68fd138b9..804b25207a95bf1c698682bf49f009a58b35ea46 100644 (file)
@@ -1,9 +1,10 @@
 <?php
 
+declare(strict_types=1);
 
 namespace Shaarli\Bookmark;
 
-
+use DateTime;
 use Exception;
 use malkusch\lock\mutex\Mutex;
 use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
@@ -54,7 +55,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function __construct(ConfigManager $conf, History $history, Mutex $mutex, $isLoggedIn)
+    public function __construct(ConfigManager $conf, History $history, Mutex $mutex, bool $isLoggedIn)
     {
         $this->conf = $conf;
         $this->history = $history;
@@ -96,7 +97,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function findByHash($hash)
+    public function findByHash(string $hash): Bookmark
     {
         $bookmark = $this->bookmarkFilter->filter(BookmarkFilter::$FILTER_HASH, $hash);
         // PHP 7.3 introduced array_key_first() to avoid this hack
@@ -111,7 +112,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function findByUrl($url)
+    public function findByUrl(string $url): ?Bookmark
     {
         return $this->bookmarks->getByUrl($url);
     }
@@ -120,10 +121,10 @@ class BookmarkFileService implements BookmarkServiceInterface
      * @inheritDoc
      */
     public function search(
-        $request = [],
-        $visibility = null,
-        $caseSensitive = false,
-        $untaggedOnly = false,
+        array $request = [],
+        string $visibility = null,
+        bool $caseSensitive = false,
+        bool $untaggedOnly = false,
         bool $ignoreSticky = false
     ) {
         if ($visibility === null) {
@@ -131,8 +132,8 @@ class BookmarkFileService implements BookmarkServiceInterface
         }
 
         // Filter bookmark database according to parameters.
-        $searchtags = isset($request['searchtags']) ? $request['searchtags'] : '';
-        $searchterm = isset($request['searchterm']) ? $request['searchterm'] : '';
+        $searchTags = isset($request['searchtags']) ? $request['searchtags'] : '';
+        $searchTerm = isset($request['searchterm']) ? $request['searchterm'] : '';
 
         if ($ignoreSticky) {
             $this->bookmarks->reorder('DESC', true);
@@ -140,7 +141,7 @@ class BookmarkFileService implements BookmarkServiceInterface
 
         return $this->bookmarkFilter->filter(
             BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
-            [$searchtags, $searchterm],
+            [$searchTags, $searchTerm],
             $caseSensitive,
             $visibility,
             $untaggedOnly
@@ -150,7 +151,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function get($id, $visibility = null)
+    public function get(int $id, string $visibility = null): Bookmark
     {
         if (! isset($this->bookmarks[$id])) {
             throw new BookmarkNotFoundException();
@@ -173,20 +174,17 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function set($bookmark, $save = true)
+    public function set(Bookmark $bookmark, bool $save = true): Bookmark
     {
         if (true !== $this->isLoggedIn) {
             throw new Exception(t('You\'re not authorized to alter the datastore'));
         }
-        if (! $bookmark instanceof Bookmark) {
-            throw new Exception(t('Provided data is invalid'));
-        }
         if (! isset($this->bookmarks[$bookmark->getId()])) {
             throw new BookmarkNotFoundException();
         }
         $bookmark->validate();
 
-        $bookmark->setUpdated(new \DateTime());
+        $bookmark->setUpdated(new DateTime());
         $this->bookmarks[$bookmark->getId()] = $bookmark;
         if ($save === true) {
             $this->save();
@@ -198,15 +196,12 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function add($bookmark, $save = true)
+    public function add(Bookmark $bookmark, bool $save = true): Bookmark
     {
         if (true !== $this->isLoggedIn) {
             throw new Exception(t('You\'re not authorized to alter the datastore'));
         }
-        if (! $bookmark instanceof Bookmark) {
-            throw new Exception(t('Provided data is invalid'));
-        }
-        if (! empty($bookmark->getId())) {
+        if (!empty($bookmark->getId())) {
             throw new Exception(t('This bookmarks already exists'));
         }
         $bookmark->setId($this->bookmarks->getNextId());
@@ -223,14 +218,11 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function addOrSet($bookmark, $save = true)
+    public function addOrSet(Bookmark $bookmark, bool $save = true): Bookmark
     {
         if (true !== $this->isLoggedIn) {
             throw new Exception(t('You\'re not authorized to alter the datastore'));
         }
-        if (! $bookmark instanceof Bookmark) {
-            throw new Exception('Provided data is invalid');
-        }
         if ($bookmark->getId() === null) {
             return $this->add($bookmark, $save);
         }
@@ -240,14 +232,11 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function remove($bookmark, $save = true)
+    public function remove(Bookmark $bookmark, bool $save = true): void
     {
         if (true !== $this->isLoggedIn) {
             throw new Exception(t('You\'re not authorized to alter the datastore'));
         }
-        if (! $bookmark instanceof Bookmark) {
-            throw new Exception(t('Provided data is invalid'));
-        }
         if (! isset($this->bookmarks[$bookmark->getId()])) {
             throw new BookmarkNotFoundException();
         }
@@ -262,7 +251,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function exists($id, $visibility = null)
+    public function exists(int $id, string $visibility = null): bool
     {
         if (! isset($this->bookmarks[$id])) {
             return false;
@@ -285,7 +274,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function count($visibility = null)
+    public function count(string $visibility = null): int
     {
         return count($this->search([], $visibility));
     }
@@ -293,7 +282,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function save()
+    public function save(): void
     {
         if (true !== $this->isLoggedIn) {
             // TODO: raise an Exception instead
@@ -308,7 +297,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function bookmarksCountPerTag($filteringTags = [], $visibility = null)
+    public function bookmarksCountPerTag(array $filteringTags = [], string $visibility = null): array
     {
         $bookmarks = $this->search(['searchtags' => $filteringTags], $visibility);
         $tags = [];
@@ -344,13 +333,14 @@ class BookmarkFileService implements BookmarkServiceInterface
         $keys = array_keys($tags);
         $tmpTags = array_combine($keys, $keys);
         array_multisort($tags, SORT_DESC, $tmpTags, SORT_ASC, $tags);
+
         return $tags;
     }
 
     /**
      * @inheritDoc
      */
-    public function days()
+    public function days(): array
     {
         $bookmarkDays = [];
         foreach ($this->search() as $bookmark) {
@@ -365,7 +355,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function filterDay($request)
+    public function filterDay(string $request)
     {
         $visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC;
 
@@ -375,7 +365,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * @inheritDoc
      */
-    public function initialize()
+    public function initialize(): void
     {
         $initializer = new BookmarkInitializer($this);
         $initializer->initialize();
@@ -388,7 +378,7 @@ class BookmarkFileService implements BookmarkServiceInterface
     /**
      * Handles migration to the new database format (BookmarksArray).
      */
-    protected function migrate()
+    protected function migrate(): void
     {
         $bookmarkDb = new LegacyLinkDB(
             $this->conf->get('resource.datastore'),
index 6636bbfeec63e6e759154eda8bde4c377337d26c..4232f11471148758f83fcd4f148c0b8cefcf9434 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Shaarli\Bookmark;
 
 use Exception;
@@ -77,8 +79,13 @@ class BookmarkFilter
      *
      * @throws BookmarkNotFoundException
      */
-    public function filter($type, $request, $casesensitive = false, $visibility = 'all', $untaggedonly = false)
-    {
+    public function filter(
+        string $type,
+        $request,
+        bool $casesensitive = false,
+        string $visibility = 'all',
+        bool $untaggedonly = false
+    ) {
         if (!in_array($visibility, ['all', 'public', 'private'])) {
             $visibility = 'all';
         }
@@ -128,7 +135,7 @@ class BookmarkFilter
      *
      * @return Bookmark[] filtered bookmarks.
      */
-    private function noFilter($visibility = 'all')
+    private function noFilter(string $visibility = 'all')
     {
         if ($visibility === 'all') {
             return $this->bookmarks;
@@ -151,11 +158,11 @@ class BookmarkFilter
      *
      * @param string $smallHash permalink hash.
      *
-     * @return array $filtered array containing permalink data.
+     * @return Bookmark[] $filtered array containing permalink data.
      *
-     * @throws \Shaarli\Bookmark\Exception\BookmarkNotFoundException if the smallhash doesn't match any link.
+     * @throws BookmarkNotFoundException if the smallhash doesn't match any link.
      */
-    private function filterSmallHash($smallHash)
+    private function filterSmallHash(string $smallHash)
     {
         foreach ($this->bookmarks as $key => $l) {
             if ($smallHash == $l->getShortUrl()) {
@@ -186,9 +193,9 @@ class BookmarkFilter
      * @param string $searchterms search query.
      * @param string $visibility  Optional: return only all/private/public bookmarks.
      *
-     * @return array search results.
+     * @return Bookmark[] search results.
      */
-    private function filterFulltext($searchterms, $visibility = 'all')
+    private function filterFulltext(string $searchterms, string $visibility = 'all')
     {
         if (empty($searchterms)) {
             return $this->noFilter($visibility);
@@ -268,7 +275,7 @@ class BookmarkFilter
      *
      * @return string generated regex fragment
      */
-    private static function tag2regex($tag)
+    private static function tag2regex(string $tag): string
     {
         $len = strlen($tag);
         if (!$len || $tag === "-" || $tag === "*") {
@@ -314,13 +321,13 @@ class BookmarkFilter
      * You can specify one or more tags, separated by space or a comma, e.g.
      *  print_r($mydb->filterTags('linux programming'));
      *
-     * @param string $tags          list of tags separated by commas or blank spaces.
-     * @param bool   $casesensitive ignore case if false.
-     * @param string $visibility    Optional: return only all/private/public bookmarks.
+     * @param string|array $tags          list of tags, separated by commas or blank spaces if passed as string.
+     * @param bool         $casesensitive ignore case if false.
+     * @param string       $visibility    Optional: return only all/private/public bookmarks.
      *
-     * @return array filtered bookmarks.
+     * @return Bookmark[] filtered bookmarks.
      */
-    public function filterTags($tags, $casesensitive = false, $visibility = 'all')
+    public function filterTags($tags, bool $casesensitive = false, string $visibility = 'all')
     {
         // get single tags (we may get passed an array, even though the docs say different)
         $inputTags = $tags;
@@ -396,9 +403,9 @@ class BookmarkFilter
      *
      * @param string $visibility return only all/private/public bookmarks.
      *
-     * @return array filtered bookmarks.
+     * @return Bookmark[] filtered bookmarks.
      */
-    public function filterUntagged($visibility)
+    public function filterUntagged(string $visibility)
     {
         $filtered = [];
         foreach ($this->bookmarks as $key => $link) {
@@ -427,11 +434,11 @@ class BookmarkFilter
      * @param string $day day to filter.
      * @param string $visibility return only all/private/public bookmarks.
 
-     * @return array all link matching given day.
+     * @return Bookmark[] all link matching given day.
      *
      * @throws Exception if date format is invalid.
      */
-    public function filterDay($day, $visibility)
+    public function filterDay(string $day, string $visibility)
     {
         if (!checkDateFormat('Ymd', $day)) {
             throw new Exception('Invalid date format');
@@ -460,9 +467,9 @@ class BookmarkFilter
      * @param string $tags          string containing a list of tags.
      * @param bool   $casesensitive will convert everything to lowercase if false.
      *
-     * @return array filtered tags string.
+     * @return string[] filtered tags string.
      */
-    public static function tagsStrToArray($tags, $casesensitive)
+    public static function tagsStrToArray(string $tags, bool $casesensitive): array
     {
         // We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek)
         $tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8');
index 099653b0e62d0b7acc7adee5c6f573c721238611..f40fa476247ff2fc9fd75c1048c4b7a340516b61 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Shaarli\Bookmark;
 
 use malkusch\lock\mutex\Mutex;
@@ -61,7 +63,7 @@ class BookmarkIO
     /**
      * Reads database from disk to memory
      *
-     * @return BookmarkArray instance
+     * @return Bookmark[]
      *
      * @throws NotWritableDataStoreException    Data couldn't be loaded
      * @throws EmptyDataStoreException          Datastore file exists but does not contain any bookmark
@@ -101,7 +103,7 @@ class BookmarkIO
     /**
      * Saves the database from memory to disk
      *
-     * @param BookmarkArray $links instance.
+     * @param Bookmark[] $links
      *
      * @throws NotWritableDataStoreException the datastore is not writable
      */
index 815047e38010d5123cb5f27539e7d5ec55fb419d..04b996f3e6500edc13d8e96353ede55ad250323d 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+declare(strict_types=1);
+
 namespace Shaarli\Bookmark;
 
 /**
@@ -23,7 +25,7 @@ class BookmarkInitializer
      *
      * @param BookmarkServiceInterface $bookmarkService
      */
-    public function __construct($bookmarkService)
+    public function __construct(BookmarkServiceInterface $bookmarkService)
     {
         $this->bookmarkService = $bookmarkService;
     }
@@ -31,7 +33,7 @@ class BookmarkInitializer
     /**
      * Initialize the data store with default bookmarks
      */
-    public function initialize()
+    public function initialize(): void
     {
         $bookmark = new Bookmark();
         $bookmark->setTitle('quicksilver (loop) on Vimeo ' . t('(private bookmark with thumbnail demo)'));
index 638cfa5fd73be1bc972b1391308ca88fd459b411..37a54d03ece93be2642974ee53a8b01cf699a217 100644 (file)
@@ -1,7 +1,8 @@
 <?php
 
-namespace Shaarli\Bookmark;
+declare(strict_types=1);
 
+namespace Shaarli\Bookmark;
 
 use Shaarli\Bookmark\Exception\BookmarkNotFoundException;
 use Shaarli\Bookmark\Exception\NotWritableDataStoreException;
@@ -10,6 +11,9 @@ use Shaarli\Bookmark\Exception\NotWritableDataStoreException;
  * Class BookmarksService
  *
  * This is the entry point to manipulate the bookmark DB.
+ *
+ * Regarding return types of a list of bookmarks, it can either be an array or an ArrayAccess implementation,
+ * so until PHP 8.0 is the minimal supported version with union return types it cannot be explicitly added.
  */
 interface BookmarkServiceInterface
 {
@@ -18,51 +22,51 @@ interface BookmarkServiceInterface
      *
      * @param string $hash
      *
-     * @return mixed
+     * @return Bookmark
      *
      * @throws \Exception
      */
-    public function findByHash($hash);
+    public function findByHash(string $hash): Bookmark;
 
     /**
      * @param $url
      *
      * @return Bookmark|null
      */
-    public function findByUrl($url);
+    public function findByUrl(string $url): ?Bookmark;
 
     /**
      * Search bookmarks
      *
-     * @param mixed  $request
-     * @param string $visibility
-     * @param bool   $caseSensitive
-     * @param bool   $untaggedOnly
-     * @param bool   $ignoreSticky
+     * @param array   $request
+     * @param ?string $visibility
+     * @param bool    $caseSensitive
+     * @param bool    $untaggedOnly
+     * @param bool    $ignoreSticky
      *
      * @return Bookmark[]
      */
     public function search(
-        $request = [],
-        $visibility = null,
-        $caseSensitive = false,
-        $untaggedOnly = false,
+        array $request = [],
+        string $visibility = null,
+        bool $caseSensitive = false,
+        bool $untaggedOnly = false,
         bool $ignoreSticky = false
     );
 
     /**
      * Get a single bookmark by its ID.
      *
-     * @param int    $id         Bookmark ID
-     * @param string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an
-     *                           exception
+     * @param int    $id          Bookmark ID
+     * @param ?string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an
+     *                            exception
      *
      * @return Bookmark
      *
      * @throws BookmarkNotFoundException
      * @throws \Exception
      */
-    public function get($id, $visibility = null);
+    public function get(int $id, string $visibility = null);
 
     /**
      * Updates an existing bookmark (depending on its ID).
@@ -75,7 +79,7 @@ interface BookmarkServiceInterface
      * @throws BookmarkNotFoundException
      * @throws \Exception
      */
-    public function set($bookmark, $save = true);
+    public function set(Bookmark $bookmark, bool $save = true): Bookmark;
 
     /**
      * Adds a new bookmark (the ID must be empty).
@@ -87,7 +91,7 @@ interface BookmarkServiceInterface
      *
      * @throws \Exception
      */
-    public function add($bookmark, $save = true);
+    public function add(Bookmark $bookmark, bool $save = true): Bookmark;
 
     /**
      * Adds or updates a bookmark depending on its ID:
@@ -101,7 +105,7 @@ interface BookmarkServiceInterface
      *
      * @throws \Exception
      */
-    public function addOrSet($bookmark, $save = true);
+    public function addOrSet(Bookmark $bookmark, bool $save = true): Bookmark;
 
     /**
      * Deletes a bookmark.
@@ -111,51 +115,51 @@ interface BookmarkServiceInterface
      *
      * @throws \Exception
      */
-    public function remove($bookmark, $save = true);
+    public function remove(Bookmark $bookmark, bool $save = true): void;
 
     /**
      * Get a single bookmark by its ID.
      *
-     * @param int    $id         Bookmark ID
-     * @param string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an
-     *                           exception
+     * @param int     $id         Bookmark ID
+     * @param ?string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an
+     *                            exception
      *
      * @return bool
      */
-    public function exists($id, $visibility = null);
+    public function exists(int $id, string $visibility = null): bool;
 
     /**
      * Return the number of available bookmarks for given visibility.
      *
-     * @param string $visibility public|private|all
+     * @param ?string $visibility public|private|all
      *
      * @return int Number of bookmarks
      */
-    public function count($visibility = null);
+    public function count(string $visibility = null): int;
 
     /**
      * Write the datastore.
      *
      * @throws NotWritableDataStoreException
      */
-    public function save();
+    public function save(): void;
 
     /**
      * Returns the list tags appearing in the bookmarks with the given tags
      *
-     * @param array  $filteringTags tags selecting the bookmarks to consider
-     * @param string $visibility    process only all/private/public bookmarks
+     * @param array|null  $filteringTags tags selecting the bookmarks to consider
+     * @param string|null $visibility    process only all/private/public bookmarks
      *
      * @return array tag => bookmarksCount
      */
-    public function bookmarksCountPerTag($filteringTags = [], $visibility = 'all');
+    public function bookmarksCountPerTag(array $filteringTags = [], ?string $visibility = null): array;
 
     /**
      * Returns the list of days containing articles (oldest first)
      *
      * @return array containing days (in format YYYYMMDD).
      */
-    public function days();
+    public function days(): array;
 
     /**
      * Returns the list of articles for a given day.
@@ -166,10 +170,10 @@ interface BookmarkServiceInterface
      *
      * @throws BookmarkNotFoundException
      */
-    public function filterDay($request);
+    public function filterDay(string $request);
 
     /**
      * Creates the default database after a fresh install.
      */
-    public function initialize();
+    public function initialize(): void;
 }
index f6def6308849eb6401fd908a4d063046f163dc11..f70fce4fb7fa0589269256c1907ffa5654051d01 100644 (file)
@@ -102,7 +102,7 @@ class FeedBuilder
         }
 
         // Optionally filter the results:
-        $linksToDisplay = $this->linkDB->search($userInput, null, false, false, true);
+        $linksToDisplay = $this->linkDB->search($userInput ?? [], null, false, false, true);
 
         $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay), $userInput);
 
index 81c87ed0369a4651abeeaa51ec7294159895decc..4dc09d388c86b932c9fe7cb0618a9aa222f8712a 100644 (file)
@@ -52,7 +52,7 @@ class ThumbnailsController extends ShaarliAdminController
         }
 
         try {
-            $bookmark = $this->container->bookmarkService->get($id);
+            $bookmark = $this->container->bookmarkService->get((int) $id);
         } catch (BookmarkNotFoundException $e) {
             return $response->withStatus(404);
         }
index d74c3118c4eded1713a339ebc824d1978c83f93d..65048f10668cb1217070acebe1a5f981e7dfa346 100644 (file)
@@ -118,7 +118,7 @@ class LoginManager
      *
      * @return true when the user is logged in, false otherwise
      */
-    public function isLoggedIn()
+    public function isLoggedIn(): bool
     {
         if ($this->openShaarli) {
             return true;
index 6dc0e5b7aa112e19f3dd7cbc6f820356885f3088..e810104eae23fe3bb3467403b504b9b3622010c8 100644 (file)
@@ -89,14 +89,6 @@ class HistoryTest extends \Shaarli\TestCase
         $this->assertEquals(History::CREATED, $actual['event']);
         $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
         $this->assertEquals(1, $actual['id']);
-
-        $history = new History(self::$historyFilePath);
-        $bookmark = (new Bookmark())->setId('str');
-        $history->addLink($bookmark);
-        $actual = $history->getHistory()[0];
-        $this->assertEquals(History::CREATED, $actual['event']);
-        $this->assertTrue(new DateTime('-2 seconds') < $actual['datetime']);
-        $this->assertEquals('str', $actual['id']);
     }
 
 //    /**
index ebed9bfcaf2d071faa25e8e05e6e404d1f0f5d38..1953078cd985a0c75aaa39a6464b7e833d8d2912 100644 (file)
@@ -90,19 +90,6 @@ class BookmarkArrayTest extends TestCase
         $array['nope'] = $bookmark;
     }
 
-    /**
-     * Test adding a bad entry: invalid ID type
-     */
-    public function testArrayAccessAddBadEntryIdType()
-    {
-        $this->expectException(\Shaarli\Bookmark\Exception\InvalidBookmarkException::class);
-
-        $array = new BookmarkArray();
-        $bookmark = (new Bookmark())->setId('nope');
-        $bookmark->validate();
-        $array[] = $bookmark;
-    }
-
     /**
      * Test adding a bad entry: ID/offset not consistent
      */
index 6c56dfaabbcb84655e82d75342f1b031332cd52c..59c0608c5a7c701db3a363733d7492563df27718 100644 (file)
@@ -264,17 +264,6 @@ class BookmarkFileServiceTest extends TestCase
         $this->publicLinkDB->add(new Bookmark());
     }
 
-    /**
-     * Test add() method with an entry which is not a bookmark instance
-     */
-    public function testAddNotABookmark()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage('Provided data is invalid');
-
-        $this->privateLinkDB->add(['title' => 'hi!']);
-    }
-
     /**
      * Test add() method with a Bookmark already containing an ID
      */
@@ -412,17 +401,6 @@ class BookmarkFileServiceTest extends TestCase
         $this->publicLinkDB->set(new Bookmark());
     }
 
-    /**
-     * Test set() method with an entry which is not a bookmark instance
-     */
-    public function testSetNotABookmark()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage('Provided data is invalid');
-
-        $this->privateLinkDB->set(['title' => 'hi!']);
-    }
-
     /**
      * Test set() method with a Bookmark without an ID defined.
      */
@@ -496,17 +474,6 @@ class BookmarkFileServiceTest extends TestCase
         $this->publicLinkDB->addOrSet(new Bookmark());
     }
 
-    /**
-     * Test addOrSet() method with an entry which is not a bookmark instance
-     */
-    public function testAddOrSetNotABookmark()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage('Provided data is invalid');
-
-        $this->privateLinkDB->addOrSet(['title' => 'hi!']);
-    }
-
     /**
      * Test addOrSet() method for a bookmark without any field set and without writing the data store
      */
@@ -564,17 +531,6 @@ class BookmarkFileServiceTest extends TestCase
         $this->publicLinkDB->remove($bookmark);
     }
 
-    /**
-     * Test remove() method with an entry which is not a bookmark instance
-     */
-    public function testRemoveNotABookmark()
-    {
-        $this->expectException(\Exception::class);
-        $this->expectExceptionMessage('Provided data is invalid');
-
-        $this->privateLinkDB->remove(['title' => 'hi!']);
-    }
-
     /**
      * Test remove() method with a Bookmark with an unknown ID
      */
index afec24403e75bb0c34255e9dff54db987ecdc3e4..4c7ae4c07fb91f56ab4e9718e775425cd3b6872e 100644 (file)
@@ -153,25 +153,6 @@ class BookmarkTest extends TestCase
         $this->assertContainsPolyfill('- ID: '. PHP_EOL, $exception->getMessage());
     }
 
-    /**
-     * Test validate() with a a bookmark with a non integer ID.
-     */
-    public function testValidateNotValidStringId()
-    {
-        $bookmark = new Bookmark();
-        $bookmark->setId('str');
-        $bookmark->setShortUrl('abc');
-        $bookmark->setCreated(\DateTime::createFromFormat('Ymd_His', '20190514_200102'));
-        $exception = null;
-        try {
-            $bookmark->validate();
-        } catch (InvalidBookmarkException $e) {
-            $exception = $e;
-        }
-        $this->assertNotNull($exception);
-        $this->assertContainsPolyfill('- ID: str'. PHP_EOL, $exception->getMessage());
-    }
-
     /**
      * Test validate() with a a bookmark without short url.
      */
@@ -210,25 +191,6 @@ class BookmarkTest extends TestCase
         $this->assertContainsPolyfill('- Created: '. PHP_EOL, $exception->getMessage());
     }
 
-    /**
-     * Test validate() with a a bookmark with a bad created datetime.
-     */
-    public function testValidateNotValidBadCreated()
-    {
-        $bookmark = new Bookmark();
-        $bookmark->setId(1);
-        $bookmark->setShortUrl('abc');
-        $bookmark->setCreated('hi!');
-        $exception = null;
-        try {
-            $bookmark->validate();
-        } catch (InvalidBookmarkException $e) {
-            $exception = $e;
-        }
-        $this->assertNotNull($exception);
-        $this->assertContainsPolyfill('- Created: Not a DateTime object'. PHP_EOL, $exception->getMessage());
-    }
-
     /**
      * Test setId() and make sure that default fields are generated.
      */
index ba774e2119137248bd5d23797137bbc006327f0f..83bbee7c39909c8867028d3908e5e44df0ac92a3 100644 (file)
@@ -356,6 +356,10 @@ class DeleteBookmarkTest extends TestCase
         ;
         $response = new Response();
 
+        $this->container->bookmarkService->method('get')->with('123')->willReturn(
+            (new Bookmark())->setId(123)->setUrl('http://domain.tld')->setTitle('Title 123')
+        );
+
         $this->container->formatterFactory = $this->createMock(FormatterFactory::class);
         $this->container->formatterFactory
             ->expects(static::once())
index f7a68226148e4b9434649b8fb409956745322416..37542c260eeef5bc67771d8fdf0e22e5f86575bb 100644 (file)
@@ -66,23 +66,27 @@ class SaveBookmarkTest extends TestCase
         $this->container->bookmarkService
             ->expects(static::once())
             ->method('addOrSet')
-            ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void {
+            ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): Bookmark {
                 static::assertFalse($save);
 
                 $checkBookmark($bookmark);
 
                 $bookmark->setId($id);
+
+                return $bookmark;
             })
         ;
         $this->container->bookmarkService
             ->expects(static::once())
             ->method('set')
-            ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void {
+            ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): Bookmark {
                 static::assertTrue($save);
 
                 $checkBookmark($bookmark);
 
                 static::assertSame($id, $bookmark->getId());
+
+                return $bookmark;
             })
         ;
 
@@ -155,21 +159,25 @@ class SaveBookmarkTest extends TestCase
         $this->container->bookmarkService
             ->expects(static::once())
             ->method('addOrSet')
-            ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void {
+            ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): Bookmark {
                 static::assertFalse($save);
 
                 $checkBookmark($bookmark);
+
+                return $bookmark;
             })
         ;
         $this->container->bookmarkService
             ->expects(static::once())
             ->method('set')
-            ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): void {
+            ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($checkBookmark, $id): Bookmark {
                 static::assertTrue($save);
 
                 $checkBookmark($bookmark);
 
                 static::assertSame($id, $bookmark->getId());
+
+                return $bookmark;
             })
         ;
 
@@ -230,8 +238,10 @@ class SaveBookmarkTest extends TestCase
         $this->container->bookmarkService
             ->expects(static::once())
             ->method('addOrSet')
-            ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($thumb): void {
+            ->willReturnCallback(function (Bookmark $bookmark, bool $save) use ($thumb): Bookmark {
                 static::assertSame($thumb, $bookmark->getThumbnail());
+
+                return $bookmark;
             })
         ;
 
index f4a8acffbdc822790bc6f5a30de0934022719d55..e5749654bec896f8866e4daee2b79a692cbe2d80 100644 (file)
@@ -89,8 +89,10 @@ class ThumbnailsControllerTest extends TestCase
         $this->container->bookmarkService
             ->expects(static::once())
             ->method('set')
-            ->willReturnCallback(function (Bookmark $bookmark) use ($thumb) {
+            ->willReturnCallback(function (Bookmark $bookmark) use ($thumb): Bookmark {
                 static::assertSame($thumb, $bookmark->getThumbnail());
+
+                return $bookmark;
             })
         ;