From f05e2394be970fac6e396256f013339430fb253f Mon Sep 17 00:00:00 2001 From: Anton Komarev <1849174+antonkomarev@users.noreply.github.com> Date: Sat, 18 Jul 2020 20:32:06 +0300 Subject: [PATCH] Add Username Value Object (#6) Add Username Value Object --- composer.json | 3 +- composer.lock | 51 +++++++++++++++++++++++- contracts/CounterRepositoryInterface.php | 6 ++- public/database-repository.php | 3 ++ public/file-repository.php | 3 ++ src/CounterDatabaseRepository.php | 16 +++++--- src/CounterFileRepository.php | 20 ++++++---- src/Username.php | 44 ++++++++++++++++++++ 8 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 src/Username.php diff --git a/composer.json b/composer.json index 30fa4e8..3c43944 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ }, "require": { "php": "^7.4", - "vlucas/phpdotenv": "^5.0" + "vlucas/phpdotenv": "^5.0", + "webmozart/assert": "^1.9" }, "require-dev": { }, diff --git a/composer.lock b/composer.lock index b2283c1..94a019e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "26df08e2e3ecf73c0f0c84752718e156", + "content-hash": "1199b144b9fb9e5178e80602812dd8b2", "packages": [ { "name": "graham-campbell/result-type", @@ -369,6 +369,55 @@ "environment" ], "time": "2020-06-07T19:04:14+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2020-07-08T17:02:28+00:00" } ], "packages-dev": [], diff --git a/contracts/CounterRepositoryInterface.php b/contracts/CounterRepositoryInterface.php index 46d4d65..a54c610 100644 --- a/contracts/CounterRepositoryInterface.php +++ b/contracts/CounterRepositoryInterface.php @@ -13,9 +13,11 @@ declare(strict_types=1); namespace Contracts\Komarev\GitHubProfileViewsCounter; +use Komarev\GitHubProfileViewsCounter\Username; + interface CounterRepositoryInterface { - public function getViewsCountByUsername(string $username): int; + public function getViewsCountByUsername(Username $username): int; - public function addViewByUsername(string $username): void; + public function addViewByUsername(Username $username): void; } diff --git a/public/database-repository.php b/public/database-repository.php index 6578d6b..7c68eeb 100644 --- a/public/database-repository.php +++ b/public/database-repository.php @@ -15,6 +15,7 @@ use Dotenv\Dotenv; use Dotenv\Exception\InvalidPathException; use Komarev\GitHubProfileViewsCounter\BadgeImageRendererService; use Komarev\GitHubProfileViewsCounter\CounterDatabaseRepository; +use Komarev\GitHubProfileViewsCounter\Username; $basePath = realpath(__DIR__ . '/..'); @@ -50,6 +51,8 @@ try { exit; } + $username = new Username($username); + $dsn = sprintf( '%s:host=%s;port=%d;dbname=%s', $_ENV['DB_DRIVER'], $_ENV['DB_HOST'], $_ENV['DB_PORT'], $_ENV['DB_NAME'] diff --git a/public/file-repository.php b/public/file-repository.php index ba62a40..99c90e6 100644 --- a/public/file-repository.php +++ b/public/file-repository.php @@ -14,6 +14,7 @@ declare(strict_types=1); use Dotenv\Dotenv; use Komarev\GitHubProfileViewsCounter\BadgeImageRendererService; use Komarev\GitHubProfileViewsCounter\CounterFileRepository; +use Komarev\GitHubProfileViewsCounter\Username; $basePath = realpath(__DIR__ . '/..'); @@ -46,6 +47,8 @@ try { exit; } + $username = new Username($username); + $counterRepository = new CounterFileRepository($storagePath); $counterRepository->addViewByUsername($username); diff --git a/src/CounterDatabaseRepository.php b/src/CounterDatabaseRepository.php index c9b68e7..461af33 100644 --- a/src/CounterDatabaseRepository.php +++ b/src/CounterDatabaseRepository.php @@ -16,20 +16,24 @@ namespace Komarev\GitHubProfileViewsCounter; use Contracts\Komarev\GitHubProfileViewsCounter\CounterRepositoryInterface; use PDO; -final class CounterDatabaseRepository implements CounterRepositoryInterface +final class CounterDatabaseRepository implements + CounterRepositoryInterface { private PDO $connection; - public function __construct(PDO $connection) + private string $tableName; + + public function __construct(PDO $connection, string $tableName = null) { $this->connection = $connection; + $this->tableName = $tableName !== null ? $tableName : 'github_profile_views'; } - public function getViewsCountByUsername(string $username): int + public function getViewsCountByUsername(Username $username): int { $statement = $this->connection->prepare( 'SELECT COUNT(*) - FROM github_profile_views + FROM ' . $this->tableName . ' WHERE username = :username;' ); $statement->bindParam('username', $username); @@ -38,10 +42,10 @@ final class CounterDatabaseRepository implements CounterRepositoryInterface return (int) $statement->fetchColumn(0); } - public function addViewByUsername(string $username): void + public function addViewByUsername(Username $username): void { $statement = $this->connection->prepare( - 'INSERT INTO github_profile_views + 'INSERT INTO ' . $this->tableName . ' (username, created_at) VALUES (:username, NOW());' ); diff --git a/src/CounterFileRepository.php b/src/CounterFileRepository.php index 3769c84..e4c2016 100644 --- a/src/CounterFileRepository.php +++ b/src/CounterFileRepository.php @@ -18,7 +18,8 @@ use Contracts\Komarev\GitHubProfileViewsCounter\InvalidPathException; use DateTimeImmutable; use DateTimeZone; -final class CounterFileRepository implements CounterRepositoryInterface +final class CounterFileRepository implements + CounterRepositoryInterface { private string $storagePath; @@ -35,38 +36,43 @@ final class CounterFileRepository implements CounterRepositoryInterface $this->storagePath = $storagePath; } - public function getViewsCountByUsername(string $username): int + public function getViewsCountByUsername(Username $username): int { $counterFilePath = $this->getCounterFilePath($username); return file_exists($counterFilePath) ? (int) file_get_contents($counterFilePath) : 0; } - public function addViewByUsername(string $username): void + public function addViewByUsername(Username $username): void { file_put_contents( $this->getViewsFilePath($username), - (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DATE_RFC3339_EXTENDED) . PHP_EOL, + $this->getCurrentFormattedDateTime() . PHP_EOL, FILE_APPEND ); $this->incrementViewsCount($username); } - private function incrementViewsCount(string $username): void + private function incrementViewsCount(Username $username): void { $counterFilePath = $this->getCounterFilePath($username); file_put_contents($counterFilePath, $this->getViewsCountByUsername($username) + 1); } - private function getViewsFilePath(string $username): string + private function getViewsFilePath(Username $username): string { return $this->storagePath . '/' . $username . '-views'; } - private function getCounterFilePath(string $username): string + private function getCounterFilePath(Username $username): string { return $this->storagePath . '/' . $username . '-views-count'; } + + private function getCurrentFormattedDateTime(): string + { + return (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format(DATE_RFC3339_EXTENDED); + } } diff --git a/src/Username.php b/src/Username.php new file mode 100644 index 0000000..4811e86 --- /dev/null +++ b/src/Username.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Komarev\GitHubProfileViewsCounter; + +use Webmozart\Assert\Assert; + +final class Username +{ + private const MIN_LENGTH = 1; + + private const MAX_LENGTH = 39; + + /** + * RegEx adapted from https://github.com/shinnn/github-username-regex/blob/master/index.js + */ + private const REGEX = '#^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$#i'; + + private string $username; + + public function __construct(string $username) + { + Assert::minLength($username, self::MIN_LENGTH); + Assert::maxLength($username, self::MAX_LENGTH); + Assert::regex($username, self::REGEX, 'Username may only contain alphanumeric characters or single hyphens, and cannot begin or end with a hyphen.'); + + $this->username = strtolower($username); + } + + public function __toString(): string + { + return $this->username; + } +}