Работа с PHP была для меня одним из самых увлекательных занятий, я работал как над крупными, так и над мелкими проектами и узнавал что-то новое на каждом этапе.
Я буду делиться некоторыми маленькими хитростями, которые помогли мне сократить пару строк кода, и другими, которые полностью перевернули мой рабочий процесс.
Совет 1: (if и else)
Возможно, вы уже видели это в учебниках или других статьях, но это нечто действительно важное, о чем я должен упомянуть немного подробнее. Нет ничего плохого в использовании else и else if в вашем коде, однако, в некоторых случаях эти блоки становятся лишними. Давайте рассмотрим пример:
function output_gender(bool $user_is_male) { if ($user_is_male) { return "User is male"; } else { return "User is female"; } }
В данном случае функция output_gender возвращает заданный результат в зависимости от того, истинно или ложно значение $user_is_male. Когда в функции используется return, любой код ниже оператора return полностью игнорируется, поэтому, если $user_is_male истинно, то блок else будет проигнорирован, поскольку возвращается значение. Используя эту концепцию, мы можем избавиться от блока else следующим образом:
function output_gender(bool $user_is_male) { if ($user_is_male) { return "User is male"; } return "User is female"; }
Мы знаем, что оператор if не будет выполняться, если переданное условие false. Это означает, что он перейдет сразу к части ” User is female”.
Совет 2: (if блоки: меньше vs больше)
Совет 2 основывается на совете, который мы только что рассмотрели выше, но он немного глубже. В if/else или даже на примере совета 1 могут возникнуть условия, когда один блок, либо if, либо else, содержит меньше кода, чем другой. В таких ситуациях лучше сначала обработать блок с меньшим количеством кода. Давайте рассмотрим реальный пример.
public function categoryWithPosts($category) { $category = Category::find($category); if ($category) { $category->posts = $category->posts()->published()->get(); return response(['data' => $category], 200); } else { return response(['error' => 'Category not found'], 404); } }
Приведенный выше код проверяет наличие категории поста и выполняет условие, основанное на том, найдена ли категория или нет. Если мы воспользуемся ТОЛЬКО первым советом, то код будет выглядеть следующим образом:
public function categoryWithPosts($category) { $category = Category::find($category); if ($category) { $category->posts = $category->posts()->published()->get(); // having any more code here would // bloat this part of the function return response(['data' => $category], 200); } return response(['error' => 'Category not found'], 404); }
Этот код правильный, однако вы можете ясно видеть, что наш основной код обернут с помощью {} и вставлен дальше. Если бы этот код был значительно длиннее, было бы непросто сохранить все это в блоке if. Следуя совету 2, мы можем использовать это вместо:
public function categoryWithPosts($category) { $category = Category::find($category); if (!$category) { return response(['error' => 'Category not found'], 404); } $category->posts = $category->posts()->published()->get(); // we can freely have more code here // without worrying about the code looking weird return response(['data' => $category], 200); }
Поскольку блок else содержит меньше кода, мы используем отрицательное утверждение с символом !, чтобы этот код выполнялся первым. Итак, наш if скорее содержит if not category, run code….. Это дает нам больше возможностей для свободной работы с нашим основным кодом.
Совет 3: (проверка множества строк)
Допустим, мы хотим выяснить, является или нет определенная переменная одной из многих строк, нам, очевидно, придется написать кучу условных операторов, чтобы проверить это:
$item = "candy"; switch ($item) { case 'candy': return true; case 'toy': return true; default: return false; } // we're not adding break because we're using return // or if ($item == 'candy' || $item == 'toy') { return true; } return false;
Этот код возвращает false, если переменная item не является ни конфеткой, ни игрушкой. Это совершенно правильно, однако очень повторяется. Вместо этого мы можем проверить массив на наличие строки, которую мы хотим найти:
if (in_array($item, ["candy", "toy"])) { return true; } return false;
Даже это может быть сокращено еще больше, потому что in_array возвращает логическое значение.
return in_array($item, ["candy", "toy"]);
Мы просто сократили эти строки до одной строки, правильно? Как это работает? У нас есть массив, содержащий строки, которые мы хотим проверить. Затем мы передаем его в in_array. Это создает простое условие, например:
if $item is inside the array holding "candy" and "toy", return true, else false
Вы можете задаться вопросом, почему бы просто не вернуть, является ли $item конфеткой или игрушкой напрямую, ведь это тоже всего одна строка, как здесь:
return ($item == 'candy' || $item == 'toy');
Это даст нам тот же результат, однако, допустим, мы проверяем 10 строк:
return ($letter == 'a' || $letter == 'b' || $letter == 'c' || $letter == 'd' ...);
Вы можете видеть, что это легко выходит из-под контроля, по сравнению с этим:
return in_array($letter, ["a", "b", "c", "d", ...]);
Обратите внимание, что первый параметр in_array – это строка, которую мы на самом деле проверяем
Совет 4: (??)
?? это, вероятно, самый простой способ создать условия inline без двух частей. Что я имею в виду? Давайте рассмотрим пример, который сделает все объяснения за меня.
$data = [ "a" => 1, "b" => 2, "c" => null, ]; return $data["c"] ? $data["c"] : "No data";
Последняя строка здесь проверяет, является ли ключ c в $data истинным, если нет, то возвращает “No data”.
Мы можем переписать последнюю строку с ??, чтобы она выглядела следующим образом:
// ... return $data["c"] ?? "No data";
В этом случае ?? ведет себя как логический оператор || в других языках. В реальном мире это выглядит следующим образом:
$user = getUserFromDb($user_id) ?? trigger_error("User id is invalid");
echo $user;
getUserFromDb должен вернуть пользователя из базы данных, однако, если пользователь не найден, вместо того, чтобы установить переменную user, мы нарушаем работу приложения с помощью trigger_error. Без ?? мы должны были бы написать это вместо этого:
$user = getUserFromDb($user_id); if (!$user) { trigger_error("User id is invalid"); } echo $user;
Совет 5: (Рекурсивность вместо повторения)
Я думаю, этот совет довольно прост: старайтесь использовать рекурсивность, а не повторяться много раз. Бывают ситуации, которые заставляют вас повторять какой-то код, это нормально, но если вы обнаружите, что повторяете один и тот же код, просто сделайте его методом. К чему приводит рекурсивность? Давайте рассмотрим пример: Это метод, который я написал для объекта запроса моего фреймворка Leaf, чтобы вернуть определенное поле, переданное в запросе.
/** * Returns request data * * This methods returns data passed into the request (request or form data). * This method returns get, post, put patch, delete or raw faw form data or NULL * if the data isn't found. * * @param string|array $params The parameter(s) to return * @param bool $safeData Sanitize output */
Это означает, что данный метод может принимать массив или строку и в зависимости от входных данных возвращать строку или массив. Решением будет проверить, является ли входной сигнал массивом, выполнить цикл для получения строк в массиве, а затем выполнить выборку данных для этих строк, что будет выглядеть следующим образом.
public function get($params, bool $safeData = true) { if (is_string($params)) return $this->body($safeData)[$params] ?? null; $data = []; foreach ($params as $param) { $data[$param] = $this->body($safeData)[$params] ?? null; } return $data; }
Здесь вы заметили, что $this->body($safeData)[$params] ?? null повторяется, и не только это, но что если вместо него будет передан массив, содержащий другой массив. Поскольку это библиотека, неизвестно, какие вещи пользователи будут передавать туда, поэтому я сделал вот так.
public function get($params, bool $safeData = true) { if (is_string($params)) return $this->body($safeData)[$params] ?? null; $data = []; foreach ($params as $param) { $data[$param] = $this->get($param, $safeData); // I called the function again } return $data; }
Это гарантирует, что пока зацикленное значение не станет строкой, он не будет пытаться получить ее данные. Небольшой трюк по сравнению с приведенными выше, но определенно полезный. Обратите внимание, что эта функция привязана к классу, поэтому в ней используется $this
Совет 6: (PHP + HTML)
Это необходимо, когда вы хотите написать PHP в HTML или HTML в PHP. Обычно мы делаем что-то вроде:
<?php foreach ($items as $item) { echo ' <div class="product__card"> <h3>{$item->name}</h3> </div> '; } ?>
Хотя это нормально, вы можете видеть, что мы выводим HTML в виде строки. Чем объемнее HTML, тем более напряженной становится задача сопоставления тегов и отслеживания того, какую именно часть HTML мы пишем. Для этого есть изящное решение.
<?php foreach ($items as $item): ?> <div class="product__card"> <h3><?php echo $item->name; ?></h3> </div> <?php endforeach; ?>
Вы можете наглядно увидеть, как мы поддерживаем форматирование HTML и выравнивание кода… и нет, это не шаблонизатор, это просто PHP упрощает нам работу. Одна из главных особенностей PHP заключается в том, что он позволяет делать одно и то же разными способами. В приведенном выше примере мы используем:
foreach (...): // code endforeach; // also works with if if (...): // code endif; // also if (...) #one line code while(): // ... endwhile;
Совет 7: (Написание функциональных блоков)
Функциональные блоки могут варьироваться от большой функции до одиночной обертки вокруг стандартной функции PHP, главное – просто создать этот функциональный блок. Это делается не только для того, чтобы избежать повторений, но и для того, чтобы ускорить рабочий процесс и сделать код более читабельным.
Вы можете написать простой метод для создания редиректа следующим образом:
function redirectTo($route) { header("location: $route", true, 302); }
So instead of writing header("location: /home", true, 302)
everytime, it makes more sense to write redirectTo("/home")
. The same applies to 3rd party libraries, and long processes, writing a reusable block of code in an open way eg:
Поэтому вместо того, чтобы каждый раз писать header(“location: /home”, true, 302), логичнее написать redirectTo(“/home”). То же самое относится к сторонним библиотекам и длинным процессам, записывая повторно используемый блок кода открытым способом, например:
UserNotification::send($user_id, $notification);
очевидно, лучше, чем писать кучу строк каждый раз, когда вам нужно отправить уведомление пользователю. Еще один очень маленький, но очень полезный совет.
Совет 8: (Использование типов)
Еще одна простая функция. Это одна из наименее используемых, но очень мощных функций, доступных в PHP. Это функция, которая может избавить вас и других разработчиков от множества стрессов (если вы работаете в команде).
Конечно, вы можете писать описания функций, как в примере из совета 5 выше, но это становится довольно сложной задачей – писать описания функций для всех ваших функций и переменных в большом проекте.
Давайте рассмотрим, как типы могут спасти нам жизнь:
function getItem($item) { // $item is expected to be an array // for whatever reason return allItems()[$item[0]]; }
Если над проектом работает другой разработчик или даже вы сами через несколько недель, увидев метод getItem, очевидно, что переменная $item там должна быть строкой, но функция была написана для обработки массива.
Опасность заключается в том, что передача строки не нарушит работу приложения, оно будет работать безупречно. Почему?
Если в функцию передать “chair”, то она будет оценена как allItems()[“c”], что приведет к ошибкам, которые не дадут вам уснуть в 12 часов ночи?. Этого можно легко избежать следующим образом:
function getItem(array $item) { return allItems()[$item[0]]; }
Это позволит убедиться, что все, что передается сюда, имеет нужный тип. Вы можете прочитать больше на сайте php.net
Вы также можете использовать такие методы, как is_string и is_array, которые мы рассматривали выше:
function getItem($item) { if (!is_array($item)) throwErr("item should be array"); return allItems()[$item[0]]; }
Совет 9: (Фреймворки/библиотеки не являются злом)
Буду откровенен, библиотеки с открытым исходным кодом создают проблемы! Иногда библиотеки, которые мы используем, создают больше проблем, вместо того чтобы помочь нам. Это может показаться, что я полностью отрицаю пакеты с открытым исходным кодом, но это не так, я и сам пишу пакеты с открытым исходным кодом, так что это не так!
Я хочу сказать, что вам следует больше читать о пакетах, которые вы используете, читать их документацию, проверять их проблемы на GitHub, не рисковать понапрасну. Одно, что я могу посоветовать, и это относится к совету 7, – пишите обертки для функций вокруг пакетов, которые вы используете. Это даст вам немного больше контроля, а также сделает ваш код чище.
Что касается фреймворков, возможно, вы уже слышали это раньше, но сначала вам следует ознакомиться с PHP. Фреймворки PHP, независимо от того, на каком языке они были написаны, все равно используют принципы и стиль PHP, поэтому первым шагом должно стать знакомство с PHP.
Далее следует выбрать то, что вам удобно, и придерживаться этого. Существует множество вариантов:
- Laravel: если вы любите магию, laravel делает буквально все для вас (если вы не решите иначе)
- Slim: фреймворк rest API, обладающий своего рода атмосферой “принеси свой собственный”.
- Leaf: Это то, что я написал, вдохновленный Slim и Laravel, это дает вам магию, которую вы можете контролировать.
Я упомянул только те фреймворки, которые я действительно использую, чтобы избежать предвзятости.
Совет 10: (Не просто программируйте!)
Хорошо, этот совет – бонусный. Он относится не только к PHP, но и практически ко всем языкам/фреймворкам, с которыми вы работаете. То, что я имею в виду под “не просто программируйте”, относительно просто.
Допустим, вы хотите написать метод, который запрашивает платеж со счета пользователя. Если вы сразу приступите к написанию этой функции, это может привести (или не привести) к тому, что в какой-то момент вы запутаетесь, вам придется остановиться, прокрутить страницу назад, проверить что-то из файла или что-то подобное.
Что я предлагаю? Вот:
// in class scope public function requestPayout() { // parse token to get user id // fetch user from DB with id // check if the user is eligible for payouts // get user balance with user helper // check and throwErr for insufficient funds // ... }
Вышеизложенное просто позволяет вам провести все необходимые размышления, прежде чем приступать к написанию каких-либо функций. Это также в некотором смысле поможет вам перепроверить то, что вы создаете, поскольку в итоге вы сначала перечислите все свои процессы.
Спасибо за чтение
Это несколько советов и хитростей, которые я обнаружил на своем пути в PHP, некоторые из них могут подойти вам, а другие нет, выбирайте те, которые вам удобны, и придерживайтесь их.