From 2c11c492c47cc741d60fc329384f14e8156e4c81 Mon Sep 17 00:00:00 2001 From: jdeeline <60224142+jdeeline@users.noreply.github.com> Date: Thu, 12 Jan 2023 05:19:47 +0700 Subject: [PATCH 1/2] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлены недостающие знаки препинания, исправлены явная опечатка (все знани**е**), тавтология (уверены, но не уверены на 100 процентов). --- manuscript/6-validation.md | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/manuscript/6-validation.md b/manuscript/6-validation.md index 79bb163..5283170 100644 --- a/manuscript/6-validation.md +++ b/manuscript/6-validation.md @@ -42,7 +42,7 @@ class PostService Одной из проверок является существование нужной категории в базе данных. Давайте представим, что в будущем в модель категорий будет добавлен трейт **SoftDeletes**. Этот функционал будет помечать строки в базе данных как удаленные вместо того, чтобы физически их удалять, а также не включать строки, помеченные как удаленные в результаты запросов. -Все приложение продолжит работать, не заметив этого изменения. +Всё приложение продолжит работать, не заметив этого изменения. Кроме этой валидации. Она позволит создать статью с удаленной категорией, нарушив тем самым консистентность данных. Исправим это: @@ -63,13 +63,13 @@ $this->validate($request, [ Мы не делали никаких изменений в форме добавления статьи, и вообще во всей HTTP части приложения. Изменения касались либо бизнес-логики (архивные категории), либо логики хранения данных (Soft delete), однако менять приходится классы HTTP-запросов с их валидацией. Это ещё один пример высокой связанности (high coupling). -Не так давно мы решили вынести всю работу с базой данных в Слой Приложения, но по старой привычке все ещё лезем в базу напрямую из валидации, игнорируя все абстракции, которые строятся с помощью Eloquent или Слоя Приложения. +Не так давно мы решили вынести всю работу с базой данных в Слой Приложения, но по старой привычке всё ещё лезем в базу напрямую из валидации, игнорируя все абстракции, которые строятся с помощью Eloquent или Слоя Приложения. ![](images/application_layer.png) -Надо разделить валидацию. В валидации HTTP слоя, нам просто необходимо убедиться, что пользователь не ошибся во вводе данных: +Надо разделить валидацию. В валидации HTTP слоя нам просто необходимо убедиться, что пользователь не ошибся во вводе данных: ```php $this->validate($request, [ @@ -103,7 +103,7 @@ class PostService ``` Валидацию же, затрагивающую бизнес-логику или базу данных, необходимо проводить в более правильных местах. Теперь код работает более стабильно: валидация в контроллерах или **FormRequest** не меняется от случайных изменений в других слоях. -Метод **PostService::create** не доверяет такие важные проверки вызывающему коду и валидирует все сам. +Метод **PostService::create** не доверяет такие важные проверки вызывающему коду и валидирует всё сам. В качестве бонуса, приложение теперь имеет намного более понятные тексты ошибок. @@ -112,14 +112,14 @@ class PostService ## Два уровня валидации -В прошлом примере валидация была разделена на две части и я сказал, что метод **PostService::create** не доверяет сложную валидацию вызывающему коду, но он все ещё доверяет ему в простом: +В прошлом примере валидация была разделена на две части и я сказал, что метод **PostService::create** не доверяет сложную валидацию вызывающему коду, но он всё ещё доверяет ему в простом: ```php $post->title = $request->getTitle(); ``` -Здесь мы уверены, что заголовок у нас будет непустой, однако на 100 процентов уверенности нет. +Здесь мы исходим из того, что заголовок у нас будет непустой, однако на 100 процентов уверенности нет. Да, сейчас оно проверяется правилом 'required' при валидации, но это далеко, где-то в контроллерах или ещё дальше. -Метод **PostService::create** может быть вызван из другого кода и там эта проверка может быть забыта. +Метод **PostService::create** может быть вызван из другого кода, и там эта проверка может быть забыта. Давайте рассмотрим пример с регистрацией пользователя (он удобнее): ```php @@ -212,7 +212,7 @@ class UserService ## Валидация аннотациями Проект **Symfony** содержит отличный компонент для валидации аннотациями - **symfony/validator**. -Для того, чтобы использовать его вне Symfony нужно установить composer-пакеты **symfony/validator**, **doctrine/annotations** и **doctrine/cache** и сделать небольшую инициализацию. +Для того, чтобы использовать его вне Symfony, нужно установить composer-пакеты **symfony/validator**, **doctrine/annotations** и **doctrine/cache** и сделать небольшую инициализацию. Перепишем наш **RegisterUserDto**: ```php @@ -292,9 +292,9 @@ class UserService ``` Правила валидации описываются в специальных phpDoc-комментариях, которые называются аннотациями. Метод **ValidatorInterface::validate** возвращает список нарушений правил валидации. -Если он пуст - все хорошо. Если нет, выбрасываем исключение валидации - **ValidationException**. +Если он пуст - всё хорошо. Если нет, выбрасываем исключение валидации - **ValidationException**. Используя эту явную валидацию, в Слое Приложения можно быть уверенным в валидности данных. -Также, в качестве бонуса можно удалить валидацию в слое Web, API и т.д, поскольку все данные уже проверяются глубже. +Также, в качестве бонуса, можно удалить валидацию в слое Web, API и т.д, поскольку все данные уже проверяются глубже. Выглядит неплохо, но с этим есть некоторые проблемы. ### Проблема данных Http запроса @@ -304,23 +304,23 @@ class UserService Другой пример: одно из значений, передаваемых в Слой Приложения заполняется email-адресом текущего пользователя. Если этот email окажется пустым, то пользователь может увидеть сообщение "Формат email неверный", при том, что он даже не вводил никакого email! -Поэтому, делать валидацию пользовательского ввода в Слое Приложения - не самая лучшая идея. +Поэтому делать валидацию пользовательского ввода в Слое Приложения - не самая лучшая идея. ### Проблема сложных структур данных Представьте некий DTO создания заказа такси - **CreateTaxiOrderDto**. Это будет авиа-такси, поэтому заказы могут быть из одной страны в другую. Там будут поля **fromHouse**, **fromStreet**, **fromCity**, **fromState**, **fromCountry**, **toHouse**, **toStreet**, **toCity**,... -Огромный DTO с кучей полей, дублирующих друг-друга, зависящие друг от друга. +Огромный DTO с кучей полей, дублирующих друг-друга, зависящих друг от друга. Номер дома не имеет никакого смысла без имени улицы. Имя улицы, без города и страны. -И представьте какой будет хаос, когда наше такси станет интер-галактическим! +И представьте, какой будет хаос, когда наше такси станет интер-галактическим! ## Value objects Решение этой проблемы лежит прямо в **RegisterUserDto**. Мы не храним отдельно **$birthDay**, **$birthMonth** и **$birthYear**. Не валидируем их каждый раз. Мы просто храним объект **DateTime**! Он всегда хранит корректную дату и время. -Сравнивая, даты мы никогда не сравниваем их года, месяцы и дни. Там есть метод **diff()** для сравнений дат. -Этот класс содержит все знание о датах внутри себя. +Сравнивая даты, мы никогда не сравниваем их года, месяцы и дни. Там есть метод **diff()** для сравнений дат. +Этот класс содержит все знания о датах внутри себя. Давайте попробуем сделать что-то похожее: ```php @@ -393,7 +393,7 @@ final class RegisterUserDto ``` Да, создавать класс для каждого возможного типа вводимых данных - это не то, о чем мечтает каждый программист. Но это естественный путь декомпозиции приложения. -Вместо того, чтобы использовать строки и всегда сомневаться лежит ли в них нужное значение, эти классы позволяют всегда иметь корректные значения, как с DateTime. +Вместо того, чтобы использовать строки и всегда сомневаться, лежит ли в них нужное значение, эти классы позволяют всегда иметь корректные значения, как с DateTime. Этот шаблон называется **Объект-значение**(**Value Object** или **VO**). Метод **getEmail()** больше не возвращает какую-то строку, он возвращает **Email**, который без сомнения можно использовать везде, где нужны email-адреса. **UserService** может без страха использовать эти значения: @@ -414,12 +414,12 @@ final class UserService } ``` Да, вызовы **->value()** выглядят не очень. -Это можно решить перекрыв метод **__toString()** в классах **Email** и **UserName**, но я не уверен, что это сработает со значениями в Eloquent. +Это можно решить, перекрыв метод **__toString()** в классах **Email** и **UserName**, но я не уверен, что это сработает со значениями в Eloquent. Даже если и сработает - это будет неявной магией. Я лишнюю магию не люблю, позднее в книге мы найдем решение этой проблемы. ## Объект-значение как композиция других значений -Объекты-значения **Email** и **UserName** это просто оболочки для строк. +Объекты-значения **Email** и **UserName** - это просто оболочки для строк. Шаблон **Объект-значение** - более широкое понятие. Географическая координата может быть описана двумя float значениями: долгота и широта. Обычно, мало кому интересна долгота, без знания широты. @@ -486,7 +486,7 @@ final class City } ``` Примером того, как знание о координатах инкапсулировано в классе **GeoPoint**, является метод **getDistance** класса **City**. -Для вычисления дистанции между городами, просто используется расстояние между двумя центральными точками городов. +Для вычисления дистанции между городами просто используется расстояние между двумя центральными точками городов. Другие примеры объектов-значений: @@ -568,7 +568,7 @@ final class Email Идея удаления кода Laravel-валидации выглядит интересной. Можно удалить вызов **$this->validate()** и просто ловить исключение **InvalidArgumentException** в глобальном обработчике ошибок. -Но, как я уже говорил, данные HTTP-запроса, не всегда равны данным, передаваемым в Слой Приложения, да и исключение **InvalidArgumentException** может быть выброшено во многих других ситуациях. +Но, как я уже говорил, данные HTTP-запроса не всегда равны данным, передаваемым в Слой Приложения, да и исключение **InvalidArgumentException** может быть выброшено во многих других ситуациях. Опять может повториться ситуация, когда пользователь видит ошибки про данные, которые он не вводил. Если вы помните, PhpStorm по умолчанию имеет 3 класса непроверяемых исключений: **Error**, **RuntimeException** и **LogicException**: @@ -584,13 +584,13 @@ final class Email ## Пара слов в конце главы Вынесение логики в Слой Приложения ведет к некоторым проблемам с валидацией данных. -Мы не можем напрямую использовать объекты **FormRequest** и приходится использовать некие объекты передачи данных(DTO), пусть даже это будут простые массивы. +Мы не можем напрямую использовать объекты **FormRequest** и приходится использовать некие объекты передачи данных (DTO), пусть даже это будут простые массивы. Если Слой Приложения всегда получает ровно те данные, которые ввел пользователь, то вся валидация может быть перенесена туда и использовано решение с пакетом **symfony/validator** или другим. -Но это будет опасно и не очень удобно, если идет работа со сложными структурами данных, такими как адреса или точки координат например. +Но это будет опасно и не очень удобно, если идет работа со сложными структурами данных, такими как адреса или точки координат, например. Валидация может быть оставлена в Web, API и других частях кода, а Слой Приложения будет просто доверять переданным ему данным. По моему опыту это работает только в маленьких проектах. Большие проекты, над которыми работают команды разработчиков, постоянно будут сталкиваться с проблемами невалидных данных, которые будут вести к неправильным значениям в базе данных или просто к неправильным исключениям. Шаблон объект-значение требует некоторого дополнительного кодинга и "мышления объектами" от программистов, но это наиболее безопасный и естественный способ представлять данные, имеющие какой-то дополнительный смысл, т.е. "не просто строка, а email". -Как всегда, это выбор между краткосрочной и долгосрочной производительностью. \ No newline at end of file +Как всегда, это выбор между краткосрочной и долгосрочной производительностью. From 1ad99dbf124c0b7164e9e3ab3b54fef58118cb83 Mon Sep 17 00:00:00 2001 From: Daiyrbek Artelov Date: Mon, 23 Jan 2023 19:21:38 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=9E=D0=BF=D0=B5=D1=87=D0=B0=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20(#43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manuscript/2-di.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manuscript/2-di.md b/manuscript/2-di.md index e402c96..ea7ab6f 100644 --- a/manuscript/2-di.md +++ b/manuscript/2-di.md @@ -4,7 +4,7 @@ Вы, возможно, слышали о Принципе Единственной Ответственности (Single Responsibility Principle, SRP). Одно из его определений: «Каждый модуль, класс или функция должны иметь ответственность над единой частью функционала». -Много разработчиков упрощают его до «класс должно делать что-то одно», но это определение не самое удачное. +Много разработчиков упрощают его до «класс должен делать что-то одно», но это определение не самое удачное. Роберт Мартин предложил другое определение, где заменил слово «ответственность» на «причину для изменения»: «Класс должен иметь лишь одну причину для изменения». «Причина для изменения» более удобный термин и мы можем начать рассуждать об архитектуре используя его. Почти все шаблоны и практики имеют своей целью лучше подготовить приложение к изменениям, но приложения бывают разные, с разными требованиями и разными возможными изменениями.