i.belonogov 1 month ago
parent
commit
f8e275e88c
7 changed files with 2183 additions and 2 deletions
  1. 38 0
      .gitattributes
  2. 1 0
      .gitignore
  3. 14 0
      CHANGELOG.md
  4. 21 0
      LICENSE.md
  5. 1721 2
      README.md
  6. 118 0
      phpstorm/php-conventions-code-style.xml
  7. 270 0
      phpstorm/php-conventions-inspections.xml

+ 38 - 0
.gitattributes

@@ -0,0 +1,38 @@
+*.php text eol=lf
+*.js  text eol=lf
+*.sh  text eol=lf
+*.sql text eol=lf
+
+*.svg text eol=lf
+*.pug text eol=lf
+
+*.json text eol=lf
+
+*.yaml     text eol=lf
+*.yml      text eol=lf
+*.yml.dist text eol=lf
+
+*.xml      text eol=lf
+*.xml.dist text eol=lf
+
+*.css  text eol=lf
+*.less text eol=lf
+
+*.jade text eol=lf
+*.twig text eol=lf
+
+composer.lock text eol=lf
+
+.htaccess text eol=lf
+.bowerrc  text eol=lf
+.jshintrc text eol=lf
+
+*.j2   text eol=lf
+*.conf text eol=lf
+*.md   text eol=lf
+
+.bashrc       text eol=lf
+.bash_git     text eol=lf
+.screenrc     text eol=lf
+.bash_aliases text eol=lf
+.vimrc        text eol=lf

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+.idea

+ 14 - 0
CHANGELOG.md

