From 4ef7afea5c88edf36e7bd00acc517eae453946df Mon Sep 17 00:00:00 2001 From: Artem Onyshchenko Date: Fri, 24 Apr 2020 20:12:27 +0300 Subject: [PATCH] feat(master): Add MicroBase module - Add base components for microservices --- .coveralls.yml | 3 + .docker/php7.3-dev/Dockerfile | 18 ++ .editorconfig | 25 +++ .env | 13 ++ .gitignore | 24 +++ .travis.yml | 30 +++ Makefile | 82 ++++++++ composer.json | 43 ++++ depfile.yml | 25 +++ docker-compose.yml | 12 ++ ecs.yml | 17 ++ phpmd.xml | 28 +++ phpstan.neon | 7 + phpunit.xml.dist | 32 +++ psalm.xml | 55 ++++++ src/Domain/Command/CommandInterface.php | 22 +++ src/Domain/Entity/SerializableInterface.php | 27 +++ src/Domain/Exception/AlertException.php | 17 ++ .../ClassNotAllowedToUnserializeException.php | 14 ++ src/Domain/Exception/CriticalException.php | 17 ++ src/Domain/Exception/EmergencyException.php | 17 ++ src/Domain/Exception/ErrorException.php | 17 ++ src/Domain/Exception/FactoryException.php | 14 ++ src/Domain/Exception/InvalidDataException.php | 14 ++ .../Exception/InvalidDataTypeException.php | 14 ++ .../InvalidResponseContentTypeException.php | 14 ++ .../InvalidResponseStatusCodeException.php | 14 ++ .../InvalidResponseStatusException.php | 14 ++ src/Domain/Exception/LoggerException.php | 14 ++ .../Exception/NormalizationException.php | 14 ++ .../Exception/ParentExceptionInterface.php | 37 ++++ src/Domain/Exception/ParentExceptionTrait.php | 86 ++++++++ .../Exception/SerializationException.php | 14 ++ .../Factory/CommandFactoryInterface.php | 24 +++ .../Service/NormalizableServiceInterface.php | 29 +++ .../Service/SerializableServiceInterface.php | 29 +++ src/Utils/LoggerTrait.php | 187 ++++++++++++++++++ tests/unit/.gitignore | 0 38 files changed, 1063 insertions(+) create mode 100644 .coveralls.yml create mode 100644 .docker/php7.3-dev/Dockerfile create mode 100644 .editorconfig create mode 100644 .env create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Makefile create mode 100644 composer.json create mode 100644 depfile.yml create mode 100644 docker-compose.yml create mode 100644 ecs.yml create mode 100644 phpmd.xml create mode 100644 phpstan.neon create mode 100644 phpunit.xml.dist create mode 100644 psalm.xml create mode 100644 src/Domain/Command/CommandInterface.php create mode 100644 src/Domain/Entity/SerializableInterface.php create mode 100644 src/Domain/Exception/AlertException.php create mode 100644 src/Domain/Exception/ClassNotAllowedToUnserializeException.php create mode 100644 src/Domain/Exception/CriticalException.php create mode 100644 src/Domain/Exception/EmergencyException.php create mode 100644 src/Domain/Exception/ErrorException.php create mode 100644 src/Domain/Exception/FactoryException.php create mode 100644 src/Domain/Exception/InvalidDataException.php create mode 100644 src/Domain/Exception/InvalidDataTypeException.php create mode 100644 src/Domain/Exception/InvalidResponseContentTypeException.php create mode 100644 src/Domain/Exception/InvalidResponseStatusCodeException.php create mode 100644 src/Domain/Exception/InvalidResponseStatusException.php create mode 100644 src/Domain/Exception/LoggerException.php create mode 100644 src/Domain/Exception/NormalizationException.php create mode 100644 src/Domain/Exception/ParentExceptionInterface.php create mode 100644 src/Domain/Exception/ParentExceptionTrait.php create mode 100644 src/Domain/Exception/SerializationException.php create mode 100644 src/Domain/Factory/CommandFactoryInterface.php create mode 100644 src/Domain/Service/NormalizableServiceInterface.php create mode 100644 src/Domain/Service/SerializableServiceInterface.php create mode 100644 src/Utils/LoggerTrait.php create mode 100644 tests/unit/.gitignore diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..6035eb3 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +service_name: travis-ci +coverage_clover: build/clover.xml +json_path: build/coveralls-upload.json \ No newline at end of file diff --git a/.docker/php7.3-dev/Dockerfile b/.docker/php7.3-dev/Dockerfile new file mode 100644 index 0000000..fcc639c --- /dev/null +++ b/.docker/php7.3-dev/Dockerfile @@ -0,0 +1,18 @@ +FROM php:7.3-cli + +RUN apt-get update && apt-get install -y git unzip + +ENV COMPOSER_ALLOW_SUPERUSER 1 +ENV COMPOSER_MEMORY_LIMIT -1 + +RUN mkdir /.composer_cache +ENV COMPOSER_CACHE_DIR /.composer_cache + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +RUN composer -vvv global require hirak/prestissimo + +# php extensions + +RUN pecl install xdebug +RUN docker-php-ext-enable xdebug diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f5939b2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +# 4 space indentation +[*.php] +indent_style = space +indent_size = 4 + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab + +# Matches the exact files either package.json or .travis.yml +[{*.yml, *.yaml}] +indent_style = space +indent_size = 2 + +[composer.json] +indent_size = 4 diff --git a/.env b/.env new file mode 100644 index 0000000..693bbb3 --- /dev/null +++ b/.env @@ -0,0 +1,13 @@ +# This file is a "template" of which env vars need to be defined for your application +# Copy this file to .env file for development, create environment variables when deploying to production +# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration + +###> symfony/framework-bundle ### +APP_ENV=dev +APP_SECRET=secret +###< symfony/framework-bundle ### + +###> common variables ### +MICROBASE_COMPOSE_PROJECT_NAME=micro-base +CI_COMMIT_REF_SLUG=master +###< common variables ### diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a760c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +vendor/ + +###> PhpStorm project profile ### +.idea/ +###< PhpStorm project profile ### + +###> phpunit/phpunit ### +phpunit.xml +.phpunit.result.cache +###< phpunit/phpunit ### + +###> friendsofphp/php-cs-fixer ### +.php_cs.cache +###< friendsofphp/php-cs-fixer ### + +###> squizlabs/php_codesniffer ### +.phpcs-cache +###< squizlabs/php_codesniffer ### + +###> sensiolabs-de/deptrac ### +.deptrac.cache +###< sensiolabs-de/deptrac ### + +composer.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..270a07b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ + +language: php + +matrix: + include: + - php: 7.3 + fast_finish: true + +env: + global: + TEST_CONFIG="phpunit.xml.dist" + +before_install: + - echo "memory_limit=2G" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini || return 0 + +install: + - travis_retry composer self-update + - composer install + +script: + - vendor/bin/phpstan analyse -l 6 -c phpstan.neon src tests + - vendor/bin/psalm --config=psalm.xml + - vendor/bin/ecs check src tests + - vendor/bin/phpmd src/ text phpmd.xml + - vendor/bin/phpunit --configuration $TEST_CONFIG + - composer validate --no-check-publish + - git log $(git describe --abbrev=0 --tags)...HEAD --no-merges --pretty=format:"* [%h](http://github.com/${TRAVIS_REPO_SLUG}/commit/%H) %s (%cN)" + +after_success: + - travis_retry php ./vendor/bin/php-coveralls -v --config .coveralls.yml -v; diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..04a87a5 --- /dev/null +++ b/Makefile @@ -0,0 +1,82 @@ +version = $(shell git describe --tags --dirty --always) +build_name = application-$(version) +# use the rest as arguments for "run" +RUN_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) +# ...and turn them into do-nothing targets +#$(eval $(RUN_ARGS):;@:) + +.PHONY: build +build: ## build environment and initialize composer and project dependencies + docker-compose build + make composer-install + +.PHONY: stop +stop: + docker-compose stop + +.PHONY: composer-install +composer-install: ## Install project dependencies + docker-compose run --rm --no-deps php sh -lc 'composer install' + +.PHONY: composer-update +composer-update: ## Update project dependencies + docker-compose run --rm --no-deps php sh -lc 'composer update' + +.PHONY: composer-outdated +composer-outdated: ## Show outdated project dependencies + docker-compose run --rm --no-deps php sh -lc 'composer outdated' + +.PHONY: composer-validate +composer-validate: ## Validate composer config + docker-compose run --rm --no-deps php sh -lc 'composer validate --no-check-publish' + +.PHONY: composer +composer: ## Execute composer command + docker-compose run --rm --no-deps php sh -lc "composer $(RUN_ARGS)" + +.PHONY: phpunit +phpunit: ## execute project unit tests + docker-compose run --rm --no-deps php sh -lc "./vendor/bin/phpunit $(conf)" + +.PHONY: style +style: ## executes php analizers + docker-compose run --rm --no-deps php sh -lc './vendor/bin/phpstan analyse -l 6 -c phpstan.neon src tests' + docker-compose run --rm --no-deps php sh -lc './vendor/bin/psalm --config=psalm.xml' + +.PHONY: lint +lint: ## checks syntax of PHP files + docker-compose run --rm --no-deps php sh -lc './vendor/bin/parallel-lint ./ --exclude vendor --exclude bin/.phpunit' + +.PHONY: layer +layer: ## Check issues with layers (deptrac tool) + docker-compose run --rm --no-deps php sh -lc './vendor/bin/deptrac analyze --formatter-graphviz=0' + +.PHONY: logs +logs: ## look for service logs + docker-compose logs -f $(RUN_ARGS) + +.PHONY: help +help: ## Display this help message + @cat $(MAKEFILE_LIST) | grep -e "^[a-zA-Z_\-]*: *.*## *" | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: php-shell +php-shell: ## PHP shell + docker-compose run --rm php sh -lflay + +unit-tests: ## Run unit-tests suite + docker-compose run --rm php sh -lc 'vendor/bin/phpunit --testsuite unit-tests' + +static-analysis: style layer coding-standards ## Run phpstan, easycoding standarts code static analysis + +coding-standards: ## Run check and validate code standards tests + docker-compose run --rm --no-deps php sh -lc 'vendor/bin/ecs check src tests' + docker-compose run --rm --no-deps php sh -lc 'vendor/bin/phpmd src/ text phpmd.xml' + +coding-standards-fixer: ## Run code standards fixer + docker-compose run --rm --no-deps php sh -lc 'vendor/bin/ecs check src tests --fix' + +security-tests: ## The SensioLabs Security Checker + docker-compose run --rm --no-deps php sh -lc 'vendor/bin/security-checker security:check --end-point=http://security.sensiolabs.org/check_lock' + +.PHONY: test lint static-analysis phpunit coding-standards composer-validate +test: build lint static-analysis phpunit coding-standards composer-validate stop ## Run all test suites diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c517d73 --- /dev/null +++ b/composer.json @@ -0,0 +1,43 @@ +{ + "name": "micro-module/base", + "type": "library", + "description": "Micro module Base common library", + "license": "proprietary", + "require": { + "php": "^7.3", + "ext-json": "*", + "beberlei/assert": "^3.2", + "psr/log": "^1.1", + "ramsey/uuid": "^3.8 || ^4.0", + "monolog/monolog": "~1.22 || ~2.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^0.4", + "php-parallel-lint/php-parallel-lint": "^1.0", + "mockery/mockery": "^1.3", + "phpmd/phpmd": "^2.8", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^9.0", + "roave/security-advisories": "dev-master", + "symplify/easy-coding-standard": "^7.2", + "vimeo/psalm": "3.5.0" + }, + "config": { + "preferred-install": { + "*": "dist" + }, + "sort-packages": true + }, + "autoload": { + "psr-4": { + "MicroModule\\Base\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "MicroModule\\Base\\Tests\\Unit\\": "tests/unit" + } + } +} diff --git a/depfile.yml b/depfile.yml new file mode 100644 index 0000000..9cd398e --- /dev/null +++ b/depfile.yml @@ -0,0 +1,25 @@ +paths: + - ./src +exclude_files: + +layers: + - name: Domain + collectors: + - type: className + regex: .*\\Domain\\.* + - name: Application + collectors: + - type: className + regex: .*\\Application\\.* + - name: Infrastructure + collectors: + - type: className + regex: .*\\Infrastructure\\.* + +ruleset: + Domain: + Application: + - Domain + Infrastructure: + - Domain + - Application diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f29c43f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.7" + +services: + php: + container_name: ${MICROBASE_COMPOSE_PROJECT_NAME}_php + user: 1000:1000 + build: + context: .docker/php7.3-dev + volumes: + - ~/.composer/cache/:/.composer_cache/:rw + - .:/app:rw + working_dir: /app diff --git a/ecs.yml b/ecs.yml new file mode 100644 index 0000000..629fafb --- /dev/null +++ b/ecs.yml @@ -0,0 +1,17 @@ +imports: + - { resource: 'vendor/symplify/easy-coding-standard/config/clean-code.yml' } + - { resource: 'vendor/symplify/easy-coding-standard/config/symfony.yml' } + - { resource: 'vendor/symplify/easy-coding-standard/config/php71.yml' } + +parameters: + skip: + SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingParameterTypeHint: + - 'src/Infrastructure/Testing/RedisInMemory.php' + SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.MissingReturnTypeHint: + - 'src/Infrastructure/Testing/RedisInMemory.php' + SlevomatCodingStandard\Sniffs\Classes\UnusedPrivateElementsSniff.UnusedMethod: + - 'src/Common/Alerting/AlertingProcessor.php' + Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer: + - 'src/Domain/Exception/ParentExceptionTrait.php' + Symplify\CodingStandard\Fixer\Commenting\ParamReturnAndVarTagMalformsFixer: + - 'src/Domain/Exception/ParentExceptionTrait.php' diff --git a/phpmd.xml b/phpmd.xml new file mode 100644 index 0000000..700df16 --- /dev/null +++ b/phpmd.xml @@ -0,0 +1,28 @@ + + + Ruleset for PHP Mess Detector that enforces coding standards + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..c1c0981 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - vendor/phpstan/phpstan-mockery/extension.neon + +parameters: + excludes_analyse: + - src/Infrastructure/Testing/RedisInMemory.php + - src/Infrastructure/Testing/RedisFactory.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..d19a08f --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + tests/unit + + + + + + + + + + ./src + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..39b4cb3 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Domain/Command/CommandInterface.php b/src/Domain/Command/CommandInterface.php new file mode 100644 index 0000000..15f8674 --- /dev/null +++ b/src/Domain/Command/CommandInterface.php @@ -0,0 +1,22 @@ +parentException; + } + + /** + * Return context exception array from parent exception object. + * + * @param mixed[] $context + * @param string $contextSerializeType + * + * @return mixed[] + */ + public function getParentExceptionContext(array $context = [], string $contextSerializeType = ParentExceptionInterface::CONTEXT_SELIALIZE_NONE): array + { + $exception = $this->getParentException(); + + if ($exception instanceof ParentExceptionInterface) { + $parentException = $exception->getParentException(); + $context['parentException'] = get_class($parentException); + $context['parentExceptionErrorMessage'] = sprintf('%s: "%s" at %s line %s', get_class($parentException), $parentException->getMessage(), $parentException->getFile(), $parentException->getLine()); + $context['parentExceptionContext'] = $this->serializeContext($exception->getParentExceptionContext(), $contextSerializeType); + } + + return $context; + } + + /** + * Serialize if needed context array to string. + * + * @param mixed[] $context + * @param string $contextSerializeType + * + * @return mixed + */ + protected function serializeContext(array $context = [], string $contextSerializeType = ParentExceptionInterface::CONTEXT_SELIALIZE_NONE) + { + switch ($contextSerializeType) { + case ParentExceptionInterface::CONTEXT_SELIALIZE_BASIC: + $context = serialize($context); + + break; + + case ParentExceptionInterface::CONTEXT_SELIALIZE_JSON: + $context = json_encode($context); + + break; + + case ParentExceptionInterface::CONTEXT_SELIALIZE_PRINTR: + $context = print_r($context, true); + + break; + + case ParentExceptionInterface::CONTEXT_SELIALIZE_NONE: + default: + break; + } + + return $context; + } +} diff --git a/src/Domain/Exception/SerializationException.php b/src/Domain/Exception/SerializationException.php new file mode 100644 index 0000000..2d480ae --- /dev/null +++ b/src/Domain/Exception/SerializationException.php @@ -0,0 +1,14 @@ +logger = $logger; + + return $this; + } + + /** + * Log an regular message or warning. + * + * @param string $message + * @param int $level + * @param mixed[] $context + * + * @return $this + */ + public function logMessage(string $message, int $level, array $context = []): self + { + if (!$this->logger instanceof LoggerInterface) { + return $this; + } + + switch ($level) { + case LOG_DEBUG: + $this->logger->debug($message, $context); + + break; + + case LOG_INFO: + $this->logger->info($message, $context); + + break; + + case LOG_NOTICE: + $this->logger->notice($message, $context); + + break; + + case LOG_WARNING: + $this->logger->warning($message, $context); + + break; + + default: + throw new LoggerException(sprintf("Try to log invalid message level type '%s'", $level)); + + break; + } + + return $this; + } + + /** + * Log an exception. + * + * @param Throwable $exception The \Throwable instance + * @param int $level + * @param string $message The error message to log + * + * @return $this + */ + public function logException(Throwable $exception, int $level, string $message): self + { + if (!$this->logger instanceof LoggerInterface) { + return $this; + } + + $context = ['exception' => $exception]; + $this->parentException = $exception; + $context = $this->getParentExceptionContext($context, ParentExceptionInterface::CONTEXT_SELIALIZE_PRINTR); + + switch ($level) { + case LOG_EMERG: + $this->logger->emergency($message, $context); + + break; + + case LOG_ALERT: + $this->logger->alert($message, $context); + + break; + + case LOG_CRIT: + $this->logger->critical($message, $context); + + break; + + case LOG_ERR: + $this->logger->error($message, $context); + + break; + + default: + throw new LoggerException(sprintf("Try to log invalid error level type '%s'", $level)); + + break; + } + + return $this; + } + + /** + * Define exception level from exception type. + * + * @param Throwable $exception + * + * @return int + */ + public function getExceptionLevel(Throwable $exception): int + { + switch (true) { + case $exception instanceof EmergencyException: + return LOG_EMERG; + + case $exception instanceof AlertException: + return LOG_ALERT; + + case $exception instanceof CriticalException: + return LOG_CRIT; + + case $exception instanceof ErrorException: + return LOG_ERR; + } + + return LOG_ERR; + } + + /** + * Generate exception message. + * + * @param Throwable $exception + * + * @return string + */ + public function getExceptionMessage(Throwable $exception): string + { + return sprintf($this->exceptionMessageTemplate, get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine()); + } +} diff --git a/tests/unit/.gitignore b/tests/unit/.gitignore new file mode 100644 index 0000000..e69de29