В данной статье рассматривается подход к написанию и оформлению PHP кода. Нижеизложенные моменты были сформированы путем анализа существующих подходов компаний и личного опыта.
Правила именования файлов и папок
Все названия для папок и файлов должны быть осмысленными и говорящими (не требующие дополнительного разъяснения).
Папки
Все папки именуются в нижнем регистре с разделением слов с использованием символа -
(минус).
Если папка хранит в себе классы, которые относятся к пространству имен (namespace), то папка именуется в соответствии с названием пространства имен (namespace).
Разрешенные символы для именования папок: латинские буквы и символ -
(минус).
Файлы
Все файлы, относящиеся к проекту, именуются в нижнем регистре с разделением слов с использованием символа -
(минус).
Если файл является файлом класса, то именуется в соответствии с названием класса.
Правила именования пространств имен, классов, методов и переменных
Все названия должны быть осмысленными и говорящими (не требующие дополнительного разъяснения).
Пространства имен
Названия пространств имен должны быть в нижнем регистре и состоять из одного слова. В случае необходимости именовать пространств имен более одного слова производится дробление на составные части, являющиеся вложенными пространствами имен.
Классы
Названия классов должны соответствовать PascalCase
. В PascalCase
каждое слово начинается с заглавной буквы.
Трейты имеют постфикс Trait
. Интерфейсы имеют постфикс Interface
. Абстрактные классы имеют префикс Abstract
.
Методы
Названия методов должны соответствовать camelCase
. camelCase
должен начинаться со строчной буквы, а первая буква каждого последующего слова должна быть заглавной. Все слова при этом пишутся слитно между собой
К названиям методов применяются следующие правила:
- Название метода должно передавать намерения программиста
- Название метода должно сообщить, почему этот метод существует, что он
делает и как используется (isPostRequest
,getRequestType
,parseSchemaElement
,renderPageWithSetupsAndTeardowns
) - Название метода не должно быть коротким
- Название метода должно начинаться с глагола
- Названия
boolean
методов должны содержать глаголis
,has
илиcan
Переменные
Названия переменных должны соответствовать camelCase
. camelCase
должен начинаться со строчной буквы, а первая буква каждого последующего слова должна быть заглавной. Все слова при этом пишутся слитно между собой
Константы должны быть соответствовать UPPER_CASE_SNAKE_CASE
. В UPPER_CASE_SNAKE_CASE
все слова пишутся заглавными буквами, а пробелы заменяются знаками подчеркивания.
К названиям переменных применяются следующие правила:
Название переменной должно передавать намерения программиста
Название переменной должно сообщить, почему эта переменная существует, что она делает и как используется
Название переменной не должно быть коротким
В названии переменной не должен использоваться тип данных. Исключение составляет
Map
($typesMap
,$statesMap
), т.к. в ином случае его не отличить от массива с данными.
Если переменная хранит признак, то он должен быть включен в название (
unpaidProject
)
Переменные, отражающие свойства объекта, должны включать название объекта (
userIsAdmin
,messageIsSend
,figureCanBePainted
,projectName
)
Переменные и свойства объекта должны являться существительными и называться так, чтобы они правильно читались при использовании, а не при инициализации
Плохо:
$object->expire_at $object->setExpireAt($date); $object->getExpireAt();
Хорошо:
$object->expiration_date; $object->setExpirationDate($date); $object->getExpirationDate();
Названия
boolean
переменных должны содержать глаголis
,has
илиcan
Запрещены отрицательные логические названия
Плохо:
if ($project->isInvalid()) { // ... } if ($project->isNotValid()) { // ... } if ($accessManager->isAccessDenied()) { // ... }
Хорошо:
if (!$project->isValid()) { // ... } if (!$accessManager->isAccessAllowed()) { // ... } if ($accessManager->canAccess()) { // ... }
При наличии свойств (пункт 8 и аналогичные), порядок имя переменной состоит из: имени объекта, по отношению к которому используется переменная), свойство и продолжение названия переменной (
userHasRoleAdmin
,statusIsActive
)
Правила оформления кода
В первую очередь ставится пространство имен (namespace), которое используется (если есть). Далее пишется конструкции использования классов (use
). В случае использования нескольких
классов одного пространства имен происходит группировка с помощью конструкции {...}
. Далее идет объявление класса.
Фигурные скобки ставятся на той же строке и разделяются пробелом.
Круглые скобки:
- Внутри не разделяются пробелом.
- Снаружи разделяются пробелами управляющие конструкции
- После названия метода/функции — пробел не ставится.
Каждая переменная должна быть объявлена на новой строке.
Условия и служебные вызовы методов разделяются переносом строки, переменные и работа с ними переносами строк не разделяются.
Внутри условий и циклов дополнительный перенос на новую строку не ставится.
Содержимое класса разделяется сверху одной пустой строкой.
class InterfaceType {
private $property = 'myProp';
public function getProperty():string {
return $this->property;
}
}
Перед возвращаемым значением(return
) обязательно ставится перенос строки, если метод не состоит из единственной строки.
Если условие является большим, то обязательно выделить его в одно или несколько смысловых выражений и использовать его (их) в условии.
Плохо:
if (IS_REGISTRATOR() && (($params.status === 'W' || $params.status === 'D' || $params.status === 'A') && $params.remark && (($params.subres_level == 0 && ($user_info->selected_title->tid == $params.boss || $user_info->selected_title->tid == $doc_signer_tid || !$params.usertid) || $params.subres_level > 0 && $user_info->selected_title->tid == $params.usertid))) { ... }
Хорошо:
$docIsInWorkAcceptOrDraft = ...;
$bossHasSignerPriviledge = ...;
$userCanSign = ...;
if ($docIsInWorkAcceptOrDraft && $bossHasSignerPriviledge && $userCanSign) {
// ...
}
Комментирование кода
В общем случае комментарии запрещены (НЕ "всегда"). Любой участок кода, который необходимо выделить или прокомментировать, надо выносить в отдельный метод.
Комментарии должны быть расположены перед объявлением классов, переменных и методов и быть оформлены в соответствии с PHPDoc. Комментарий перед классом должен содержать описание бизнес-логики и отражать его назначение с приведением примеров использования.
Однострочный комментарии обозначаются символами //
, а многострочный /*...*/
.
Готовые алгоритмы, взятые из внешнего источника, должны быть помечены ссылкой на источник.
Правила написания кода
Везде, где имеет смысл, должнно быть прописано declare(strict_types=1);
В функциях/методах вместо отсутствующего скалярного значения используется null
. 0
и пустую строку нельзя использовать в качестве показателя отсутствия значения.
function sendEmail(string $title, string $message = null, string $date = null): void {
// ...
}
// сообщение не было передано
$object->sendEmail('Title', null, '2017-01-01');
// было передано пустое сообщение
$object->sendEmail('Title', '', '2017-01-01');
Нельзя изменять переменные, которые передаются в метод на вход (исключение — если эта переменная объект).
Нельзя нескольким переменным присваивать одно и то же значение. Для проверки наличия ключа в ассоциативном массиве используется array_key_exists
, а не isset
. Нельзя смешивать в массиве строковые и числовые ключи. Нельзя сортировать ассоциативные массивы.
Строки обрамляются одинарными кавычками. Двойные кавычки используются только, если:
- Внутри строки должны быть одинарные кавычки
- Внутри строки используется подстановка переменных
- Внутри строки используются спец. символы
\n
,\r
,\t
и т.д.
Вместо лишней конкатенации используем подстановку переменных в двойных кавычках
В методах должна быть использована максимально возможная типизация, включая тип возвращаемого значения(: type
). Все параметры и их типы должны быть описаны в объявлении метода либо в PHPDoc. Методы названия, которых начинаются c check
и validate
, должны выбрасывать исключения и не возвращать значения.
Все методы класса по умолчанию должны быть private
. Если метод используется наследниками класса, то он объявляется protected
. Если используется сторонними классами, тогда public
.
Если метод может вернуть null
, то желательно реализовать шаблон проектирования Null object, или выбросить исключение, или вернуть объект особого случая (пример: пустой массив).
При возврате из метода данных типа json
— недопустимо писать return true
, всегда использовать конструкцию return ['success' => ['message' => '.....']]
или ['error' => ['message' => '.....']]
. message
приведен для примера, можно использовать любые ключи в неограниченном количестве.
Метод должен явно отличать нормальные ситуации от исключительных.
По умолчанию тексты исключений не должны показываться пользователю. Они предназначены для логирования и отладки. Текст исключения можно показать пользователю, если оно явно для этого предназначено.
В условном операторе должно проверяться исключительно boolean
значение. В сравнении не boolean
переменных используется строгое сравнение с приведением типа (===
), автоматическое приведение и нестрогое сравнение не используются.
Плохо:
if ($user) {
// ...
}
if ($request->postData('amount') == 100) {
// ...
}
if (!$request->postData('amount')) {
// ...
}
Хорошо:
if ($user === null) { // $user является типом object
// ...
}
if ((int)$request->postData('amount') === 100) {
// ...
}
if ($booking->comment === '') {
// ...
}
При использовании в условном выражении одновременно операторов И и ИЛИ обязательно выделять приоритет скобками.
dimsog
PSR
EvgeniiR
greabock, dimsog,
Не в поддержку конвенций описанных в статье, но PSR это лишь рекомендации, которые можно соблюдать, а можно и не соблюдать. Некоторые из них хороши, некоторые так себе или не подходят конкретному проекту, некоторые пора пересмотреть.
И нет ничего плохого в отклонении от них в рамках конкретного проекта, или использовании своих, даже если они и противоречат PSR.
P.S. Хабр упорно перемещает мой комментарий не в ту ветку, написал в саппорт.
greabock
PSR — всего лишь рекомендации — вы не обязаны их соблюдать.
GRASP — всего лишь шаблны — вы не обязаны их использовать.
SOLID — всего лишь принципы — вам не обязательно им следовать.
Подпишусь под каждым утверждением.
А еще никто вас никто и никогда не обяжет и соблюдать элементарный этикет, отказаться от подворотов в минус двадцать и использовать презервативы при случайных связях. Нет таких законов.
EvgeniiR
Принципы проектирования основываются на ± общепринятых конецпциях делающих код проще в поддержке, вроде внутренней/внешней связности модулей(coupling/cohesion), information hiding.
От несоблюдения PSR, связность не повысится, лишний код не появится(а то и уберётся), поддерживать его тяжелее не станет, а может и легче станет, с лучше отражающими реальность интерфейсами(а не логгер который никогда не бросает исключения по интерфейсу, даже если они критичны), нормальным неймингом.
А я не про законы, я про практическую ценность конкретных ограничений.
greabock
PSR — это и есть ± общепринятый стандарт.
Если код сложнее читать — его сложнее поддерживать. Читать его сложнее программисту, который привык писать по стандарту (с долей вероятности превышающей 50% — PSR) отличному от стандарта который вы используете.
И да, если ваш логгер бросает исключения, которые (внезапно!) должны логироваться, то честное слово — у вас проблемы побольше чем выразительность PSR. И, пожалуй, я могу понять если это легаси, с которым приходится жить. Но в новых проектах так делать нельзя.
a-tk
Даже если это в легаси, надо покарать того, кто это наследие оставил.
Alroniks
Легаси разное бывает, иногда такое древнее, что и карать как бы и не за что. В те времена, когда охотились на мамонтов, так было принято.
EvgeniiR
Что не так с требованиями бизнеса не проводить операцию при невозможности записать лог?
Код сложнее читать когда связность выше, зависимостей больше.
А влияние стандарта на это примерно такое же, как скорость набора с двух разных клавиатур с различающимся на миллиметр ходом клавиш.
Стандарты PSR — не безоговорочный идеал.
Тот самый общепринятый стандарт, от того самого PHP-FIG, из которого вышли разработчики всех мейнстрим-фреймворков?
greabock
С требованием бизнеса всё норм. Только вот это уже не логгер. Потому что логгер не про бизнес вообще.
Я попробую метафорой.
Атомарное действие в php (будь то HTTP запрос или какая-консольная команда, если она не демон). Это как поезд который идет из точки А в точку Б.
Логгер — это как самописец в поезде. Вы же не станете жать экстренное торможение, только потому что самописец отказал? Вы даже не можете знать, что он отказал.
То, о чем вы говорите — это контрольно-пропускной пункт в котором поезд останавливается для оформления документов, регистрации груза и т.п. Поезд не пойдет дальше, пока все документы не будут оформлены.
То что вы пытаетесь использовать самописец, для операций требующих подтверждения записи — это ошибка. Ведь чтобы убедиться в том что самописец записал данные — вам нужно его открыть ключом или сломать. Для проверки работоспособности самописцев — есть депо обслуживания.
Возвращаясь к миру PHP: Вы пытаетесь реализовать транзакции — это хорошо. Вы не можете отличить логирование от транзакционных операций — это плохо.
Fortop
Вероятно бизнесу на самом деле нужен не лог, а какой-нибудь audit trail.
Это совсем другая подсистема, которая лишь похожа на лог.