@@ -0,0 +1,14 @@
+### 1.1
+
+- [#41](https://github.com/index0h/php-conventions/issues/41) Добавить ссылки на принципы программирования
+- [#38](https://github.com/index0h/php-conventions/issues/38) Добавить оглавление
+- [#32](https://github.com/index0h/php-conventions/issues/32) 7.3. Разделение тест методов
+- [#25](https://github.com/index0h/php-conventions/issues/25) 2.6. Свойства
+- [#21](https://github.com/index0h/php-conventions/issues/21) 4.3. Инструкция switch
+- [#20](https://github.com/index0h/php-conventions/issues/20) 2.2. Дублирование типов в docblock
+- [#51](https://github.com/index0h/php-conventions/issues/51) Dependency Ingection
+- [#15](https://github.com/index0h/php-conventions/issues/15) Add php7.4 support
+
+### 1.0
+
+- Оформление базового стандарта

+ 21 - 0
LICENSE.md

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Roman Levishchenko
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 1721 - 2
README.md

@@ -1,3 +1,1722 @@
-# php-code-style-conventions
+Цель данных рекомендаций — снижение сложности восприятия, поддержки, тестирования и расширения кода, написанного разными
+авторами; она достигается путём рассмотрения серии правил и ожиданий относительно написания PHP-кода.
 
-В данном репозитории рассматривается подход к написанию и оформлению PHP кода. Нижеизложенные моменты были сформированы путем анализа существующих подходов компаний и личного опыта.
+Слова «НЕОБХОДИМО» / «ДОЛЖНО» («MUST»), «НЕДОПУСТИМО» («MUST NOT»), «ТРЕБУЕТСЯ» («REQUIRED»), «НУЖНО» («SHALL»),
+«НЕ ПОЗВОЛЯЕТСЯ» («SHALL NOT»), «СЛЕДУЕТ» («SHOULD»), «НЕ СЛЕДУЕТ» («SHOULD NOT»), «РЕКОМЕНДУЕТСЯ» («RECOMMENDED»),
+«МОЖЕТ» / «ВОЗМОЖНО» («MAY») и «НЕОБЯЗАТЕЛЬНО» («OPTIONAL») в этом документе следует понимать так,
+как это описано в [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) (и его [переводе](http://rfc.com.ru/rfc2119.htm)).
+
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+
+
+- [1. Оформление](#1-%D0%BE%D1%84%D0%BE%D1%80%D0%BC%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5)
+  - [1.1. Базовый стандарт оформления кода](#11-%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D0%B9-%D1%81%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82-%D0%BE%D1%84%D0%BE%D1%80%D0%BC%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F-%D0%BA%D0%BE%D0%B4%D0%B0)
+  - [1.2. Строки](#12-%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8)
+  - [1.3. Выравнивание присвоений переменных и элементов массива](#13-%D0%B2%D1%8B%D1%80%D0%B0%D0%B2%D0%BD%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BF%D1%80%D0%B8%D1%81%D0%B2%D0%BE%D0%B5%D0%BD%D0%B8%D0%B9-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D1%85-%D0%B8-%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2-%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B0)
+  - [1.4. Массивы](#14-%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D1%8B)
+  - [1.5. Последовательность вызовов (Chaining)](#15-%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C-%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%D0%BE%D0%B2-chaining)
+  - [1.6. Выделение управляющих инструкций](#16-%D0%B2%D1%8B%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D1%8F%D1%8E%D1%89%D0%B8%D1%85-%D0%B8%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D0%B9)
+- [2. Документирование](#2-%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
+  - [2.1. Базовый стандарт для оформления документации в коде](#21-%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D1%8B%D0%B9-%D1%81%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82-%D0%B4%D0%BB%D1%8F-%D0%BE%D1%84%D0%BE%D1%80%D0%BC%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B4%D0%BE%D0%BA%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%86%D0%B8%D0%B8-%D0%B2-%D0%BA%D0%BE%D0%B4%D0%B5)
+  - [2.2. Дублирование типов в docblock](#22-%D0%B4%D1%83%D0%B1%D0%BB%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%82%D0%B8%D0%BF%D0%BE%D0%B2-%D0%B2-docblock)
+  - [2.3. Массивы в docblock](#23-%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D1%8B-%D0%B2-docblock)
+  - [2.4. Неопределенные типы аргументов и возвращаемых результатов](#24-%D0%BD%D0%B5%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D1%82%D0%B8%D0%BF%D1%8B-%D0%B0%D1%80%D0%B3%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2-%D0%B8-%D0%B2%D0%BE%D0%B7%D0%B2%D1%80%D0%B0%D1%89%D0%B0%D0%B5%D0%BC%D1%8B%D1%85-%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2)
+  - [2.5. Тип переменных](#25-%D1%82%D0%B8%D0%BF-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D1%85)
+  - [2.6. Свойства](#26-%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%B0)
+  - [2.7. Методы и функции](#27-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D1%8B-%D0%B8-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8)
+- [3. Объявление констант, свойств и методов](#3-%D0%BE%D0%B1%D1%8A%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BA%D0%BE%D0%BD%D1%81%D1%82%D0%B0%D0%BD%D1%82-%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2-%D0%B8-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2)
+  - [3.1. Последовательность объявлений констант, свойств и методов](#31-%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D1%8C-%D0%BE%D0%B1%D1%8A%D1%8F%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B9-%D0%BA%D0%BE%D0%BD%D1%81%D1%82%D0%B0%D0%BD%D1%82-%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2-%D0%B8-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2)
+  - [3.2. Именование свойств](#32-%D0%B8%D0%BC%D0%B5%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2)
+  - [3.3. Разделение свойств](#33-%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2)
+  - [3.4. Модификаторы доступа для свойств](#34-%D0%BC%D0%BE%D0%B4%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D0%BE%D1%80%D1%8B-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0-%D0%B4%D0%BB%D1%8F-%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2)
+  - [3.5 Типы свойств](#35-%D1%82%D0%B8%D0%BF%D1%8B-%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2)
+  - [3.6. Именование методов](#36-%D0%B8%D0%BC%D0%B5%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2)
+  - [3.7. Разделение методов](#37-%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2)
+  - [3.8. Модификаторы доступа для методов](#38-%D0%BC%D0%BE%D0%B4%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82%D0%BE%D1%80%D1%8B-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF%D0%B0-%D0%B4%D0%BB%D1%8F-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2)
+  - [3.9. Порядок аргументов в методе](#39-%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D0%BA-%D0%B0%D1%80%D0%B3%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2-%D0%B2-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%B5)
+  - [3.10. Массив в виде аргумента](#310-%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2-%D0%B2-%D0%B2%D0%B8%D0%B4%D0%B5-%D0%B0%D1%80%D0%B3%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%B0)
+- [4. Безопасность](#4-%D0%B1%D0%B5%D0%B7%D0%BE%D0%BF%D0%B0%D1%81%D0%BD%D0%BE%D1%81%D1%82%D1%8C)
+  - [4.1. Неявные приведения типов](#41-%D0%BD%D0%B5%D1%8F%D0%B2%D0%BD%D1%8B%D0%B5-%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F-%D1%82%D0%B8%D0%BF%D0%BE%D0%B2)
+  - [4.2. Сравнения с преобразованием типов](#42-%D1%81%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F-%D1%81-%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%D0%BC-%D1%82%D0%B8%D0%BF%D0%BE%D0%B2)
+  - [4.3. Инструкция switch](#43-%D0%B8%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D1%8F-switch)
+  - [4.4. Присвоения в условных операциях](#44-%D0%BF%D1%80%D0%B8%D1%81%D0%B2%D0%BE%D0%B5%D0%BD%D0%B8%D1%8F-%D0%B2-%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D1%8B%D1%85-%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F%D1%85)
+  - [4.5. Ошибки](#45-%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B8)
+  - [4.6. Оператор управления ошибками @](#46-%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80-%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F-%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B0%D0%BC%D0%B8-)
+  - [4.7. goto](#47-goto)
+  - [4.8. eval](#48-eval)
+  - [4.9. Глобальные переменные и global](#49-%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D0%B8-global)
+  - [4.10. Статические свойства](#410-%D1%81%D1%82%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5-%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%B0)
+  - [4.11. Суперглобальные переменные](#411-%D1%81%D1%83%D0%BF%D0%B5%D1%80%D0%B3%D0%BB%D0%BE%D0%B1%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5)
+  - [4.12. Динамическая подстановка имен](#412-%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F-%D0%BF%D0%BE%D0%B4%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-%D0%B8%D0%BC%D0%B5%D0%BD)
+  - [4.13. Магические методы](#413-%D0%BC%D0%B0%D0%B3%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D1%8B)
+  - [4.14. Валидация аргументов](#414-%D0%B2%D0%B0%D0%BB%D0%B8%D0%B4%D0%B0%D1%86%D0%B8%D1%8F-%D0%B0%D1%80%D0%B3%D1%83%D0%BC%D0%B5%D0%BD%D1%82%D0%BE%D0%B2)
+  - [4.15. \DateTime](#415-%5Cdatetime)
+  - [4.16. Обработка часовых поясов](#416-%D0%BE%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0-%D1%87%D0%B0%D1%81%D0%BE%D0%B2%D1%8B%D1%85-%D0%BF%D0%BE%D1%8F%D1%81%D0%BE%D0%B2)
+  - [4.17. SQL](#417-sql)
+- [5. Принципы программирования](#5-%D0%BF%D1%80%D0%B8%D0%BD%D1%86%D0%B8%D0%BF%D1%8B-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
+  - [5.1. Здравый Смысл](#51-%D0%B7%D0%B4%D1%80%D0%B0%D0%B2%D1%8B%D0%B9-%D1%81%D0%BC%D1%8B%D1%81%D0%BB)
+  - [5.2. YAGNI](#52-yagni)
+  - [5.3. SOLID](#53-solid)
+  - [5.4. DRY](#54-dry)
+  - [5.5. KISS](#55-kiss)
+  - [5.6. TMTOWTDI](#56-tmtowtdi)
+  - [5.7. GRASP](#57-grasp)
+- [6. Антишаблоны проектирования](#6-%D0%B0%D0%BD%D1%82%D0%B8%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D1%8B-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
+  - [6.1. ActiveRecord](#61-activerecord)
+  - [6.2. Singleton](#62-singleton)
+- [7. Тестирование](#7-%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)
+  - [7.1. Покрытие кода](#71-%D0%BF%D0%BE%D0%BA%D1%80%D1%8B%D1%82%D0%B8%D0%B5-%D0%BA%D0%BE%D0%B4%D0%B0)
+  - [7.2. Стратегия тестирования](#72-%D1%81%D1%82%D1%80%D0%B0%D1%82%D0%B5%D0%B3%D0%B8%D1%8F-%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
+  - [7.3. Разделение тест методов](#73-%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D1%82%D0%B5%D1%81%D1%82-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2)
+  - [7.4. Тестируемый объект](#74-%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D1%83%D0%B5%D0%BC%D1%8B%D0%B9-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82)
+  - [7.5. Проверка результатов](#75-%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0-%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2)
+- [8. PHPUnit](#8-phpunit)
+  - [8.1. Именование тестовых классов](#81-%D0%B8%D0%BC%D0%B5%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%82%D0%B5%D1%81%D1%82%D0%BE%D0%B2%D1%8B%D1%85-%D0%BA%D0%BB%D0%B0%D1%81%D1%81%D0%BE%D0%B2)
+  - [8.1. Именование тест методов](#81-%D0%B8%D0%BC%D0%B5%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D1%82%D0%B5%D1%81%D1%82-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2)
+  - [8.2. Структура теста](#82-%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0-%D1%82%D0%B5%D1%81%D1%82%D0%B0)
+  - [8.3. Переменные, используемые в тесте](#83-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D1%83%D0%B5%D0%BC%D1%8B%D0%B5-%D0%B2-%D1%82%D0%B5%D1%81%D1%82%D0%B5)
+  - [8.4. Mock-объекты](#84-mock-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D1%8B)
+  - [8.5. Инкременты вызовов mock-объектов](#85-%D0%B8%D0%BD%D0%BA%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D1%8B-%D0%B2%D1%8B%D0%B7%D0%BE%D0%B2%D0%BE%D0%B2-mock-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BE%D0%B2)
+  - [8.6. Поведение методов mock-объектов](#86-%D0%BF%D0%BE%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2-mock-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BE%D0%B2)
+  - [8.7 Порядок утверждений для одного значения](#87-%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BE%D0%BA-%D1%83%D1%82%D0%B2%D0%B5%D1%80%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B9-%D0%B4%D0%BB%D1%8F-%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D1%8F)
+  - [8.8. Проверка утверждений на основании результатов собственных проверок](#88-%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0-%D1%83%D1%82%D0%B2%D0%B5%D1%80%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B9-%D0%BD%D0%B0-%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B8-%D1%80%D0%B5%D0%B7%D1%83%D0%BB%D1%8C%D1%82%D0%B0%D1%82%D0%BE%D0%B2-%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85-%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BE%D0%BA)
+  - [8.9. Проверки значений на основании `TestCase::callback`](#89-%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B8-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B9-%D0%BD%D0%B0-%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B8-testcasecallback)
+  - [8.10. Проверка утверждений для числовых значений](#810-%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0-%D1%83%D1%82%D0%B2%D0%B5%D1%80%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B9-%D0%B4%D0%BB%D1%8F-%D1%87%D0%B8%D1%81%D0%BB%D0%BE%D0%B2%D1%8B%D1%85-%D0%B7%D0%BD%D0%B0%D1%87%D0%B5%D0%BD%D0%B8%D0%B9)
+  - [8.11. Проверка утверждений для \DateTimeImmutable](#811-%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D0%B0-%D1%83%D1%82%D0%B2%D0%B5%D1%80%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B9-%D0%B4%D0%BB%D1%8F-%5Cdatetimeimmutable)
+- [9. IDE](#9-ide)
+  - [9.1. Inspections и Code Style](#91-inspections-%D0%B8-code-style)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+## 1. Оформление
+
+### 1.1. Базовый стандарт оформления кода
+
+Код ДОЛЖЕН быть оформлен согласно всем правилам, указанным в стандарте [PSR-12](https://www.php-fig.org/psr/psr-12/).
+
+### 1.2. Строки
+
+Жесткое ограничение строки ДОЛЖНО составлять 120 символов.
+В случае превышения этого ограничения автоматические системы проверки стиля ДОЛЖНЫ считать это ошибочной ситуацией,
+для таких ситуаций НЕОБХОДИМО явно отключать проверку стиля с помощью аннотаций:
+
+* `// @codingStandardsIgnoreStart`
+* `// @codingStandardsIgnoreStop`
+
+> Это требование дополняет [PSR-12 2.3. Lines](https://www.php-fig.org/psr/psr-12/#23-lines).
+
+```php
+// @codingStandardsIgnoreStart
+use VendorWithVerlyLongName\ProjectrWithVerlyLongName\ServicesWithVerlyLongName\ServiceFolderWithVerlyLongName\ClassWithVerlyLongName;
+// @codingStandardsIgnoreStop
+```
+
+### 1.3. Выравнивание присвоений переменных и элементов массива
+
+Блок присвоений ДОЛЖЕН быть выровнен по самому длинному присвоению в блоке.
+Если операция присвоения превышает максимальную длину строки НЕОБХОДИМО:
+
+ * или начать новый блок с помощью пустой строки;
+ * или выражение должно быть перенесено на новую строку с отступом в 4 пробела,
+   при этом оператор присвоения ДОЛЖЕН остаться на той же строке, что и переменная.
+
+```php
+// Правильно
+$varName      = 'varName';
+$variableName = 'variableName';
+```
+
+```php
+// Неправильно
+$varName = 'varName';
+$variableName = 'variableName';
+```
+
+```php
+// Правильно
+$varName                            = 'varName';
+$secondVariableWithVeryLongNameHere =
+    '123456790123456790123456790123456790123456790123456790123456790123456790123456790';
+```
+
+```php
+// Неправильно
+$varName                            = 'varName';
+$secondVariableWithVeryLongNameHere
+    = '123456790123456790123456790123456790123456790123456790123456790123456790123456790';
+```
+
+```php
+// Правильно
+$firstVariableWithVeryLongNameHere = 'varName';
+
+$variableName = '123456790123456790123456790123456790123456790123456790123456790123456790123456790';
+```
+
+```php
+// Правильно
+[
+    'elementName'     => 'elementName',
+    'longNameElement' => 'longNameElement',
+]
+```
+
+```php
+// Неправильно
+[
+    'elementName' => 'elementName',
+    'longNameElement' => 'longNameElement',
+]
+```
+
+```php
+// Правильно
+[
+    'elementName'                       => 'elementName',
+    'secondElementWithVeryLongNameHere' =>
+        '123456790123456790123456790123456790123456790123456790123456790123456790123456790',
+]
+```
+
+```php
+// Неправильно
+[
+    'elementName'                       => 'elementName',
+    'secondElementWithVeryLongNameHere'
+        => '123456790123456790123456790123456790123456790123456790123456790123456790123456790',
+]
+```
+
+```php
+// Правильно
+[
+    'firstElementWithVeryLongNameHere' => 'elementName',
+
+    'elementName' => '123456790123456790123456790123456790123456790123456790123456790123456790123456790',
+]
+```
+
+### 1.4. Массивы
+
+При объявлении многострочного массива в конце последнего объявления ДОЛЖНА ставиться запятая,
+для однострочного массива запятую ставить НЕДОПУСТИМО.
+
+```php
+// Правильно
+[
+    'firstElement'  => 'firstElement',
+    'secondElement' => 'secondElement',
+]
+```
+
+```php
+// Неправильно
+[
+    'firstElement'  => 'firstElement',
+    'secondElement' => 'secondElement'
+]
+```
+
+```php
+// Правильно
+['firstElement' => 'firstElement', 'secondElement' => 'secondElement']
+```
+
+```php
+// Неправильно
+['firstElement' => 'firstElement', 'secondElement' => 'secondElement',]
+```
+
+
+### 1.5. Последовательность вызовов (Chaining)
+
+Каждый элемент вызова для последовательностей, состоящих из трех и более элементов ДОЛЖЕН находиться на новой строке.
+В случае превышения максимальной длины строки каждый элемент последовательности вызовов
+ДОЛЖЕН находиться на новой строке.
+
+```php
+// Правильно
+$this->firstMethod();
+```
+
+```php
+// Неправильно
+$this
+    ->firstMethod();
+```
+
+```php
+// Правильно
+$this
+    ->firstMethod()
+    ->secondMethod();
+```
+
+```php
+// Неправильно
+$this->firstMethod()->secondMethod();
+```
+
+```php
+// Неправильно
+$this
+    ->firstMethod()->secondMethod();
+```
+
+```php
+// Правильно
+$this
+    ->firstMethod()
+    ->thirdMethod(
+        $firstArgument,
+        $secondArgument,
+        $thirdArgument,
+        $fourthArgument,
+        $fifthArgument,
+        $sixArgument
+    );
+```
+
+```php
+// Неправильно
+$this->firstMethod()->thirdMethod(
+    $firstArgument,
+    $secondArgument,
+    $thirdArgument,
+    $fourthArgument,
+    $fifthArgument,
+    $sixArgument
+);
+```
+
+
+### 1.6. Выделение управляющих инструкций
+
+Управляющие инструкции: `if`, `for`, `foreach`, `while`, `do-while`, `switch`, `break`, `continue`, `return`
+ДОЛЖНЫ отделяться от кода того же уровня вложенности одной пустой строкой.
+
+```php
+// Правильно
+$count = 5;
+
+if ($count === 5) {
+// ...
+}
+
+// ...
+```
+
+```php
+// Неправильно
+$count = 5; // Отсутствует перевод строки
+if ($count === 5) {
+// ...
+}
+$length = 12; // Отсутствует перевод строки
+// ...
+```
+
+```php
+// Правильно
+{
+    if ($count === 5) {
+    // ...
+    }
+}
+```
+
+```php
+// Неправильно
+{
+
+    // Лишняя пустая строка
+    if ($count === 5) {
+    // ...
+    }
+    // Лишняя пустая строка
+}
+```
+
+## 2. Документирование
+
+### 2.1. Базовый стандарт для оформления документации в коде
+
+Код ДОЛЖЕН быть оформлен согласно правилам, указанным в стандарте
+[PSR-19](https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc-tags.md).
+
+### 2.2. Дублирование типов в docblock
+
+Указание типов аргументов с помощью `@param` и `@return`, дублирующее сигнатуру метода НЕДОПУСТИМО, кроме случаев:
+
+* Наличия комментария к параметру, или результату.
+* Аннотации используются сторонними средствами: psalm, phan, phpstan, и т.д.
+
+```php
+// Правильно
+public function incrementProductPriceByName(string $productName, float $price): bool
+{
+// ...
+```
+
+```php
+// Неправильно
+/**
+ * @param string $productName
+ * @param float  $price
+ * @return bool
+ */
+public function incrementProductPriceByName(string $productName, float $price): bool
+{
+// ...
+```
+
+```php
+// Правильно
+/** 
+ * @param array $parsedUrl
+ * @phan-param array{scheme:string,host:string,path:string} $parsedUrl
+ * @psalm-param array{scheme:string,host:string,path:string} $parsedUrl  
+*/
+public function showUrl(string $label, array $parsedUrl, string $host): string
+{
+```
+
+Указание типов свойств с помощью `@var`, дублирующее тип свойства НЕДОПУСТИМО, кроме случаев наличия комментария к
+свойству (php 7.4+).
+
+```php
+// Правильно (< php 7.4)
+/** @var string */
+private $productName;
+```
+
+```php
+// Неправильно (< php 7.4)
+private $productName;
+```
+
+```php
+// Правильно (php 7.4+)
+private ?string $productName;
+```
+
+```php
+// Правильно (php 7.4+)
+/** @var string|null Contains product name */
+private ?string $productName;
+```
+
+```php
+// Неправильно (php 7.4+)
+/** @var string|null */
+private ?string $productName;
+```
+
+### 2.3. Массивы в docblock
+
+Типы элементов массивов РЕКОМЕНДУЕТСЯ уточнять в docblock.
+
+```php
+// Правильно
+/**
+ * @param string[] $productNames
+ */
+public function incrementProductPricesByNames(array $productNames, float $price): bool
+{
+// ...
+```
+
+```php
+// Неправильно
+/**
+ * @param array $productNames
+ */
+public function incrementProductPricesByNames(array $productNames, float $price): bool
+{
+// ...
+```
+
+### 2.4. Неопределенные типы аргументов и возвращаемых результатов
+
+В случае, если аргумент (или возвращаемый результат) метода может быть разных типов НЕОБХОДИМО перечислить все
+допустимые типы в docblock.
+
+```php
+// Правильно
+/**
+ * @param string|int $stringOrIntArgument
+ * @return float|string|object
+ */
+public function mixedMethod($stringOrIntArgument)
+{
+// ...
+```
+
+```php
+// Неправильно
+/**
+ * @param mixed $stringOrIntArgument
+ * @return mixed
+ */
+public function mixedMethod($stringOrIntArgument)
+{
+// ...
+```
+
+### 2.5. Тип переменных
+
+Если ожидаемый тип переменной явно не определен НЕОБХОДИМО определить его с помощью однострочного docblock комментария.
+Так же необходимо указывать ожидаемый тип, если IDE не может его определить, или определяет не корректно.
+
+```php
+$rows = [
+    [
+        'id'        => 1,
+        'createdAt' => new \DateTimeImmutable(),
+    ],
+    [
+        'id'        => 2,
+        'createdAt' => new \DateTimeImmutable(),
+    ],
+    // ...
+];
+
+foreach ($rows as $row) {
+    /** @var int $id */
+    $id = $row['id'];
+    /** @var \DateTimeImmutable $createdAt */
+    $createdAt = $row['createdAt'];
+    // ...
+}
+```
+
+### 2.6. Свойства
+
+Свойства класса ДОЛЖНЫ содержать либо декларацию типа, либо docblock для всех возможных типов значений, которое они
+могут содержать.
+ДОПУСТИМО указывать декларацию типа и docblock только в случаях, когда dockblock уточняет декларацию, либо при наличии
+комментария.
+Если docblock МОЖЕТ быть описан одной строкой — ДОЛЖЕН использоваться однострочный docblock.
+
+```php
+/** @var string[]|null */
+private $names;
+
+/** @var int|null */
+private $count;
+```
+
+```php
+/** @var string[]|null */
+private ?array $names;
+
+private ?int $count;
+```
+
+### 2.7. Методы и функции
+
+Docblock для методов и функций ДОЛЖНЫ быть многострочными.
+
+```php
+// Неправильно
+/** @param string[] $userNames */
+public function saveUserNames(array $userNames): void
+```
+
+```php
+// Правильно
+/**
+ * @param string[] $userNames
+ */
+public function saveUserNames(array $userNames): void
+```
+
+## 3. Объявление констант, свойств и методов
+
+### 3.1. Последовательность объявлений констант, свойств и методов
+
+В классе ДОЛЖНА соблюдаться последовательность объявлений элементов согласно следующему списку:
+
+1. Публичные константы.
+2. Защищенные константы.
+3. Приватные константы.
+4. Публичные свойства.
+5. Защищенные свойства.
+6. Приватные свойства.
+7. __construct.
+8. __destruct.
+9. __clone.
+10. __invoke.
+11. __toString.
+12. Публичные методы.
+13. Защищенные методы.
+14. Приватные методы.
+
+### 3.2. Именование свойств
+
+Названия свойств ДОЛЖНЫ описывать предназначение данных, которые они хранят.
+
+```php
+// Правильно
+/** @var string[] */
+private $userNames;
+```
+
+```php
+// Неправильно
+/** @var string[] */
+private $data;
+```
+
+### 3.3. Разделение свойств
+
+Каждое свойство ДОЛЖНО отделяться от других свойств, констант и методов одной пустой строкой.
+Если свойство объявляется, как первый элемент класса — пустая строка перед ним НЕДОПУСТИМА.
+Если свойство объявляется, как последний элемент класса — пустая строка после него НЕДОПУСТИМА.
+
+```php
+// Правильно
+/** @var string[] */
+private $userNames;
+
+/** @var int[] */
+private $userIds;
+```
+
+```php
+// Неправильно
+/** @var string[] */
+private $userNames;
+/** @var int[] */
+private $userIds;
+```
+
+```php
+// Правильно
+{
+    /** @var string[] */
+    private $userNames;
+```
+
+```php
+// Неправильно
+{
+
+    /** @var string[] */
+    private $userNames;
+```
+
+```php
+// Правильно
+    /** @var string[] */
+    private $userNames;
+}
+```
+
+```php
+// Неправильно
+    /** @var string[] */
+    private $userNames;
+
+}
+```
+
+### 3.4. Модификаторы доступа для свойств
+
+Для модификаторов доступа к свойствам ДОЛЖНЫ выполняться следующие правила:
+
+* `private` - СЛЕДУЕТ использовать по умолчанию;
+* `protected` - СЛЕДУЕТ использоваться только для случаев доступа из дочерних классов;
+* `public` - использование НЕДОПУСТИМО;
+* `static` - использование НЕДОПУСТИМО.
+
+### 3.5 Типы свойств
+
+Свойства класса ДОЛЖНЫ содержать либо декларацию типа, либо docblock для всех возможных типов значений, которое они
+могут содержать.
+
+### 3.6. Именование методов
+
+Названия методов ДОЛЖНЫ описывать предназначение их использования внешним кодом, а не детали реализации.
+
+```php
+// Правильно
+public function findUserById(int $id): ?User
+```
+
+```php
+// Неправильно
+public function find(int $id): ?User
+```
+
+### 3.7. Разделение методов
+
+Каждый метод ДОЛЖЕН отделяться от других методов, свойств и констант одной пустой строкой.
+Если метод объявляется, как первый элемент класса — пустая строка перед ним НЕДОПУСТИМА.
+Если метод объявляется, как последний элемент класса — пустая строка после него НЕДОПУСТИМА.
+
+```php
+// Правильно
+public function findUserById(int $id): ?User
+// ...
+}
+
+public function findUserByName(string $name): ?User
+// ...
+}
+```
+
+```php
+// Неправильно
+public function findUserById(int $id): ?User
+// ...
+}
+public function findUserByName(string $name): ?User
+// ...
+}
+```
+
+```php
+// Правильно
+{
+    public function findUserById(int $id): ?User
+```
+
+```php
+// Неправильно
+{
+
+    public function findUserById(int $id): ?User
+```
+
+```php
+// Правильно
+    public function findUserById(int $id): ?User
+}
+```
+
+```php
+// Неправильно
+    public function findUserById(int $id): ?User
+
+}
+```
+
+### 3.8. Модификаторы доступа для методов
+
+Для модификаторов доступа к свойствам ДОЛЖНЫ выполняться следующие правила:
+
+* `private` - СЛЕДУЕТ использовать для методов, предназначенных для использования внутри класса;
+* `protected` - СЛЕДУЕТ использоваться только для случаев доступа из дочерних классов;
+* `public` - СЛЕДУЕТ использовать для методов, которые предназначены для использования из вне;
+* `static` - использование НЕДОПУСТИМО.
+
+### 3.9. Порядок аргументов в методе
+
+Аргументы метода ДОЛЖНЫ объявляться в следующей последовательности:
+
+1. Типизированные аргументы.
+2. Nullable-аргументы.
+3. Опциональные аргументы.
+4. Аргумент с `...`. (php 7.4+)
+
+```php
+public function firstExample(string $first, ?int $second, bool $third = false, float ...$fourth): string
+// ...
+public function secondExample(?int $second, bool $third = false, float ...$fourth): string
+// ...
+public function thirdExample(bool $third = false, float ...$fourth): string
+```
+
+### 3.10. Массив в виде аргумента
+
+Для методов, содержащих один и более аргументов с типом массив РЕКОМЕНДУЕТСЯ указывать хотя бы один такой аргумент
+с помощью оператора `...`.
+
+```php
+// Правильно
+public function concatStrings(string ...$parts): string
+```
+
+```php
+// Неправильно
+/**
+ * @param string $parts
+ */
+public function concatStrings(array $parts): string
+```
+
+## 4. Безопасность
+
+### 4.1. Неявные приведения типов
+
+Неявное приведение типов НЕДОПУСТИМО.
+
+> Неявное приведение типов — один из наиболее распространенных источников ошибок.
+> Проблемы, возникающие при неявном приведении типов сложно отслеживать,
+> так же они могут приводить к непредсказуемым последствиям.
+
+```php
+echo 5 + '5abc5';
+// 10
+```
+
+### 4.2. Сравнения с преобразованием типов
+
+Сравнения с преобразованием типов `==` и `!=` НЕДОПУСТИМЫ.
+Вместо этого НЕОБХОДИМО использовать тождественные сравнения: `===` и `!==`.
+
+> Проблемы тут те же, что и при неявном приведении типов.
+
+```php
+if ('abc' == 0) {
+    echo 'wat';
+}
+
+// wat
+```
+
+### 4.3. Инструкция switch
+
+Использовать инструкцию `switch` НЕОБХОДИМО с гарантией корректности типов каждого проверяемого выражения.
+
+> Инструкция `switch` при выполнении проверок `case` использует сравнения с приведением типов.
+> Это может привести к тем же проблемам, что и неявное приведение типов.
+
+```php
+switch ('abc') {
+    case 0:
+        echo 'wat';
+
+        break;
+}
+
+// wat
+```
+
+### 4.4. Присвоения в условных операциях
+
+Присвоение в условиях инструкций `if`, `while`, `do-while` НЕДОПУСТИМО.
+
+> В большом количестве учебной литературы используются конструкции вида `while ($row = ...)` или `if ($row = ...)`.
+> Выражения в скобках неявно приводятся к `bool`, что может привести к неожиданным последствиям.
+
+```php
+$rows = [0, null, ''];
+
+while ($row = next($rows)) {
+    printf("\$row = %s\n", var_dump($row, true));
+}
+
+// Ничего не выведет
+```
+
+### 4.5. Ошибки
+
+Создание ошибок с помощью `trigger_error` НЕДОПУСТИМО, вместо этого ДОЛЖНЫ использоваться исключения.
+
+> Ошибки могут быть перехвачены только глобально, с помощью `set_error_handler`.
+> Это значит, что контекст выполнения будет потерян.
+> Так же ошибки не содержат stack trace, в отличие от исключений.
+
+### 4.6. Оператор управления ошибками @
+
+Оператор `@` ДОЛЖЕН быть использован для выражений, которые могут бросить ошибку,
+для остальных ситуаций его использование НЕДОПУСТИМО.
+В случае подавления ошибки ДОЛЖНО быть брошено исключение с описанием причин возникновения ошибки.
+
+```php
+// Без @ будет ошибка:
+// Warning: fopen(path/to/not/exists/file): failed to open stream: No such file or directory
+$file = @fopen('path/to/not/exists/file', 'r');
+
+if ($file === false) {
+    throw new \RuntimeException('Could not open file: "path/to/not/exists/file"');
+}
+```
+
+### 4.7. goto
+
+Использование инструкции `goto` НЕДОПУСТИМО.
+
+> Оператор `goto` используется для перехода в другую часть программы, чем усложняет чтение и понимание кода.
+
+### 4.8. eval
+
+Использование инструкции `eval` НЕДОПУСТИМО.
+
+> Для безопасного выполнения `eval` необходимо выполнить очень детальный анализ кода, который будет выполняться.
+> Сложность требуемых проверок растет экспоненциально с операциями, ожидаемыми для выполнения в `eval`.
+
+### 4.9. Глобальные переменные и global
+
+Использование инструкции `global` НЕДОПУСТИМО.
+
+> Глобальные переменные являются неявными аргументами функции, или метода, не гарантирующими ни тип, ни значение,
+> ни даже своего существования.
+
+### 4.10. Статические свойства
+
+Использование статических свойств НЕДОПУСТИМО.
+
+> Статические свойства, по аналогии с глобальными переменным являются неявными аргументами функции, или метода, не
+> гарантирующими ни тип, ни корректного состояния.
+
+### 4.11. Суперглобальные переменные
+
+Использование суперглобальных переменных ДОЛЖНО быть сведено к минимуму.
+Данные из суперглобальных переменных РЕКОМЕНДУЕТСЯ получать на этапе инициализации.
+
+### 4.12. Динамическая подстановка имен
+
+Динамическая подстановка имен переменных, свойств, функций и методов НЕДОПУСТИМА.
+
+> Динамическая подстановка имен сильно усложняет чтение и отладку кода потому, что конечные имена определяются только
+> в рантайме.
+
+```php
+// Неправильно
+$this->{$methodName}($argument);
+```
+
+### 4.13. Магические методы
+
+Использование следующих магических методов НЕДОПУСТИМО:
+
+* `__call`
+* `__callStatic`
+* `__get`
+* `__set`
+
+> Данные методы усложняют чтение и понимание кода, как следствие его поддержку.
+
+### 4.14. Валидация аргументов
+
+Каждый аргумент публичного метода, защищенного метода, или функции ДОЛЖЕН быть проверен на корректность типа и граничные
+значения.
+Каждый аргумент приватного метода ДОЛЖЕН быть проверен на корректность типа, проверять граничные значения РЕКОМЕНДУЕТСЯ.
+Если аргумент не валиден — штатное выполнение метода (функции) невозможно, по этой причине ДОЛЖНО быть брошено
+исключение.
+
+### 4.15. \DateTime
+
+Вместо `\DateTime` НЕОБХОДИМО использовать `\DateTimeImmutable`.
+
+> Так как в PHP объекты передаются по ссылке изменение объекта `\DateTime` в одной части кода влечет за собой изменение
+> по всему рантайму, что может привести к непредсказуемым последствиям. Что бы исключить целый класс ошибок, связанных
+> с не явным изменением даты/времени из внешнего кода НЕОБХОДИМО использовать `\DateTimeImmutable`.
+
+```php
+$externalServiceGenerateExpiredAt = function (\DateTime $createdAt): \DateTime {
+    return $createdAt->modify('3 days');
+};
+
+$createdAt = new \DateTime('2019-01-01');
+$expiredAt = $externalServiceGenerateExpiredAt($createdAt);
+
+printf("createdAt: %s, expiredAt: %s", $createdAt->format('Y-m-d'), $expiredAt->format('Y-m-d'));
+
+// createdAt: 2019-01-04, expiredAt: 2019-01-04
+```
+
+### 4.16. Обработка часовых поясов
+
+Для задач хранения и обработки времени НЕОБХОДИМО использовать часовой пояс `UTC`.
+Для задач связанных с выводом МОЖНО использовать произвольный часовой пояс.
+
+> В случае хранения или обработки времени со смещением по часовому поясу есть большая вероятность возникновения ошибок
+> связанных с несоответствием часовых поясов.
+
+### 4.17. SQL
+
+Для подстановки параметров в SQL запросы НЕОБХОДИМО использовать
+[псевдопеременные](https://www.php.net/manual/ru/pdo.prepared-statements.php).
+
+> Подстановка параметров с помощью конкатенации ведет к целому классу проблем безопасности: sql-инъекции.
+
+## 5. Принципы программирования
+
+### 5.1. Здравый Смысл
+
+Принцип Здравого Смысла разрешает отмену любого правила данных рекомендаций, в случае, когда правило приводит к
+чрезмерному усложнению поддержки кода.
+Этот принцип МОЖНО использовать, но очень осторожно.
+
+### 5.2. YAGNI
+
+РЕКОМЕНДУЕТСЯ следовать принципу [YAGNI](https://ru.wikipedia.org/wiki/YAGNI).
+
+### 5.3. SOLID
+
+Код ДОЛЖЕН следовать принципам [SOLID](https://ru.wikipedia.org/wiki/SOLID_(объектно-ориентированное_программирование)).
+
+### 5.4. DRY
+
+Принципу [DRY](https://ru.wikipedia.org/wiki/Don't_repeat_yourself) СЛЕДУЕТ придерживаться только в случае, когда он не
+противоречит SOLID и Здравому смыслу.
+
+> Примеры, когда не стоит следовать принципу DRY:
+>
+> * У вас есть две разных сущности, отвечающие разным доменам с некой общей функциональностью.
+>   В такой ситуации не стоит наследовать сущности одну от другой, или общую функциональность выносить в абстрактный
+>   класс, или трейт. Дело в том, что общая функциональность является общей только в текущий момент времени, в будущем
+>   же она может измениться в каждом домене по своему. Фактически вам придется в общей функциональности реализовывать
+>   ее разделение, в зависимости от домена.
+>
+> * В тестах DRY может привести к ложно позитивным и ложно негативным ошибкам.
+
+### 5.5. KISS
+
+Принципу [KISS](https://ru.wikipedia.org/wiki/KISS_(принцип)) СЛЕДУЕТ придерживаться только в случае, когда он не
+противоречит другим правилам данных рекомендаций.
+
+> Примеры, когда не стоит следовать принципу KISS:
+>
+> * Класс должен быть "не большого размера". Если придерживаться этого правила - в результате у вы усложните
+>   взаимодействие между вашими объектами, что может привести к существенному увеличению сложности в поддержке кода.
+>   По этой причине класс должен полностью описывать объект реального мира, которому он соответствует.
+>
+> * Методы должны быть "не большого размера". Здесь проблемы те же, что и у классов. Разделяя метод на множество
+>   маленьких вы расширяете интерфейс класса, что в будущем может привести к излишней связанности, как с данным классом,
+>   так и с его наследниками.
+
+### 5.6. TMTOWTDI
+
+Принцип [TMTOWTDI](https://ru.wikipedia.org/wiki/TMTOWTDI) НЕ РЕКОМЕНДУЕТСЯ использовать.
+
+> Множество способов реализации одного и того же алгоритма ведет к тому, что правки алгоритма придется выполнять в
+> каждой реализации, что в свою очередь усложняет поддержку и увеличивает вероятность ошибок.
+
+### 5.7. GRASP
+
+РЕКОМЕНДУЕТСЯ следовать принципам [GRASP](https://ru.wikipedia.org/wiki/GRASP).
+
+## 6. Антишаблоны проектирования
+
+### 6.1. ActiveRecord
+
+`ActiveRecord` РЕКОМЕНДУЕТСЯ считать антишаблоном и не использовать его. Вместо этого РЕКОМЕНДУЕТСЯ использовать
+шаблон `Repository`.
+
+> ActiveRecord объединяет сущности, представляющие домен вместе с инфраструктурой, в виде логики работы с базой данных.
+> Такое поведение нарушает принципы SRP и ISP из SOLID, и приводит к следующим последствиям.
+>
+>  * Усложнение unit тестирования, так как требует менять поведение подключения к базе данных.
+>  * Потеря контроля над тем, у каких модулей использующих сущность должен быть доступ к базе данных, а у каких нет.
+>  * Увеличение количества зависимостей от модели и, как следствие, изменяемого объема кода, при правках модели.
+
+### 6.2. Singleton
+
+`Singleton` РЕКОМЕНДУЕТСЯ считать антишаблоном и не использовать его. Вместо этого РЕКОМЕНДУЕТСЯ использовать
+`Dependency Injection`.
+
+> Проблема `Singleton` заключается в том, что состояние объекта, как правило, хранится в статическом свойстве, и
+> является не явным аргументом метода, или функции, см. пункт `4.10.` данных рекомендаций.
+
+## 7. Тестирование
+
+### 7.1. Покрытие кода
+
+Каждый метод (функция) ДОЛЖНЫ быть покрыты тестами для всех возможных вариантов выполнения метода (функции).
+
+```php
+// Для данного метода ДОЛЖНО быть 3 теста.
+// 1. Число $number кратно $divider, что бы проверить корректность преобразование типа.
+// Например `divide(4, 2);`.
+// 2. Число $number не кратно $divider. Например `divide(1, 2);`.
+// 3. Число $divider равно 0. Например `divide(3, 0);`.
+public function divide(int $number, int $divider): float.
+{
+    if ($divider === 0) {
+        throw new \InvalidArgumentException('Argument "$divider" must be not zero');
+    }
+
+    return (float) $number / $divider;
+}
+
+// Для данного метода ДОЛЖНО быть 4 теста.
+// 1. Название команды соответствует `self::FIRST_COMMAND`.
+// 2. Название команды соответствует `self::SECOND_COMMAND`.
+// 3. Название команды - пустая строка.
+// 4. Команда не найдена.
+public function execute(string $commandName): void
+{
+    if (empty($commandName)) {
+        throw new \InvalidArgumentException('Argument "$commandName" must be not empty');
+    }
+
+    switch ($commandName) {
+        case self::FIRST_COMMAND:
+            $this->firstCommand();
+
+            break;
+        case self::SECOND_COMMAND:
+            $this->secondCommand();
+
+            break;
+        default:
+            throw new \DomainException(sprintf('Unknown command: "%s"', $commandName));
+    }
+}
+```
+
+### 7.2. Стратегия тестирования
+
+Код ТРЕБУЕТСЯ покрывать, согласно "белому ящику". В случае чрезмерной сложности использования "белого ящика" СЛЕДУЕТ
+использовать стратегию "черного ящика".
+
+### 7.3. Разделение тест методов
+
+Каждый тест метод ДОЛЖЕН иметь полностью независимое состояние, относительно других тест методов.
+Каждый тест метод ДОЛЖЕН проверять конкретное поведение тестируемого метода (функции), тест методы, которые проверяют
+несколько аспектов поведения НЕДОПУСТИМЫ.
+
+> Принцип DRY для тестов не применяется, что бы минимизировать ложно позитивные и ложно негативные результаты.
+
+### 7.4. Тестируемый объект
+
+Тестируемый объект ТРЕБУЕТСЯ создавать с помощью оператора `new`. В случае невозможности, или чрезмерной сложности теста
+ДОПУСКАЕТСЯ использовать mock от тестируемого класса.
+Название для объекта СЛЕДУЕТ использовать то же, что и название тестируемого класса, в lowerCamelCase.
+
+### 7.5. Проверка результатов
+
+Проверку результатов НЕОБХОДИМО выполнять на тождество, т.е. и на тип и на значение.
+Числа с плавающей точкой ДОЛЖНЫ проверяться с учетом погрешности.
+Значения времени, зависящие от текущего времени ДОЛЖНЫ проверяться с учетом погрешности.
+
+## 8. PHPUnit
+
+```php
+<?php
+// Service/UserRegistrator.php 
+
+declare(strict_types = 1);
+
+namespace Vendor\Project\Service;
+
+use Doctrine\ORM\EntityManagerInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
+use Symfony\Component\Security\Core\User\User;
+use Symfony\Component\Security\Core\User\UserInterface;
+
+class UserRegistrator
+{
+    /** @var PasswordEncoderInterface */
+    private $passwordEncoder;
+
+    /** @var LoggerInterface */
+    private $logger;
+
+    /** @var string */
+    private $salt;
+
+    public function __construct(PasswordEncoderInterface $passwordEncoder, LoggerInterface $logger, string $salt)
+    {
+        if (empty($salt)) {
+            throw new \InvalidArgumentException('Variable "$salt" must be not empty');
+        }
+
+        $this->passwordEncoder = $passwordEncoder;
+        $this->logger          = $logger;
+        $this->salt            = $salt;
+    }
+
+    public function register(EntityManagerInterface $entityManager, string $userName, string $password): UserInterface
+    {
+        if (preg_match('/^[a-z][a-z0-9_]{5,254}$/i', $userName)) {
+            throw new \InvalidArgumentException(
+                sprintf('Variable "$userName" is invalid, actual value: "%s"', $userName)
+            );
+        } elseif (empty($password)) {
+            throw new \InvalidArgumentException('Variable "$password" must be not empty');
+        }
+
+        $this->logger->info(sprintf('Register user: "%s"', $userName));
+
+        try {
+            $encodedPassword = $this->passwordEncoder->encodePassword($password, $this->salt);
+            $user            = new User($userName, $encodedPassword);
+
+            $entityManager->persist($user);
+            $entityManager->flush();
+
+            return $user;
+        } catch (\Throwable $exception) {
+            $this->logger->error($exception->getMessage(), ['exception' => $exception]);
+
+            throw $exception;
+        }
+    }
+}
+
+```
+
+```php
+<?php
+// Tests/Service/UserRegistratorTest.php 
+
+declare(strict_types = 1);
+
+namespace Vendor\Project\Tests\Service;
+
+use Doctrine\ORM\EntityManagerInterface;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Vendor\Project\Service\UserRegistrator;
+
+class UserRegistratorTest extends TestCase
+{
+    public function testConstructorWithEmptySalt(): void
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionMessage('Variable "$salt" must be not empty');
+
+        $salt = '';
+
+        /** @var PasswordEncoderInterface|MockObject $passwordEncoder */
+        $passwordEncoder = $this->createMock(PasswordEncoderInterface::class);
+        /** @var LoggerInterface|MockObject $logger */
+        $logger = $this->createMock(LoggerInterface::class);
+
+        new UserRegistrator($passwordEncoder, $logger, $salt);
+    }
+
+    public function testRegister(): void
+    {
+        $salt            = 'salt';
+        $userName        = 'userName';
+        $password        = 'password';
+        $encodedPassword = 'encodedPassword';
+
+        /** @var PasswordEncoderInterface|MockObject $passwordEncoder */
+        $passwordEncoder = $this->createMock(PasswordEncoderInterface::class);
+        /** @var LoggerInterface|MockObject $logger */
+        $logger = $this->createMock(LoggerInterface::class);
+
+        /** @var EntityManagerInterface|MockObject $entityManager */
+        $entityManager = $this->createMock(EntityManagerInterface::class);
+
+        $userRegistrator = new UserRegistrator($passwordEncoder, $logger, $salt);
+
+        $entityManagerIncrement = 0;
+
+        $logger
+            ->expects($this->once())
+            ->method('info')
+            ->with(sprintf('Register user: "%s"', $userName));
+
+        $passwordEncoder
+            ->expects($this->once())
+            ->method('encodePassword')
+            ->with($password, $salt)
+            ->willReturn($encodedPassword);
+
+        $entityManager
+            ->expects($this->at($entityManagerIncrement++))
+            ->method('persist')
+            ->with(
+                $this->callback(
+                    function (UserInterface $user) use ($encodedPassword, $userName): bool {
+                        $this->assertSame($userName, $user->getUsername());
+                        $this->assertSame($encodedPassword, $user->getPassword());
+
+                        return true;
+                    }
+                )
+            );
+
+        /** @noinspection PhpUnusedLocalVariableInspection */
+        $entityManager
+            ->expects($this->at($entityManagerIncrement++))
+            ->method('flush');
+
+        $logger
+            ->expects($this->never())
+            ->method('error');
+
+        $user = $userRegistrator->register($entityManager, $userName, $password);
+
+        $this->assertSame($userName, $user->getUsername());
+        $this->assertSame($encodedPassword, $user->getPassword());
+    }
+
+    public function testRegisterWithInvalidUserName(): void
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionMessage('Variable "$userName" is invalid, actual value: "*"');
+
+        $salt     = 'salt';
+        $userName = '*';
+        $password = 'password';
+
+        /** @var PasswordEncoderInterface|MockObject $passwordEncoder */
+        $passwordEncoder = $this->createMock(PasswordEncoderInterface::class);
+        /** @var LoggerInterface|MockObject $logger */
+        $logger = $this->createMock(LoggerInterface::class);
+
+        /** @var EntityManagerInterface|MockObject $entityManager */
+        $entityManager = $this->createMock(EntityManagerInterface::class);
+
+        $userRegistrator = new UserRegistrator($passwordEncoder, $logger, $salt);
+
+        $logger
+            ->expects($this->never())
+            ->method('info');
+
+        $passwordEncoder
+            ->expects($this->never())
+            ->method('encodePassword');
+
+        $entityManager
+            ->expects($this->never())
+            ->method('persist');
+
+        $entityManager
+            ->expects($this->never())
+            ->method('flush');
+
+        $logger
+            ->expects($this->never())
+            ->method('error');
+
+        $userRegistrator->register($entityManager, $userName, $password);
+    }
+
+    public function testRegisterWithInvalidPassword(): void
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionMessage('Variable "$password" must be not empty');
+
+        $salt     = 'salt';
+        $userName = 'userName';
+        $password = '';
+
+        /** @var PasswordEncoderInterface|MockObject $passwordEncoder */
+        $passwordEncoder = $this->createMock(PasswordEncoderInterface::class);
+        /** @var LoggerInterface|MockObject $logger */
+        $logger = $this->createMock(LoggerInterface::class);
+
+        /** @var EntityManagerInterface|MockObject $entityManager */
+        $entityManager = $this->createMock(EntityManagerInterface::class);
+
+        $userRegistrator = new UserRegistrator($passwordEncoder, $logger, $salt);
+
+        $logger
+            ->expects($this->never())
+            ->method('info');
+
+        $passwordEncoder
+            ->expects($this->never())
+            ->method('encodePassword');
+
+        $entityManager
+            ->expects($this->never())
+            ->method('persist');
+
+        $entityManager
+            ->expects($this->never())
+            ->method('flush');
+
+        $logger
+            ->expects($this->never())
+            ->method('error');
+
+        $userRegistrator->register($entityManager, $userName, $password);
+    }
+
+    public function testRegisterWithUnexpectedDbException(): void
+    {
+        $this->expectException(\Exception::class);
+        $this->expectExceptionMessage('UnexpectedException');
+
+        $salt            = 'salt';
+        $userName        = 'userName';
+        $password        = 'password';
+        $encodedPassword = 'encodedPassword';
+        $exception       = new \Exception('UnexpectedException');
+        $logContext      = ['exception' => $exception];
+
+        /** @var PasswordEncoderInterface|MockObject $passwordEncoder */
+        $passwordEncoder = $this->createMock(PasswordEncoderInterface::class);
+        /** @var LoggerInterface|MockObject $logger */
+        $logger = $this->createMock(LoggerInterface::class);
+
+        /** @var EntityManagerInterface|MockObject $entityManager */
+        $entityManager = $this->createMock(EntityManagerInterface::class);
+
+        $userRegistrator = new UserRegistrator($passwordEncoder, $logger, $salt);
+
+        $entityManagerIncrement = 0;
+
+        $logger
+            ->expects($this->once())
+            ->method('info')
+            ->with(sprintf('Register user: "%s"', $userName));
+
+        $passwordEncoder
+            ->expects($this->once())
+            ->method('encodePassword')
+            ->with($password, $salt)
+            ->willReturn($encodedPassword);
+
+        $entityManager
+            ->expects($this->at($entityManagerIncrement++))
+            ->method('persist')
+            ->with(
+                $this->callback(
+                    function (UserInterface $user) use ($encodedPassword, $userName): bool {
+                        $this->assertSame($userName, $user->getUsername());
+                        $this->assertSame($encodedPassword, $user->getPassword());
+
+                        return true;
+                    }
+                )
+            );
+
+        /** @noinspection PhpUnusedLocalVariableInspection */
+        $entityManager
+            ->expects($this->at($entityManagerIncrement++))
+            ->method('flush')
+            ->willThrowException($exception);
+
+        $logger
+            ->expects($this->once())
+            ->method('error')
+            ->with($exception->getMessage(), $logContext);
+
+        $userRegistrator->register($entityManager, $userName, $password);
+    }
+}
+
+```
+
+### 8.1. Именование тестовых классов
+
+Тестовый класс ДОЛЖЕН состоять из префикса `Test` и названия тестируемого класса.
+
+### 8.1. Именование тест методов
+
+Тестовый метод ДОЛЖЕН начинаться с префикса `test`, далее указывается название тестируемого метода в `UpperCamelCase`.
+Тест без дополнительных аспектов в названии ДОЛЖЕН считаться позитивным.
+Для описания дополнительных аспектов в названии тестового метода СЛЕДУЕТ использовать префиксы `With` и `Without`.
+Множество дополнительных аспектов разделяется с помощью строки `And`.
+
+```php
+public function testLogMessage(): void
+// ...
+public function testLogMessageWithEmptyMessage(): void
+// ...
+public function testLogMessageWithEmptyMessageAndEmtyContext(): void
+// ...
+public function testLogMessageWithInvalidContext(): void
+```
+
+### 8.2. Структура теста
+
+Каждый тест ТРЕБУЕТСЯ оформлять согласно структуре, описанной ниже (каждый блок отделяется от остальных пустой строкой).
+
+1. Ожидаемый тип исключения.
+2. Ожидаемое сообщение исключения.
+3. Переменные, используемые в тесте.
+4. Mock-объекты аргументов конструктора тестируемого класса.
+5. Mock-объекты аргументов тестируемого метода.
+6. Создание тестируемого объекта.
+7. Инкременты вызовов mock-объектов.
+8. Поведение методов mock-объектов согласно порядку их вызова.
+9. Вызов тестируемого метода.
+10. Проверка результатов.
+
+ДОПУСКАЕТСЯ отклонение от данной структуры, в случае, если полное следование ей невозможно. Например, когда значение
+переменной [3] определяется только после вызова конструктора тестируемого класса [6].
+
+### 8.3. Переменные, используемые в тесте
+
+Переменные, используемые в тесте ДОЛЖНЫ следовать следующим правилам.
+
+1. НЕДОПУСТИМО использовать свойства тест классов.
+2. Значения, которые будут использоваться отдельно ДОЛЖНЫ быть вынесены в отдельные переменные.
+3. Значения для переменных ТРЕБУЕТСЯ указывать явно.
+4. Значения для переменных НЕ ДОЛЖНЫ зависеть от времени, кроме ситуаций, когда тестируемый метод зависит от текущего
+времени.
+
+### 8.4. Mock-объекты
+
+Mock-объект ДОЛЖЕН быть объявлен, согласно следующим правилам.
+
+1. Переменную, содержащая mock-объект СЛЕДУЕТ называть согласно названию аргумента метода (функции), где будет
+использоваться данный объект.
+2. Mock-объект ДОЛЖЕН быть помечен строчным docblock, содержащими класс объекта, для которого создается mock и
+mock-интерфейс.
+3. Mock-объект ДОЛЖЕН присваиваться переменной тестового метода.
+
+```php
+/** @var PasswordEncoderInterface|MockObject $passwordEncoder */
+$passwordEncoder = $this->createMock(PasswordEncoderInterface::class);
+/** @var LoggerInterface|MockObject $logger */
+$logger = $this->createMock(LoggerInterface::class);
+```
+
+### 8.5. Инкременты вызовов mock-объектов
+
+Инкременты вызовов mock-объектов - это переменные типа `int`, со значением 0.
+Названия для этих переменных ДОЛЖНЫ повторять названия mock-объектов, которым они соответствуют с суффиксом `Increment`.
+Инкременты ДОЛЖНЫ использоваться, когда необходимо описать последовательность вызовов методов mock-объекта.
+
+### 8.6. Поведение методов mock-объектов
+
+Поведение методов mock-объектов ДОЛЖНО описываться согласно одной из следующих структур.
+
+1. Метод `methodName` mock-объекта `$mockObject` НЕ ДОЛЖЕН быть вызван.
+
+```php
+$mockObject
+    ->expects($this->never())
+    ->method('methodName');
+```
+
+2. Метод `methodName` mock-объекта `$mockObject` ДОЛЖЕН быть вызван с аргументами `$methodArguments` и вернуть
+результат.
+
+```php
+$mockObject
+    ->expects($this->once())    // Или `->expects($this->at($mockObjectIncrement++))`
+    ->method('methodName')
+    ->with(...$methodArguments)
+    ->willReturn($methodResult) // Или `->willReturnSelf();`
+```
+
+3. Метод `methodName` mock-объекта `$mockObject` ДОЛЖЕН быть вызван один раз без аргументов и вернуть результат.
+
+```php
+$mockObject
+    ->expects($this->once())    // Или `->expects($this->at($mockObjectIncrement++))`
+    ->method('methodName')
+    ->willReturn($methodResult) // Или `->willReturnSelf();`
+```
+
+4. Метод `methodName` mock-объекта `$mockObject` ДОЛЖЕН быть вызван с аргументами `$methodArguments` и бросить
+исключение `$exception`.
+
+```php
+$mockObject
+    ->expects($this->once())    // Или `->expects($this->at($mockObjectIncrement++))`
+    ->method('methodName')
+    ->with(...$methodArguments)
+    ->willThrowException($exception);
+```
+
+5. Метод `methodName` mock-объекта `$mockObject` ДОЛЖЕН быть вызван один раз без аргументов и бросить исключение
+`$exception`.
+
+```php
+$mockObject
+    ->expects($this->once()) // Или `->expects($this->at($mockObjectIncrement++))`
+    ->method('methodName')
+    ->willThrowException($exception);
+```
+
+### 8.7 Порядок утверждений для одного значения
+
+В случае когда для проверки одного значения требуется несколько утверждений, эти утверждения ДОЛЖНЫ быть описаны в
+порядке от максимально информативных до минимально информативных, далее от общих к частным.
+
+```php
+// Правильно
+/** @var JsonResponse|Response $response */
+$response = $action->run(/* ... */);
+
+$this->assertSame($expectedContent, $response->getContent());
+$this->assertSame(Response::HTTP_OK, $response->getStatusCode());
+$this->assertInstanceOf(JsonResponse::class, $response);
+```
+
+```php
+// Неправильно
+// В случае возникновения ошибки не будет ясно, что же за ошибка произошла, вместо этого получим только несоответствие
+// типа $response, или статус кода.
+/** @var JsonResponse|Response $response */
+$response = $action->run(/* ... */);
+
+$this->assertInstanceOf(JsonResponse::class, $response);
+$this->assertSame(Response::HTTP_OK, $response->getStatusCode());
+$this->assertSame($expectedContent, $response->getContent());
+```
+
+```php
+// Правильно
+$exceptionContext = $object->method(/* ... */);
+
+$this->assertIsArray($exceptionContext);
+$this->assertNotEmpty($exceptionContext);
+$this->assertArrayHasKey('exception', $exceptionContext);
+$this->assertInstanceOf(\Exception::class, $exceptionContext['exception']);
+$this->assertSame($expectedExceptionMessage, $exceptionContext['exception']->getMessage());
+```
+
+```php
+// Неправильно
+$exceptionContext = $object->method(/* ... */);
+
+$this->assertSame($expectedExceptionMessage, $exceptionContext['exception']->getMessage());
+```
+
+### 8.8. Проверка утверждений на основании результатов собственных проверок
+
+Проверка утверждений на основании результатов собственных проверок ДОПУСКАЕТСЯ только в случае, когда отсутствует
+`assert*` метод, включающий эту проверку. Во всех остальных случая НЕОБХОДИМО использовать `assert*` метод.
+
+> Распространенной ошибкой является "ручная" проверка значения и утверждение о ее ложном, или положительном результате.
+> В следствии такого подхода, срабатывание утверждения не покажет информацию о том, что же пошло не так.
+
+```php
+// Правильно
+$this->assertSame($expectedString, $actualString);
+
+// Неправильно
+$this->assertSame(true, $expectedString === $actualString);
+
+// Неправильно
+$this->assertTrue($expectedString === $actualString);
+```
+
+```php
+// Правильно
+$this->assertStringStartsWith($expectedPrefix, $actualString);
+
+// Неправильно
+$this->assertSame(0, strpos($actualString, $expectedPrefix));
+```
+
+```php
+// Правильно
+$this->assertTrue($actualBool);
+
+// Неправильно
+$this->assertTrue($actualBool === true);
+
+// Неправильно
+$this->assertSame(true, $actualBool);
+```
+
+### 8.9. Проверки значений на основании `TestCase::callback`
+
+Для проверок значений на основании `TestCase::callback` НЕОБХОДИМО использовать утверждения, а не возвращать `false`.
+
+> В случае, когда `TestCase::callback` получает `false`, он теряет информацию о причинах, почему проверка дала ложный
+> результат. Поиск этих причин усложняет процесс отладки. Если же вместо проверок с булевым результатом использовать
+> утверждения — информация о проблеме не будет потеряна.
+
+```php
+// Правильно
+$userRepository
+    ->expects($this->once())
+    ->method('findByGroup')
+    ->with(
+        $this->callback(
+            function (Group $group) use ($groupId, $groupName): bool {
+                $this->assertSame($groupId, $group->getId());
+                $this->assertSame($groupName, $group->getName());
+                
+                return true;
+            }
+        )
+    )
+    ->willReturn($users);
+```
+
+```php
+// Неправильно
+$userRepository
+    ->expects($this->once())
+    ->method('findByGroup')
+    ->with(
+        $this->callback(
+            function (Group $group) use ($groupId, $groupName): bool {
+                // Если следующая проверка не выполнится, в лог будет добавлено сообщение о том, что аргумент не
+                // прошел проверку. Информация о том, какая из частей этой проверки дала ложный результат, и какие
+                // в принципе значения сравнивались, будет потеряна.
+                return $group->getId() === $groupId && $group->getName() === $groupName;
+            }
+        )
+    )
+    ->willReturn($users);
+```
+
+### 8.10. Проверка утверждений для числовых значений
+
+Для проверок числовых значений без учета погрешности ДОЛЖЕН использоваться метод `assertSame`.
+
+```php
+// Правильно
+$expected = 5;
+$actual   = 5;
+
+$this->assertSame($expected, $actual);
+```
+
+```php
+// Неправильно
+$expected = 5;
+$actual   = 5;
+
+$this->assertEqual($expected, $actual);
+```
+
+Для проверок числовых значений с учетом погрешности ДОЛЖЕН использоваться метод `assertEqual` с обязательным указанием
+погрешности.
+
+```php
+// Правильно
+$expected = 5;
+$actual   = 4.5;
+
+$this->assertEqual($expected, $actual, '', 1);
+```
+
+### 8.11. Проверка утверждений для \DateTimeImmutable
+
+Проверки объектов \DateTimeImmutable ДОЛЖНЫ осуществляться на основании `timestamp`, а не сравнения объектов.
+Для проверок \DateTimeImmutable, не зависящих от текущего времени ДОЛЖЕН использоваться метод `assertSame`.
+
+```php
+$expected = new \DateTimeImmutable('2019-01-01 10:20:30');
+$actual   = new \DateTimeImmutable('2019-01-01 10:20:30');
+
+$this->assertSame($expected->getTimestamp(), $actual->getTimestamp());
+```
+
+Для проверок \DateTimeImmutable зависящих от текущего времени ДОЛЖЕН использоваться метод `assertEqual` с обязательным
+указанием погрешности.
+
+```php
+$expected = new \DateTimeImmutable();
+$actual   = new \DateTimeImmutable();
+
+$this->assertEqual($expected->getTimestamp(), $actual->getTimestamp(), '', 2);
+```
+
+> В случае проверок данных на основании текущего времени высока вероятность ложно позитивных и ложно негативных
+> результатов. Дело в том, что сам процесс выполнения теста требует некоторого времени, как результат это время является
+> неявной и не контролируемой переменной в тесте.
+
+## 9. IDE
+
+Для разработки php проектов РЕКОМЕНДУЕТСЯ использовать [PhpStorm](https://www.jetbrains.com/phpstorm/).
+
+### 9.1. Inspections и Code Style
+
+Настройки инспекций РЕКОМЕНДУЕТСЯ [импортировать](https://www.jetbrains.com/help/phpstorm/customizing-profiles.html) из
+`phpstorm/php-conventions-inspections.xml`. Данные инспекции используют
+[PHP_CodeSniffer](https://www.jetbrains.com/help/phpstorm/using-php-code-sniffer.html).
+
+Настройки код стайла РЕКОМЕНДУЕТСЯ [импортировать](https://www.jetbrains.com/help/phpstorm/configuring-code-style.html)
+из `phpstorm/php-conventions-code-style.xml`.
+
+**ВАЖНО** Inspections и Code Style содержат настройки только для PHP, по этой причине СТОИТ выполнять импорт
+как расширение схемы, выбрав опцию `Current scheme`.

+ 118 - 0
phpstorm/php-conventions-code-style.xml

@@ -0,0 +1,118 @@
+<code_scheme name="php-conventions-code-style" version="173">
+  <option name="LINE_SEPARATOR" value="&#xA;" />
+  <PHPCodeStyleSettings>
+    <option name="ALIGN_KEY_VALUE_PAIRS" value="true" />
+    <option name="ALIGN_PHPDOC_PARAM_NAMES" value="true" />
+    <option name="ALIGN_PHPDOC_COMMENTS" value="true" />
+    <option name="ALIGN_ASSIGNMENTS" value="true" />
+    <option name="COMMA_AFTER_LAST_ARRAY_ELEMENT" value="true" />
+    <option name="PHPDOC_WRAP_LONG_LINES" value="true" />
+    <option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
+    <option name="LOWER_CASE_NULL_CONST" value="true" />
+    <option name="ELSE_IF_STYLE" value="COMBINE" />
+    <option name="VARIABLE_NAMING_STYLE" value="CAMEL_CASE" />
+    <option name="BLANK_LINES_BEFORE_RETURN_STATEMENT" value="1" />
+    <option name="KEEP_RPAREN_AND_LBRACE_ON_ONE_LINE" value="true" />
+    <option name="ALIGN_CLASS_CONSTANTS" value="true" />
+    <option name="KEEP_BLANK_LINES_AFTER_LBRACE" value="0" />
+    <option name="FORCE_SHORT_DECLARATION_ARRAY_STYLE" value="true" />
+    <option name="SPACE_AROUND_ASSIGNMENT_IN_DECLARE" value="true" />
+    <option name="PLACE_PARENS_FOR_CONSTRUCTOR" value="1" />
+  </PHPCodeStyleSettings>
+  <codeStyleSettings language="PHP">
+    <option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
+    <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
+    <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+    <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
+    <option name="BLANK_LINES_AFTER_PACKAGE" value="1" />
+    <option name="BLANK_LINES_AROUND_FIELD" value="1" />
+    <option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
+    <option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
+    <option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
+    <option name="ALIGN_GROUP_FIELD_DECLARATIONS" value="true" />
+    <option name="SPACE_AFTER_TYPE_CAST" value="true" />
+    <option name="CALL_PARAMETERS_WRAP" value="5" />
+    <option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
+    <option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
+    <option name="METHOD_PARAMETERS_WRAP" value="5" />
+    <option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
+    <option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
+    <option name="EXTENDS_LIST_WRAP" value="1" />
+    <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+    <option name="METHOD_CALL_CHAIN_WRAP" value="5" />
+    <option name="BINARY_OPERATION_WRAP" value="1" />
+    <option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
+    <option name="TERNARY_OPERATION_WRAP" value="1" />
+    <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+    <option name="ARRAY_INITIALIZER_WRAP" value="5" />
+    <option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
+    <option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
+    <option name="ASSIGNMENT_WRAP" value="1" />
+    <option name="IF_BRACE_FORCE" value="3" />
+    <option name="DOWHILE_BRACE_FORCE" value="3" />
+    <option name="WHILE_BRACE_FORCE" value="3" />
+    <option name="FOR_BRACE_FORCE" value="3" />
+    <arrangement>
+      <rules>
+        <section>
+          <rule>
+            <match>
+              <AND>
+                <CONST>true</CONST>
+                <modifiers />
+              </AND>
+            </match>
+          </rule>
+        </section>
+        <section>
+          <rule>
+            <match>
+              <AND>
+                <FIELD>true</FIELD>
+                <modifiers />
+              </AND>
+            </match>
+          </rule>
+        </section>
+        <section>
+          <rule>
+            <match>
+              <CONSTRUCTOR>true</CONSTRUCTOR>
+            </match>
+          </rule>
+        </section>
+        <section>
+          <rule>
+            <match>
+              <AND>
+                <METHOD>true</METHOD>
+                <modifiers />
+              </AND>
+            </match>
+          </rule>
+        </section>
+        <section>
+          <rule>
+            <match>
+              <TRAIT>true</TRAIT>
+            </match>
+          </rule>
+        </section>
+        <section>
+          <rule>
+            <match>
+              <INTERFACE>true</INTERFACE>
+            </match>
+          </rule>
+        </section>
+        <section>
+          <rule>
+            <match>
+              <CLASS>true</CLASS>
+            </match>
+          </rule>
+        </section>
+      </rules>
+    </arrangement>
+  </codeStyleSettings>
+</code_scheme>

+ 270 - 0
phpstorm/php-conventions-inspections.xml

@@ -0,0 +1,270 @@
+<profile version="1.0">
+  <option name="myName" value="php-conventions" />
+  <inspection_tool class="AnnotationDeprecatedInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="AnnotationMissingUseInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
+  <inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="LongLine" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpAbstractStaticMethodInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpAssignmentInConditionInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpAssignmentReplaceableWithOperatorAssignmentInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpAssignmentReplaceableWithPrefixExpressionInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpCSValidationInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="CODING_STANDARD" value="PSR2" />
+    <option name="WARNING_HIGHLIGHT_LEVEL_NAME" value="WARNING" />
+    <option name="EXTENSIONS" value="php,js,css,inc" />
+  </inspection_tool>
+  <inspection_tool class="PhpClassNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="m_regex" value="[A-Z][A-Za-z\d]+" />
+    <option name="m_minLength" value="2" />
+    <option name="m_maxLength" value="80" />
+  </inspection_tool>
+  <inspection_tool class="PhpComposerExtensionStubsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+  <inspection_tool class="PhpConstantNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="m_regex" value="[A-Z][A-Z_\d]+" />
+    <option name="m_minLength" value="2" />
+    <option name="m_maxLength" value="80" />
+  </inspection_tool>
+  <inspection_tool class="PhpConstantReassignmentInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpConstructorStyleInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpDeprecationInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpDisabledExtensionStubsInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpDivisionByZeroInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpDocFieldTypeMismatchInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpDocMissingReturnTagInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="SKIP_ON_PRESENT_TYPE_HINT" value="true" />
+  </inspection_tool>
+  <inspection_tool class="PhpDocMissingThrowsInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpDocRedundantThrowsInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpDocSignatureInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="ALLOW_MISSING_PARAMETERS" value="true" />
+  </inspection_tool>
+  <inspection_tool class="PhpDuplicateArrayKeysInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpDuplicateCaseInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpDuplicateSwitchCaseBodyInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+  <inspection_tool class="PhpDynamicAsStaticMethodCallInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="MAGIC_HIGHLIGHT_LEVEL_NAME" value="WARNING" />
+  </inspection_tool>
+  <inspection_tool class="PhpExpressionResultUnusedInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpFieldAssignmentTypeMismatchInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpForeachArrayIsUsedAsValueInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpForeachNestedOuterKeyValueVariablesConflictInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpFormatFunctionParametersMismatchInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpFullyQualifiedNameUsageInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="IGNORE_GLOBAL_NAMESPACE" value="true" />
+  </inspection_tool>
+  <inspection_tool class="PhpFunctionNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="m_regex" value="[a-z][_a-z\d]+" />
+    <option name="m_minLength" value="2" />
+    <option name="m_maxLength" value="80" />
+  </inspection_tool>
+  <inspection_tool class="PhpIgnoredClassAliasDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpIllegalArrayKeyTypeInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpIllegalPsrClassPathInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+  <inspection_tool class="PhpIllegalStringOffsetInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpIncludeInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpIncompatibleReturnTypeInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="STRICT_TYPE_CHECKING" value="true" />
+  </inspection_tool>
+  <inspection_tool class="PhpInconsistentReturnPointsInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpInternalEntityUsedInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpInvalidMagicMethodModifiersInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpMethodMayBeStaticInspection" enabled="false" level="INFORMATION" enabled_by_default="false" />
+  <inspection_tool class="PhpMethodNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="m_minLength" value="1" />
+    <option name="m_maxLength" value="80" />
+  </inspection_tool>
+  <inspection_tool class="PhpMethodOrClassCallIsNotCaseSensitiveInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpMethodParametersCountMismatchInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="DISABLE_FOR__PARAMETER" value="false" />
+  </inspection_tool>
+  <inspection_tool class="PhpMissingDocCommentInspection" enabled="false" level="WARNING" enabled_by_default="false">
+    <option name="CHECK_FIELD" value="true" />
+  </inspection_tool>
+  <inspection_tool class="PhpMissingFieldTypeInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpMissingParentConstructorInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpMissingStrictTypesDeclarationInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpMultipleClassesDeclarationsInOneFile" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpNonCanonicalElementsOrderInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpNonCompoundUseInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpNonStrictObjectEqualityInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpOptionalBeforeRequiredParametersInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpParamsInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpPropertyNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="m_minLength" value="1" />
+    <option name="m_maxLength" value="80" />
+  </inspection_tool>
+  <inspection_tool class="PhpRedundantCatchClauseInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpRedundantClosingTagInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpShortOpenTagInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpSignatureMismatchDuringInheritanceInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpSillyAssignmentInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpStatementHasEmptyBodyInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="myCommentsCountAsContent" value="true" />
+  </inspection_tool>
+  <inspection_tool class="PhpStaticAsDynamicMethodCallInspection" enabled="false" level="WARNING" enabled_by_default="false">
+    <option name="MAGIC_HIGHLIGHT_LEVEL_NAME" value="WARNING" />
+  </inspection_tool>
+  <inspection_tool class="PhpSwitchCaseWithoutDefaultBranchInspection" enabled="false" level="INFORMATION" enabled_by_default="false" />
+  <inspection_tool class="PhpToStringImplementationInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="STRICT_MODE" value="true" />
+  </inspection_tool>
+  <inspection_tool class="PhpTraditionalSyntaxArrayLiteralInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUndefinedClassConstantInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUndefinedClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUndefinedConstantInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUndefinedFieldInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUndefinedFunctionInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUndefinedGotoLabelInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUndefinedMethodInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUndefinedNamespaceInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUnhandledExceptionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+  <inspection_tool class="PhpUnnecessaryFullyQualifiedNameInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="IGNORE_GLOBAL_NAMESPACE" value="true" />
+  </inspection_tool>
+  <inspection_tool class="PhpUnnecessaryParenthesesInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUnpackedArrayArgumentTypeMismatchInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUnreachableStatementInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUnused" enabled="true" level="WARNING" enabled_by_default="true" field_visibility="private" method_visibility="private" const_visibility="private">
+    <option name="SUPPRESS_GETTERS_AND_SETTERS" value="true" />
+    <option name="CLASS" value="false" />
+    <option name="FUNCTION" value="false" />
+    <option name="MAGIC_METHOD" value="false" />
+  </inspection_tool>
+  <inspection_tool class="PhpUnusedAliasInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUnusedLocalVariableInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="DONT_REPORT_ASSIGNMENT_TO_NULL" value="false" />
+  </inspection_tool>
+  <inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="DONT_REPORT_ABSTRACT_CLASS" value="true" />
+    <option name="DONT_REPORT_ANONYMOUS" value="true" />
+  </inspection_tool>
+  <inspection_tool class="PhpUnusedPrivateFieldInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUnusedPrivateMethodInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpUsageOfSilenceOperatorInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpVariableNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <option name="m_regex" value="[a-z][A-Za-z\d]*" />
+    <option name="m_minLength" value="1" />
+    <option name="m_maxLength" value="80" />
+  </inspection_tool>
+  <inspection_tool class="PhpVariableVariableInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpVoidFunctionResultUsedInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpWrongCatchClausesOrderInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpWrongForeachArgumentTypeInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="PhpWrongStringConcatenationInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="ProblematicWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="RedundantSuppression" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="RepositoryClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+  <inspection_tool class="SSBasedInspection" enabled="true" level="WARNING" enabled_by_default="true">
+    <searchConfiguration name="Public fields not allowed" text="class $a$ {public $b$;}" recursive="false" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Public static fields not allowed" text="class $a$ {public static $b$;}" recursive="false" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Protected static fields not allowed" text="class $a$ {protected static $b$;}" recursive="false" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Private static fields not allowed" text="class $a$ {private static $b$;}" recursive="false" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="This public method not allowed" text="class $a$ {public function $b$()}" recursive="false" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" regexp="^(__get|__set|__call)$" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="This final public method not allowed" text="class $a$ {final public function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" regexp="^(__get|__set|__call)$" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="This abstract public method not allowed" text="class $a$ {abstract public function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" regexp="^(__get|__set|__call)$" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Public static methods not allowed" text="class $a$ {public static function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Final public static methods not allowed" text="class $a$ {final public static function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Abstract public static methods not allowed" text="class $a$ {abstract public static function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="This protected method not allowed" text="class $a$ {protected function $b$()}" recursive="false" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" regexp="^(__get|__set|__call)$" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="This final protected method not allowed" text="class $a$ {final protected function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" regexp="^(__get|__set|__call)$" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="This abstract protected method not allowed" text="class $a$ {abstract protected function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" regexp="^(__get|__set|__call)$" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Protected static methods not allowed" text="class $a$ {protected static function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Final protected static methods not allowed" text="class $a$ {final protected static function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Abstract protected static methods not allowed" text="class $a$ {abstract protected static function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="This private method not allowed" text="class $a$ {private function $b$()}" recursive="false" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" regexp="^(__get|__set|__call)$" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="This final private method not allowed" text="class $a$ {final private function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" regexp="^(__get|__set|__call)$" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="This abstract private method not allowed" text="class $a$ {abstract private function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" regexp="^(__get|__set|__call)$" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Private static methods not allowed" text="class $a$ {private static function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Final private static methods not allowed" text="class $a$ {final private static function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+    <searchConfiguration name="Abstract private static methods not allowed" text="class $a$ {abstract private static function $b$()}" recursive="true" caseInsensitive="true" type="PHP">
+      <constraint name="__context__" within="" contains="" />
+      <constraint name="a" within="" contains="" />
+      <constraint name="b" maxCount="2147483647" target="true" within="" contains="" />
+    </searchConfiguration>
+  </inspection_tool>
+</profile>