From 8ee9ca86526506c7391fcfd97a4af6be70a3bcd0 Mon Sep 17 00:00:00 2001
From: "a.shamavov" <a.shamavov@iqdev.digital>
Date: Mon, 15 Apr 2024 15:42:12 +0500
Subject: [PATCH] refactoring

---
 composer.json                        |   2 +-
 composer.lock                        | 107 +++++++++++++++------------
 src/Action/Functions.php             |  55 +-------------
 src/Controller/HomeController.php    |  66 ++++++++---------
 src/Requests/BaseRequest.php         |  62 ++++++++++++++++
 src/Requests/MenuRequest.php         |  16 ++++
 src/Requests/SearchRequest.php       |  20 +++++
 src/Requests/SortPriceRequest.php    |  16 ++++
 src/Requests/UniqElementsRequest.php |  16 ++++
 9 files changed, 221 insertions(+), 139 deletions(-)
 create mode 100644 src/Requests/BaseRequest.php
 create mode 100644 src/Requests/MenuRequest.php
 create mode 100644 src/Requests/SearchRequest.php
 create mode 100644 src/Requests/SortPriceRequest.php
 create mode 100644 src/Requests/UniqElementsRequest.php

diff --git a/composer.json b/composer.json
index be86947..5246c9a 100644
--- a/composer.json
+++ b/composer.json
@@ -39,7 +39,7 @@
         "symfony/translation": "7.0.*",
         "symfony/twig-bundle": "7.0.*",
         "symfony/ux-turbo": "^2.16",
-        "symfony/validator": "7.0.*",
+        "symfony/validator": "6.4.*",
         "symfony/web-link": "7.0.*",
         "symfony/yaml": "7.0.*",
         "twig/extra-bundle": "^2.12|^3.0",
