Add strict types for bookmarks management

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.
This commit is contained in:
ArthurHoaro 2020-10-02 17:50:59 +02:00
parent 29c31b7ec6
commit efb7d21b52
19 changed files with 209 additions and 285 deletions

View file

@ -89,12 +89,12 @@ public static function formatLink($bookmark, $indexUrl)
* 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']) : '';

View file

@ -96,11 +96,12 @@ public function getLinks($request, $response)
*/
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 @@ public function getLink($request, $response, $args)
*/
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 @@ public function postLink($request, $response)
*/
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 @@ public function putLink($request, $response, $args)
// 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 @@ public function putLink($request, $response, $args)
);
}
$responseBookmark = $this->bookmarkService->get($args['id']);
$responseBookmark = $this->bookmarkService->get($id);
$responseBookmark = ApiUtils::updateLink($responseBookmark, $requestBookmark);
$this->bookmarkService->set($responseBookmark);
@ -189,10 +191,11 @@ public function putLink($request, $response, $args)
*/
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);

View 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 @@ public function fromArray($data)
*
* @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 @@ public function validate()
* - 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 @@ public function setId($id)
/**
* Get the Id.
*
* @return int
* @return int|null
*/
public function getId()
public function getId(): ?int
{
return $this->id;
}
@ -149,9 +150,9 @@ public function getId()
/**
* Get the ShortUrl.
*
* @return string
* @return string|null
*/
public function getShortUrl()
public function getShortUrl(): ?string
{
return $this->shortUrl;
}
@ -159,9 +160,9 @@ public function getShortUrl()
/**
* Get the Url.
*
* @return string
* @return string|null
*/
public function getUrl()
public function getUrl(): ?string
{
return $this->url;
}
@ -171,7 +172,7 @@ public function getUrl()
*
* @return string
*/
public function getTitle()
public function getTitle(): ?string
{
return $this->title;
}
@ -181,7 +182,7 @@ public function getTitle()
*
* @return string
*/
public function getDescription()
public function getDescription(): string
{
return ! empty($this->description) ? $this->description : '';
}
@ -191,7 +192,7 @@ public function getDescription()
*
* @return DateTimeInterface
*/
public function getCreated()
public function getCreated(): ?DateTimeInterface
{
return $this->created;
}
@ -201,7 +202,7 @@ public function getCreated()
*
* @return DateTimeInterface
*/
public function getUpdated()
public function getUpdated(): ?DateTimeInterface
{
return $this->updated;
}
@ -209,11 +210,11 @@ public function getUpdated()
/**
* 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 @@ public function setShortUrl($shortUrl)
/**
* 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 @@ public function setUrl($url, $allowedProtocols = [])
/**
* 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 @@ public function setTitle($title)
/**
* 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 @@ public function setDescription($description)
* 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 @@ public function setCreated($created)
/**
* 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 @@ public function setUpdated($updated)
*
* @return bool
*/
public function isPrivate()
public function isPrivate(): bool
{
return $this->private ? true : false;
}
@ -309,11 +310,11 @@ public function isPrivate()
/**
* 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 @@ public function setPrivate($private)
/**
* 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 @@ public function getTags()
/**
* 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 @@ public function getThumbnail()
/**
* 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 @@ public function setThumbnail($thumbnail)
*
* @return bool
*/
public function isSticky()
public function isSticky(): bool
{
return $this->sticky ? true : false;
}
@ -381,11 +382,11 @@ public function isSticky()
/**
* 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 @@ public function setSticky($sticky)
/**
* @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 @@ public function getTagsString()
/**
* @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 @@ public function isNote()
* - 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 @@ public function setTagsString($tags)
* @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 @@ public function renameTag($fromTag, $toTag)
*
* @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]);

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace Shaarli\Bookmark;
use Shaarli\Bookmark\Exception\InvalidBookmarkException;
@ -187,13 +189,13 @@ public function valid()
/**
* 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 @@ protected function getBookmarkOffset($id)
*
* @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 @@ public function getNextId()
}
/**
* @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])

View 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 @@ public function __construct(ConfigManager $conf, History $history, Mutex $mutex,
/**
* @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 @@ public function findByHash($hash)
/**
* @inheritDoc
*/
public function findByUrl($url)
public function findByUrl(string $url): ?Bookmark
{
return $this->bookmarks->getByUrl($url);
}
@ -120,10 +121,10 @@ public function findByUrl($url)
* @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 @@ public function search(
}
// 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 @@ public function search(
return $this->bookmarkFilter->filter(
BookmarkFilter::$FILTER_TAG | BookmarkFilter::$FILTER_TEXT,
[$searchtags, $searchterm],
[$searchTags, $searchTerm],
$caseSensitive,
$visibility,
$untaggedOnly
@ -150,7 +151,7 @@ public function search(
/**
* @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 @@ public function get($id, $visibility = null)
/**
* @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 @@ public function set($bookmark, $save = true)
/**
* @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 @@ public function add($bookmark, $save = true)
/**
* @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 @@ public function addOrSet($bookmark, $save = true)
/**
* @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 @@ public function remove($bookmark, $save = true)
/**
* @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 @@ public function exists($id, $visibility = null)
/**
* @inheritDoc
*/
public function count($visibility = null)
public function count(string $visibility = null): int
{
return count($this->search([], $visibility));
}
@ -293,7 +282,7 @@ public function count($visibility = null)
/**
* @inheritDoc
*/
public function save()
public function save(): void
{
if (true !== $this->isLoggedIn) {
// TODO: raise an Exception instead
@ -308,7 +297,7 @@ public function save()
/**
* @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 @@ public function bookmarksCountPerTag($filteringTags = [], $visibility = null)
$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 @@ public function days()
/**
* @inheritDoc
*/
public function filterDay($request)
public function filterDay(string $request)
{
$visibility = $this->isLoggedIn ? BookmarkFilter::$ALL : BookmarkFilter::$PUBLIC;
@ -375,7 +365,7 @@ public function filterDay($request)
/**
* @inheritDoc
*/
public function initialize()
public function initialize(): void
{
$initializer = new BookmarkInitializer($this);
$initializer->initialize();
@ -388,7 +378,7 @@ public function initialize()
/**
* Handles migration to the new database format (BookmarksArray).
*/
protected function migrate()
protected function migrate(): void
{
$bookmarkDb = new LegacyLinkDB(
$this->conf->get('resource.datastore'),

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace Shaarli\Bookmark;
use Exception;
@ -77,8 +79,13 @@ public function __construct($bookmarks)
*
* @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 @@ public function filter($type, $request, $casesensitive = false, $visibility = 'a
*
* @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 @@ private function noFilter($visibility = 'all')
*
* @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 @@ private function filterSmallHash($smallHash)
* @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 @@ private function filterFulltext($searchterms, $visibility = 'all')
*
* @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 @@ private static function tag2regex($tag)
* 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 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 @@ public function filterTags($tags, $casesensitive = false, $visibility = 'all')
*
* @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 @@ public function filterUntagged($visibility)
* @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 @@ public function filterDay($day, $visibility)
* @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');

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace Shaarli\Bookmark;
use malkusch\lock\mutex\Mutex;
@ -61,7 +63,7 @@ public function __construct(ConfigManager $conf, Mutex $mutex = null)
/**
* 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 @@ public function read()
/**
* Saves the database from memory to disk
*
* @param BookmarkArray $links instance.
* @param Bookmark[] $links
*
* @throws NotWritableDataStoreException the datastore is not writable
*/

View 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 @@ public function __construct($bookmarkService)
/**
* 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)'));

View 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 @@
* 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,24 +22,24 @@ 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 array $request
* @param ?string $visibility
* @param bool $caseSensitive
* @param bool $untaggedOnly
* @param bool $ignoreSticky
@ -43,10 +47,10 @@ public function findByUrl($url);
* @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
);
@ -54,7 +58,7 @@ public function search(
* 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
* @param ?string $visibility all|public|private e.g. with public, accessing a private bookmark will throw an
* exception
*
* @return Bookmark
@ -62,7 +66,7 @@ public function search(
* @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 @@ public function get($id, $visibility = null);
* @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 @@ public function set($bookmark, $save = true);
*
* @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 @@ public function add($bookmark, $save = true);
*
* @throws \Exception
*/
public function addOrSet($bookmark, $save = true);
public function addOrSet(Bookmark $bookmark, bool $save = true): Bookmark;
/**
* Deletes a bookmark.
@ -111,51 +115,51 @@ public function addOrSet($bookmark, $save = true);
*
* @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
* @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 @@ public function days();
*
* @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;
}

View file

@ -102,7 +102,7 @@ public function buildData(string $feedType, ?array $userInput)
}
// 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);

View file

@ -52,7 +52,7 @@ public function ajaxUpdate(Request $request, Response $response, array $args): R
}
try {
$bookmark = $this->container->bookmarkService->get($id);
$bookmark = $this->container->bookmarkService->get((int) $id);
} catch (BookmarkNotFoundException $e) {
return $response->withStatus(404);
}

View file

@ -118,7 +118,7 @@ public function checkLoginState($clientIpId)
*
* @return true when the user is logged in, false otherwise
*/
public function isLoggedIn()
public function isLoggedIn(): bool
{
if ($this->openShaarli) {
return true;

View file

@ -89,14 +89,6 @@ public function testAddLink()
$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']);
}
// /**

View file

@ -90,19 +90,6 @@ public function testArrayAccessAddBadEntryOffset()
$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
*/

View file

@ -264,17 +264,6 @@ public function testAddLoggedOut()
$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 @@ public function testSetLoggedOut()
$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 @@ public function testAddOrSetLoggedOut()
$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 @@ public function testRemoveLoggedOut()
$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
*/

View file

@ -153,25 +153,6 @@ public function testValidateNotValidNoId()
$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 @@ public function testValidateNotValidNoCreated()
$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.
*/

View file

@ -356,6 +356,10 @@ public function testDeleteBookmarkFromBookmarklet(): void
;
$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())

View file

@ -66,23 +66,27 @@ public function testSaveBookmark(): void
$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 @@ public function testSaveExistingBookmark(): void
$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 @@ public function testSaveBookmarkWithThumbnail(): void
$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;
})
;

View file

@ -89,8 +89,10 @@ public function testAjaxUpdateValid(): void
$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;
})
;