diff --git a/composer.lock b/composer.lock
index 5695357..7c3d7d0 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": "168d5f8561d288fe5cd6fd2b406687e2",
+    "content-hash": "91b0c89268c08e0b881610c8ba320eb8",
     "packages": [
         {
             "name": "composer/semver",
@@ -1375,16 +1375,16 @@
         },
         {
             "name": "monolog/monolog",
-            "version": "3.5.0",
+            "version": "3.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Seldaek/monolog.git",
-                "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448"
+                "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c915e2634718dbc8a4a15c61b0e62e7a44e14448",
-                "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4b18b21a5527a3d5ffdac2fd35d3ab25a9597654",
+                "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654",
                 "shasum": ""
             },
             "require": {
@@ -1407,7 +1407,7 @@
                 "phpstan/phpstan": "^1.9",
                 "phpstan/phpstan-deprecation-rules": "^1.0",
                 "phpstan/phpstan-strict-rules": "^1.4",
-                "phpunit/phpunit": "^10.1",
+                "phpunit/phpunit": "^10.5.17",
                 "predis/predis": "^1.1 || ^2",
                 "ruflin/elastica": "^7",
                 "symfony/mailer": "^5.4 || ^6",
@@ -1460,7 +1460,7 @@
             ],
             "support": {
                 "issues": "https://github.com/Seldaek/monolog/issues",
-                "source": "https://github.com/Seldaek/monolog/tree/3.5.0"
+                "source": "https://github.com/Seldaek/monolog/tree/3.6.0"
             },
             "funding": [
                 {
@@ -1472,7 +1472,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-10-27T15:32:31+00:00"
+            "time": "2024-04-12T21:02:21+00:00"
         },
         {
             "name": "phpdocumentor/reflection-common",
@@ -1529,28 +1529,35 @@
         },
         {
             "name": "phpdocumentor/reflection-docblock",
-            "version": "5.3.0",
+            "version": "5.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
-                "reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
+                "reference": "298d2febfe79d03fe714eb871d5538da55205b1a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
-                "reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/298d2febfe79d03fe714eb871d5538da55205b1a",
+                "reference": "298d2febfe79d03fe714eb871d5538da55205b1a",
                 "shasum": ""
             },
             "require": {
+                "doctrine/deprecations": "^1.1",
                 "ext-filter": "*",
-                "php": "^7.2 || ^8.0",
+                "php": "^7.4 || ^8.0",
                 "phpdocumentor/reflection-common": "^2.2",
-                "phpdocumentor/type-resolver": "^1.3",
+                "phpdocumentor/type-resolver": "^1.7",
+                "phpstan/phpdoc-parser": "^1.7",
                 "webmozart/assert": "^1.9.1"
             },
             "require-dev": {
-                "mockery/mockery": "~1.3.2",
-                "psalm/phar": "^4.8"
+                "mockery/mockery": "~1.3.5",
+                "phpstan/extension-installer": "^1.1",
+                "phpstan/phpstan": "^1.8",
+                "phpstan/phpstan-mockery": "^1.1",
+                "phpstan/phpstan-webmozart-assert": "^1.2",
+                "phpunit/phpunit": "^9.5",
+                "vimeo/psalm": "^5.13"
             },
             "type": "library",
             "extra": {
@@ -1574,15 +1581,15 @@
                 },
                 {
                     "name": "Jaap van Otterdijk",
-                    "email": "account@ijaap.nl"
+                    "email": "opensource@ijaap.nl"
                 }
             ],
             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
             "support": {
                 "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
-                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
+                "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.0"
             },
-            "time": "2021-10-19T17:43:47+00:00"
+            "time": "2024-04-09T21:13:58+00:00"
         },
         {
             "name": "phpdocumentor/type-resolver",
@@ -6820,53 +6827,55 @@
         },
         {
             "name": "symfony/validator",
-            "version": "v7.0.6",
+            "version": "v6.4.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/validator.git",
-                "reference": "a2df2c63b7944a162dee86ab8065f2f91b7d6e36"
+                "reference": "ca1d78e8677e966e307a63799677b64b194d735d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/validator/zipball/a2df2c63b7944a162dee86ab8065f2f91b7d6e36",
-                "reference": "a2df2c63b7944a162dee86ab8065f2f91b7d6e36",
+                "url": "https://api.github.com/repos/symfony/validator/zipball/ca1d78e8677e966e307a63799677b64b194d735d",
+                "reference": "ca1d78e8677e966e307a63799677b64b194d735d",
                 "shasum": ""
             },
             "require": {
-                "php": ">=8.2",
+                "php": ">=8.1",
+                "symfony/deprecation-contracts": "^2.5|^3",
                 "symfony/polyfill-ctype": "~1.8",
                 "symfony/polyfill-mbstring": "~1.0",
                 "symfony/polyfill-php83": "^1.27",
                 "symfony/translation-contracts": "^2.5|^3"
             },
             "conflict": {
+                "doctrine/annotations": "<1.13",
                 "doctrine/lexer": "<1.1",
-                "symfony/dependency-injection": "<6.4",
-                "symfony/doctrine-bridge": "<7.0",
-                "symfony/expression-language": "<6.4",
-                "symfony/http-kernel": "<6.4",
-                "symfony/intl": "<6.4",
-                "symfony/property-info": "<6.4",
-                "symfony/translation": "<6.4.3|>=7.0,<7.0.3",
-                "symfony/yaml": "<6.4"
+                "symfony/dependency-injection": "<5.4",
+                "symfony/expression-language": "<5.4",
+                "symfony/http-kernel": "<5.4",
+                "symfony/intl": "<5.4",
+                "symfony/property-info": "<5.4",
+                "symfony/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3",
+                "symfony/yaml": "<5.4"
             },
             "require-dev": {
+                "doctrine/annotations": "^1.13|^2",
                 "egulias/email-validator": "^2.1.10|^3|^4",
-                "symfony/cache": "^6.4|^7.0",
-                "symfony/config": "^6.4|^7.0",
-                "symfony/console": "^6.4|^7.0",
-                "symfony/dependency-injection": "^6.4|^7.0",
-                "symfony/expression-language": "^6.4|^7.0",
-                "symfony/finder": "^6.4|^7.0",
-                "symfony/http-client": "^6.4|^7.0",
-                "symfony/http-foundation": "^6.4|^7.0",
-                "symfony/http-kernel": "^6.4|^7.0",
-                "symfony/intl": "^6.4|^7.0",
-                "symfony/mime": "^6.4|^7.0",
-                "symfony/property-access": "^6.4|^7.0",
-                "symfony/property-info": "^6.4|^7.0",
-                "symfony/translation": "^6.4.3|^7.0.3",
-                "symfony/yaml": "^6.4|^7.0"
+                "symfony/cache": "^5.4|^6.0|^7.0",
+                "symfony/config": "^5.4|^6.0|^7.0",
+                "symfony/console": "^5.4|^6.0|^7.0",
+                "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+                "symfony/expression-language": "^5.4|^6.0|^7.0",
+                "symfony/finder": "^5.4|^6.0|^7.0",
+                "symfony/http-client": "^5.4|^6.0|^7.0",
+                "symfony/http-foundation": "^5.4|^6.0|^7.0",
+                "symfony/http-kernel": "^5.4|^6.0|^7.0",
+                "symfony/intl": "^5.4|^6.0|^7.0",
+                "symfony/mime": "^5.4|^6.0|^7.0",
+                "symfony/property-access": "^5.4|^6.0|^7.0",
+                "symfony/property-info": "^5.4|^6.0|^7.0",
+                "symfony/translation": "^5.4.35|~6.3.12|^6.4.3|^7.0.3",
+                "symfony/yaml": "^5.4|^6.0|^7.0"
             },
             "type": "library",
             "autoload": {
@@ -6894,7 +6903,7 @@
             "description": "Provides tools to validate values",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/validator/tree/v7.0.6"
+                "source": "https://github.com/symfony/validator/tree/v6.4.6"
             },
             "funding": [
                 {
@@ -6910,7 +6919,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2024-03-28T09:20:36+00:00"
+            "time": "2024-03-27T22:00:14+00:00"
         },
         {
             "name": "symfony/var-dumper",
diff --git a/src/Action/Functions.php b/src/Action/Functions.php
index ce09705..cf39542 100644
--- a/src/Action/Functions.php
+++ b/src/Action/Functions.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace App\Action;
 
+use Exception;
 use DateTimeImmutable;
 
 class Functions
@@ -13,7 +14,6 @@ class Functions
      * @param array $array
      * @return array
      */
-
     public function sortPrice(array $array): array
     {
         $prices = array_column($array, 'price');
@@ -27,13 +27,6 @@ class Functions
         return $array;
     }
 
-    /**
-     * На выход должна вернуть отсортированный массив по ключу *price* DESC
-     * и во вторую очередь по *count* ASC:
-     * [['price'=>12, 'count'=>4],  ['price'=>10, 'count'=>2],  ['price'=>8, 'count'=>4],
-     * ['price'=>8, 'count'=>5],  ['price'=>5, 'count'=>5],]
-     */
-
     /**
      * Найдет элемент с указаным id
      * @param array $array - массив, содержащий элементы со структурой
@@ -60,21 +53,11 @@ class Functions
      * @param array $array
      * @return array
      */
-
     public function uniqElements(array $array): array
     {
         return array_unique($array, SORT_REGULAR);
     }
 
-    /**
-      * Выходной массив:
-      * Array (
-      *   [0] => Array([0] => laravel, [1] => php)
-      *   [1] => Array([0] => codeigniter, [1] => php)
-      *   [3] => Array([0] => c++, [1] => java))
-      * )
-      */
-
     /**
      * Сгруппировать подразедлы в верхние разделы меню
      * Дочерние элементы поместить в массив родителя с ключом submenu
@@ -85,7 +68,6 @@ class Functions
      * @param array $aMenu
      * @return array
      */
-
     public function prepareMenu(array $aMenu): array
     {
         $result = [];
@@ -106,47 +88,12 @@ class Functions
         return $result;
     }
 
-    /**
-     * Выходные данные:
-     * $aMenu = [
-     * [
-     *  'name' => 'Смартфоны и гаджеты',
-     *  'depth' => 0,
-     *  'submenu' => [
-     *      ['name' => 'Смартфоны, мобильные телефоны','depth' => 1,],
-     *      ['name' => 'Планшеты','depth' => 1,],
-     *      ['name' => 'Наушники и гарнитуры','depth' => 1,],],
-     * ],
-     * [
-     *  'name' => 'Компьютеры и ноутбуки',
-     *  'depth' => 0,
-     *  'submenu' => [
-     *      ['name' => 'Ноутбуки и аксессуары','depth' => 1,],
-     *      ['name' => 'Компьютеры и мониторы','depth' => 1,],
-     *      ['name' => 'Компьютерные комплектующие','depth' => 1,],]],
-     * [
-     *  'name' => 'Техника для дома',
-     *  'depth' => 0,
-     *  'submenu' => [
-     *      ['name' => 'Техника для уборки','depth' => 1,],
-     *      ['name' => 'Товары для ухода за одеждой','depth' => 1,],
-     *      ['name' => 'Аксессуары для техники','depth' => 1,],]
-     * ],
-     * [
-     *  'name' => 'Товары для дома и кухни',
-     *  'depth' => 0,
-     *  'submenu' => [
-     *      ['name' => 'Посуда','depth' => 1,],]],
-     * ];
-     */
-
     /**
      * Функция рассчитывает кол-во дней до нового года
      * @param DateTimeImmutable $date дата от которой, необходимо рассчитать кол-во дней
      * @return int
      * @throws Exception
      */
-
     public function howDaysToNy(DateTimeImmutable $date): int
     {
         $endYear = date("Y-12-31", date_timestamp_get($date));
diff --git a/src/Controller/HomeController.php b/src/Controller/HomeController.php
index b97e756..f8eec4a 100644
--- a/src/Controller/HomeController.php
+++ b/src/Controller/HomeController.php
@@ -3,66 +3,62 @@
 namespace App\Controller;
 
 use App\Action\Functions;
-use App\Validation\ArrayValidation;
+use App\Requests\{
+    SortPriceRequest,
+    SearchRequest,
+    UniqElementsRequest,
+    MenuRequest
+};
 use DateTimeImmutable;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 use Symfony\Component\HttpFoundation\Response;
-use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Attribute\Route;
 
 class HomeController extends AbstractController
 {
-    private Functions $functions;
+    public function __construct(private Functions $functions) {}
 
-    public function __construct(Functions $functions)
+    #[Route('/sortPrice', name: 'sortPrice', methods: ['POST'])]
+    public function sortPrice(SortPriceRequest $request): Response
     {
-        $this->functions = $functions;
-    }
-
-    #[Route('/func1', name: 'home', methods: ['POST'])]
-    public function func1(Request $request): Response
-    {
-        $array = $request->get('arr');
-        if (!ArrayValidation::validateFunc1($array)) {
-            return new Response("Invalid array");
-        }
-        $array = $this->functions->sortPrice($array);
+        $array = $this->functions->sortPrice($request->getRequest()->toArray()['items']);
         return $this->json($array);
     }
 
-    #[Route('/func2', name: 'func2', methods: ['POST'])]
-    public function func2(Request $request): Response
+    #[Route('/search', name: 'search', methods: ['POST'])]
+    public function search(SearchRequest $request): Response
     {
-        $id = $request->query->getInt('id');
-        $array = $request->get('arr');
-        if (!ArrayValidation::validateFunc2($array)) {
-            return new Response("Invalid array");
-        }
+        $array = $request->getRequest()->toArray()['items'];
+        $id = $request->getRequest()->query->get('id');
         $result = $this->functions->search($array, $id);
         return $this->json($result);
     }
 
-    #[Route('/func3', name: 'func3', methods: ['POST'])]
-    public function home(Request $request): Response
+    #[Route('/uniqElements', name: 'uniqElements', methods: ['POST'])]
+    public function uniqElements(UniqElementsRequest $request): Response
     {
-        $array = $request->get('arr');
-        $result = $this->functions->uniqElements($array);
+        $result = $this->functions->uniqElements($request->getRequest()->toArray()['items']);
         return $this->json($result);
     }
 
-    #[Route('/func4', name: 'func4', methods: ['POST'])]
-    public function func4(Request $request): Response
+    #[Route('/prepareMenu', name: 'prepareMenu', methods: ['POST'])]
+    public function prepareMenu(MenuRequest $request): Response
     {
-        $array = $request->get('arr');
-        if (!ArrayValidation::validateFunc4($array)) {
-            return new Response("Invalid array");
-        }
-        $result = $this->functions->prepareMenu($array);
+        $result = $this->functions->prepareMenu($request->getRequest()->toArray()['items']);
         return $this->json($result);
     }
 
-    #[Route('/func5/{day}/{month}/{year}', name: 'func5')]
-    public function func5(int $day, int $month, int $year): Response
+    #[Route(
+        '/howDaysToNy/{day}/{month}/{year}',
+        name: 'howDaysToNy',
+        requirements: [
+            'day' => '(?<!-)(?<!\d)\d+',
+            'month' => '(?<!-)(?<!\d)\d+',
+            'year' => '(?<!-)(?<!\d)\d+'
+        ],
+        methods: ['GET']
+    )]
+    public function howDaysToNy(int $day, int $month, int $year): Response
     {
         $dateAsString = $year . "-" . $month . "-" . $day;
         try {
diff --git a/src/Requests/BaseRequest.php b/src/Requests/BaseRequest.php
new file mode 100644
index 0000000..9bebc7d
--- /dev/null
+++ b/src/Requests/BaseRequest.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace App\Requests;
+
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Validator\ConstraintViolation;
+use Symfony\Component\Validator\Validator\ValidatorInterface;
+
+abstract class BaseRequest
+{
+    public function __construct(protected ValidatorInterface $validator)
+    {
+        $this->populate();
+
+        if ($this->autoValidateRequest()) {
+            $this->validate();
+        }
+    }
+
+    public function validate(): void
+    {
+        $errors = $this->validator->validate($this);
+
+        $messages = ['message' => 'validation_failed', 'errors' => []];
+
+        /** @var ConstraintViolation $errors */
+        foreach ($errors as $message) {
+            $messages['errors'][] = [
+                'property' => $message->getPropertyPath(),
+                'value' => $message->getInvalidValue(),
+                'message' => $message->getMessage(),
+            ];
+        }
+
+        if (count($messages['errors']) > 0) {
+            $response = new JsonResponse($messages, 201);
+            $response->send();
+
+            exit;
+        }
+    }
+
+    public function getRequest(): Request
+    {
+        return Request::createFromGlobals();
+    }
+
+    protected function populate(): void
+    {
+        foreach ($this->getRequest()->request->all() as $property => $value) {
+            if (property_exists($this, $property)) {
+                $this->{$property} = $value;
+            }
+        }
+    }
+
+    protected function autoValidateRequest(): bool
+    {
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/Requests/MenuRequest.php b/src/Requests/MenuRequest.php
new file mode 100644
index 0000000..e012d13
--- /dev/null
+++ b/src/Requests/MenuRequest.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Requests;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class MenuRequest extends BaseRequest
+{
+    #[Assert\All([
+        new Assert\Collection([
+            'name' => new Assert\Type('string'),
+            'depth' => new Assert\Type('int'),
+        ])
+    ])]
+    public array $items;
+}
\ No newline at end of file
diff --git a/src/Requests/SearchRequest.php b/src/Requests/SearchRequest.php
new file mode 100644
index 0000000..eb03843
--- /dev/null
+++ b/src/Requests/SearchRequest.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Requests;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class SearchRequest extends BaseRequest
+{
+    #[Assert\All([
+        new Assert\Collection([
+            'id' => new Assert\Type('int'),
+            'name' => new Assert\Type('string'),
+            'age' => new Assert\Type('int'),
+        ])
+    ])]
+    public array $items;
+
+    #[Assert\Type('int')]
+    public int $id;
+}
\ No newline at end of file
diff --git a/src/Requests/SortPriceRequest.php b/src/Requests/SortPriceRequest.php
new file mode 100644
index 0000000..38bdf00
--- /dev/null
+++ b/src/Requests/SortPriceRequest.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Requests;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class SortPriceRequest extends BaseRequest
+{
+    #[Assert\All([
+        new Assert\Collection([
+            'price' => new Assert\Type('int'),
+            'count' => new Assert\Type('int')
+        ])
+    ])]
+    public array $items;
+}
\ No newline at end of file
diff --git a/src/Requests/UniqElementsRequest.php b/src/Requests/UniqElementsRequest.php
new file mode 100644
index 0000000..f53156b
--- /dev/null
+++ b/src/Requests/UniqElementsRequest.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Requests;
+
+use Symfony\Component\Validator\Constraints as Assert;
+
+class UniqElementsRequest extends BaseRequest
+{
+    #[Assert\All([
+        new Assert\Type('array'),
+        new Assert\All([
+            new Assert\Type('string'),
+        ])
+    ])]
+    public array $items;
+}
\ No newline at end of file
-- 
GitLab