PHP: путешествие от «Hello World» до фреймворков
PHP — это довольно популярный язык программирования. Много лет в интернете можно услышать утверждение, что PHP умирает. Однако язык до сих пор жив и активно используется. Если вы занимаетесь выбором языка для изучения в 2024 году, возможно, вам стоит обратить внимание на другие языки программирования, у языка появилось много достойных и более популярных конкурентов. Но если вы, всё-таки, решились и начали осваивать PHP, то этот материал для вас.
PHP преимущественно используется для разработки веб-приложений. Если быть точным, то серверной их части, которую обычно называют бэкендом. Это означает, что ваш код будет работать на сервере. Это несколько упрощает вам задачу, поскольку вам нужно заботиться только о том, чтобы ваше приложение работало только с определёнными версиями PHP, которые доступны на вашем сервере, а не адаптировать ваш код под множество версий браузера, чем вынуждены заниматься фронтенд разработчики.
Но даже не смотря на узкую специализацию языка, его всё-таки можно использовать и в других сферах, хоть и не так эффективно. Например, вполне можно писать приложения для десктопа или просто писать скрипты для командной строки.
PHP всегда привлекал к себе внимание начинающих за счёт низкого порога входа. Код на PHP прощает многие ошибки, которые не прощают другие языки. Вы можете часами разбираться почему ваше первое приложение не хочет запускаться, если будете писать на Java или Python. В PHP, обычно, проблем у начинающих возникает намного меньше. Это и достоинство и недостаток языка. Вы очень быстро сможете начать разрабатывать свои приложения, но позже, вы можете столкнуться с множеством ошибок в коде по мере роста вашего проекта и вам всё равно придётся углубиться в изучение основ, которые, в тех же Java и Python просто обязательны с первого дня.
Объектно ориентированный подход
Итак. Вы уже написали свой первый «Hello, World» на PHP и, возможно, даже пишите какие-то длинные скрипты, которые проводят некие магические вычисления. Чем больше данных, тем более запутанных становится ваш код. Вы постоянно вынуждены копировать куски одного и того же кода, чтобы выполнить одно и тоже действие в другом месте.
Постепенно приходит понимание что такое функция и вы начинаете переносить в них повторяющиеся куски кода. Теперь вместо
echo 4 * 6 + 7;
...
echo 12 * 3 + 20;
...
echo 22 * 24 + 100;
в нашем коде появляется что-то вроде такого:
calc(4, 6, 7);
...
calc(12, 3, 20);
...
calc(22, 24, 100);
...
function calc($a, $b, $c) {
echo $a * $b + $c;
}
И пусть теперь вас ругают более опытные коллеги, что вы написали плохой код, но теперь вы, как минимум, не будете бегать по всему коду, искать вашу формулу и исправлять её, если заходим вместо этих операций с тремя значениями сделать что-то другое. А если у вас что-то сложнее простых арифметических действий, то вам теперь не нужно постоянно копировать этот самый кусок кода по всей программе, а просто вызвать вашу функцию.
Двигаемся дальше и видим, что ваш код обрастает огромными структурами, которые мы из раза в раз повторяете. Если нам нужно куда-то передать информацию о человеке, то мы используем массивы, например, так:
$user = array(
"name" => "Саша",
"birthday" => "2001-03-08",
"contacts" => array(
"phone" => "555-55-55",
"email" => "sasha@example.org"
)
);
Вроде бы всё понятно. Но эти структуры норовят вырасти в объёме. В них то и дело появляются новые поля и приходится снова и снова бегать по всему коду и искать, где есть подобные конструкции и редактировать их. Ещё и многоуровневые массивы, когда чтобы добраться до нужной информации нужно указать несколько квадратных скобок после имени массива чтобы добраться до нужных данных. Забыл один уровень и нужных данных у тебя нет. Ещё хуже, начинают вылезать всякие предупреждения. Жизнь потихоньку превращается в ад.
И тут вы узнаёте о чудодейственных классах, где вы не только можете хранить все наши ценные данные, не хуже чем в массивах, но ещё и перенести в них ваши функции, которые связаны с этими данными. Например, чтобы вычислить возраст, вы находите решение на каком-нибудь StackOverflow, делаете функцию getAge, и у вас получается что-то вроде такого:
$contacts = new Contacts;
$contacts->phone = "555-55-55";
$contacts->email = "sasha@example.org";
$user = new User;
$user->name = "Саша";
$user->birthday = "2001-03-08";
$user->contacts = $contacts;
class User
{
public $name;
public $birthday = null;
public $contacts;
public function getAge()
{
if ($this->birthday) {
$birthday = new \DateTime($this->birthday);
$interval = $birthday->diff(new \DateTime());
return $interval->y;
}
return false;
}
}
class Contacts
{
public $phone;
public $email;
}
Довольно многословно, но теперь наша IDE нам начинает подсказывать что и как называется. Уже не нужно держать в памяти всю структуру данных, достаточно просто воспользоваться подсказками. Если нам нужен телефон, то вы просто пишете
$phone = $user->contacts->phone;
Ваши старшие коллеги втирают нам что-то про инкапсуляцию, про чёрный ящик, сеттеры, геттеры… Но ваша задача решена и код работает.
Теперь вы знаете, что есть классы. Что в эти классах можно хранить свои переменные, которые называются свойствами и вкладывать функции, которые называются методами. Теперь вы, вместо вереницы параметров функции или метода, просто передаёте объект нужного класса, который содержит все необходимые данные.
Далее, возможно вы узнаёте про статические свойства и методы классов. Изучаете наследование…
Всемогущий composer
Вы успешно развиваете свои проекты, но начинаете понимать. Количество классов растёт с неимоверной скоростью. Вы уже сами путаетесь что и где подключать. Самое логичное, что вы могли придумать, класть один класс в отдельный файл. Но их стало настолько много, что от вереницы require
рябит в глазах. Нужно что-то с этим делать.
В интернете вы узнаёте про автозагрзку классов, про некий «composer». Наверно что-то сложное. Ищем как можно автоматически загружать классы. PHP всегда мог загрузить любой файл из указанных ему каталогов в include_path
. Также есть описание, что можно написать свою функцию для автозагрузки. Немного исследуем вопрос и получаем следующий код, который размещаем в файле, который расположен в каталоге вместе с другими классами.
set_include_path(__DIR__);
spl_autoload_extensions('.php');
spl_autoload_register();
Здесь set_include_path
задаёт путь, где будет производиться поиск файлов с классами, spl_autoload_extensions
задаёт хвост для имени файла, который будет добавлен к имени класса, spl_autoload_register
регистрирует автозагрузку классов. Увы, но данный код позволяет загружать классы только если в имени файлов все символы имени класса переведены в нижний регистр. Как это можно исправить? Написать свою функцию автозагрузки.
Тут вы узнаёте о пространствах имён (namespace
) и ваш код автозагрузки классов из простого подключения класса превращается в какую-то уже довольно приличного размера функцию. И этот код придётся везде копировать.
Вы не первые, кто подумал об этом и уже довольно давно программисты PHP собрались и придумали себе инструмент — composer. Это скрипт, который написан на языке PHP. Всё, что вам нужно для его запуска, компонент php для запуска приложений из командной строки. Тем, кто пользуется Linux, повезло больше, потому что им достаточно установить соответствующий пакет в своей системе и, далее, пользоваться командой composer
из командной строки. В Windows это сделать немного сложнее. Но даже если вы не хотите ничего ставить, вы просто можете скачать файл composer.phar
и запускать его из командной строки Linux — ./composer.phar
(не забывайте о флаге +x) или Windows — php.exe composer.phar
, конечно, с указанием всех необходимых опций.
Чем же занимается этот скрипт? Он просто опрашивает одну или несколько баз данных на предмет наличия в них пакета с определённым именем. Основной базой данных является сайт https://packagist.org/. На этом сайте любой желающий может выкладывать свои пакеты. Естественно, в таком случае, все пакеты будут открыто доступны любому разработчику на PHP. Если вы хотите закрыть пакеты со своим кодом, то вам необходимо изучить документацию composer
и создать свой собственный закрытый репозиторий с исходным кодом, который вы будете использовать. Создание проприетарного кода выходит за рамки данной статьи, поэтому будем считать, что скрывать вам нечего и вы используете только публично доступные репозитории. В этом случае, кроме установки composer
на своё ПК, от нас никаких дополнительных настроек не требуется.
Чтобы добавить composer
в свой проект достаточно выполнить команду composer init
. Первый раз не нужно особо задумываться об ответах. Используйте значения по умолчанию и просто нажимайте Enter в ответ на все вопросы, пока скрипт не завершит свою работу. Все вводимые значения можно будет легко исправить даже просто правкой одного файла. В результате выполнения вашей команды в проекте появятся (если из ещё не было) две папки src/
и vendor/
, а также файл composer.json
, примерно следующего содержания:
{
"name": "fsa/composer",
"autoload": {
"psr-4": {
"Fsa\\Composer\\": "src/"
}
},
"authors": [
{
"name": "Sergei Fedotov",
"email": "fsa@tavda.info"
}
],
"require": {}
}
Этот файл имеет стандартный формат JSON. Как вы можете заметить, если самостоятельно запускали команду, в файле содержатся те самые значения, которые вы вводили или принимали по умолчанию на этапе инициализации. Содержимое этого файла может изменяться после выполнения команды composer
. Кроме того, файл можно редактировать вручную. Но я не советую делать этого при знакомстве, потому что это может иметь побочные эффекты и вы можете просто нарушить структуру JSON файла. При достаточном опыте и понимании как работает composer
, редактирование этого файла может стать вашим рутиным действием.
Рассмотрим самый явный вариант для начинающих. Вы ничего не хотите публиковать и вам composer
нужен только для того, чтобы использовать уже готовые библиотеки кода для решения своих задач. В этом случае для вас очень мало интересного будет в файле composer.json
.
name
— имя пакеты. Используется только в случае публикации вашего пакета или приложения.autoload
— настройка автозагрузки классов. Стоит обратить на это внимание!authors
— информация об авторах. Может быть полезно только в случае публикации вашего пакета или приложения.require
— это список сторонних пакетов, которые будут вам требоваться.
Кроме require
в файле может присутствовать параметр require-dev
. По сути это единый список используемых сторонних пакетов. Однако некоторые пакеты могут быть вам нужны только на этапе разработки вашего проекта. Вот именно эти пакеты и будут располагаться в секции require-dev
. Когда наступит время развернуть приложение на сервере, вы можете просто сказать composer
использовать пакеты только из секции require
.
Папка src/
в корне проекта. Это ни что иное, как папка для хранения ваших классов, которые необходимо будет загружать. Аналогичная папка есть практически во всех пакетах, которые предоставляет composer
. Именно она указана в секции autoload
и в любой момент может быть названа другим именем или перемещена совершенно в другое место на диске. Если вы её удалите, то ничего страшного не произойдёт. Просто composer
больше не сможет автоматически загрузить оттуда классы, которые вам необходимы. Если папка была создана автоматически, то она пустая.
Папка vendor/
в корне проекта. Это основной каталог, где composer
хранит всё необходимое для своей работы. И, пожалуй, единственным файлом, на который требуется обратить внимание на этапе знакомства с composer
— файл vendor/autoload.php
. Именно его необходимо будет подключить внутри своего файла PHP, чтобы у вас начала работать автозагрузка классов. Эту папку почти всегда можно безболезненно удалить с вашего диска и создать заново, но для этого потребуется доступ в интернет (или каким-то чудом необходимые данные окажутся в кэше на вашем ПК). Никаких изменений внутри этой папки категорически не рекомендуется делать, потому что они могут быть легко утеряны. Небольшой спойлер. Путём настройки composer
можно оптимизировать процесс поиска классов и сделать его быстрее. Это можно делать на боевых серверах, чтобы ускорить обработку запросов. При разработке лучше пользоваться тем файлом, что вам предоставлен по умолчанию, потому что в этом случае у вас не будет неожиданных побочных эффектов после редактирования ваших классов.
После того, как composer
был проинициализирован внутри вашего проекта и вы получили файл composer,json
, можно заняться установкой необходимых пакетов. Для примера установим пакет от автора этого текста, который облегчает работу с Telegram Bot API:
composer require fsa/telegram-bot-api
При этом в файле composer.json
сразу появятся изменения в секции require
:
"require": {
"fsa/telegram-bot-api": "^0.4.1"
}
В нашем случае composer
обратился к базе данных на сайте https://packagist.org/, нашёл у пользователя fsa
пакет с именем telegram-bot-api
. Из этой информации нашёл адрес репозитория https://github.com/fsa/php-telegram-bot-api
и скачал оттуда последнюю, на текущий момент, стабильную версию пакета (0.4.1). При желании можно установить и другие версии. Для этого необходимо явно указать требуемую версию пакета. При этом может быть даже указана не точная версия, а шаблон. Например, если указать 1.0.*
, то будет скачана самая старшая версия из имеющихся, которые начинаются на 1.0.
. Или можно указать ^0.4
, тогда будет скачана последняя версия из ветки 0.4
. Подробнее о формате шаблонов читайте на сайте composer
. Можно даже указать *
в качестве номера версии. Но делать этого крайне не рекомендуется, потому что в новых версиях библиотек могут появиться какие-то не совместимые изменения и ваш программный код попросту может перестанет работать. Но в этом правиле есть одно исключение. В секции require
могут быть указаны необходимые компоненты языка PHP. Например, если вы хотите пользоваться функционалом curl в PHP, то можете явно это указать в своём composer.json
:
composer require ext-curl "*"
Конечно, это не заставит composer
доустановить необходимые пакеты в вашей операционной системе, но при попытке развернуть приложение вам будет сообщено, что на вашей машине чего-то не хватает. И вам не придётся думать, что же пошло не так. На одном компьютере этот код работает, а на другом нет.
Обратите внимание, что номер версии может содержать символы, которые интерпретируются в командной строке по особому. В этом случае их необходимо экранировать, либо включением всей строки версии в кавычки, либо каждый отдельный спецсимвол с помощью символа слэш (composer require ext-curl \*
).
Возможно, вы обратите внимание на предупреждение, которое выводится в консоли при попытке выполнить команду composer
:
Composer could not detect the root package (fsa/composer) version, defaulting to '1.0.0'. See <https://getcomposer.org/root-version>
На начальных этапах это предупреждение можно просто игнорировать. Оно означает, что composer
не может выяснить версию вашего пакета. Подробнее об этом можно почитать по указанной ссылке. Но если не вдаваться в подробности, то данное предупреждение пропадёт как только вы инициализируете систему контроля версий в вашем проекте (например, git) и сделаете первый коммит. В этом случае composer
обнаружит её использование и будет получать информацию о версии из него. Есть и другие способы указать номер версии, но в 2024 году я бы не рекомендовал их использовать, тем более что отсутствие номера версии без размещения вашего пакета где-то в публичном или ограниченном доступе никак не мешает работе.
После установки пакета можно создать простой файл php, создать бота в Telegram и отправить через него себе сообщение:
<?php
require_once 'vendor/autoload.php';
$api = new FSA\Telegram\TelegramBotApi;
$query = new FSA\Telegram\TelegramBotQuery('TOKEN');
$result = $query->httpPostJson(
$api->sendMessage(123456789, 'Hello, World!')
);
var_dump($result);
где 'TOKEN'
- это токен доступа к вашему боту Telegram, а 123456789
— идентификатор учётной записи пользователя или закрытого чата, или текстовое название вашего публичного чата. Если вы реально будете пробовать эту библиотеку, то не забывайте, что ваш пользователь должен быть подписан на бота, а при отправке сообщения в группу, бот должен иметь права в этой группе для соответствующих действий.
Как вы уже читали ранее, в composer.json
есть отдельная секция require-dev
, которая предназначена для пакетов, которые необходимы приложению только на этапе разработки. Например, таковым может быть пакет phpunit
. В этом случае при подключении такого пакета добавляется ключ --dev
или -D
:
composer require phpunit/phpunit --dev
Такие пакеты будут установлены по умолчанию, но могут быть проигнорированы при установке на боевом сервере.
Хранить папку vendor/
в каждом своём проекте в репозитории расточительно. К тому же её всегда легко воспроизвести. Поэтому, в подавляющем большинстве проектов, эта папка находится в списке игнорирования. Если вы хотите использовать чужой проект, то вам необходимо выполнить одну из двух команд
composer install
или
composer update
В результате выполнения обоих команд будет проанализированы настройки composer
и установлены соответствующие пакеты. Ключевым отличием этих команд является то, что при вызове install
в корне вашего проекта проверяется наличие файла composer.lock
и, если этот файл будет найден, то будет выполнена установка именно тех версий пакетов, которые перечислены в этом файле. Благодаря этому имеется возможность полностью воспроизвести то состояние папки vendor/
, которое было на машине разработчика.
Если же файл composer.lock
не будет обнаружен, то команда install
поведёт себя также, как будет вести себя команда update
. Она проанализирует содержимое файла composer.json
, определит необходимые версии пакетов, установит их а папку vendor/
и создаст или обновит (если вы выполняли update
) файл composer.lock
, чтобы вы могли его передать вашим коллегам или клиентам, если необходимо.
Если вы редактируете файл composer.json
вручную, то после его изменения необходимо выполнить одну из команд. Конечно, если вы меняли версии пакетов, то это должна быть composer up
, что является сокращением для команды composer update
.
Если вы ранее работали с npm
для JS или другими менеджерами пакетов для других языков программирования, то можете заметить, что у composer
, в плане логики работы, с ними много общего.
Пакеты composer и пространства имён
Главное, что нужно понять, что composer
не накладывает никаких ограничение на то, где будут располагаться ваши классы как физически на диске, так и в каких пространствах имён. Когда вы только знакомитесь с ООП, то вы можете даже не подозревать о пространствах имён. По умолчанию ваши классы всегда расположены в основном пространстве. Но вы легко можете это изменить просто добавив директиву namespace
перед объявлением класса, например:
<?php
namespace Fsa\Composer;
class Example {}
Ранее был приведёт пример, что в файле конфигурации composer.json
можете обнаружить следующие строки:
"autoload": {
"psr-4": {
"Fsa\\Composer\\": "src/"
}
},
Это означает, что composer
будет искать классы, которые имеют пространство имён Fsa\Composer
в папке с именем src/
(относительно папки расположения самого фала composer.json
). Какое пространство имён выбрать, зависит только от вас. Можно даже не использовать пространство имён для вашего приложения, тогда файл конфигурации будет выглядеть следующим образом:
"autoload": {
"psr-4": {
"": "src/"
}
},
Но в интернете можно найти замечания, что подобная конфигурация может отрицательно сказаться на производительности. Да, подобная конфигурация работает, но смысла её проверять нет. К тому же, многие фреймворки предлагают свои пространства имён по умолчанию. Например, Symfony использует App
:
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
Если вы соберётесь когда-нибудь публиковать свои библиотеки в открытом доступе, то обязательно изучите рекомендации выбора пространства имён для вашего пакета. Ничего страшного не произойдёт даже если будет пересечение имён с другим пакетом. Но если кто-то захочет использовать ваш пакет и конфликтующий, то он не сможет этого сделать.
Благодаря разным пространствам имён вы можете иметь в своём проекте несколько классов совершенно с одинаковыми именами. Главное, чтобы они не оказались в одном пространстве имён. Чтобы каждый раз не писать полное имя класса, как ранее было в примере с Telegram Bot API — FSA\Telegram\TelegramBotApi
, можно один раз указать в начале вашего файла через use
какой именно класс вы хотите использовать, а также задать ему новый алиас, например:
<?php
require_once 'vendor/autoload.php';
use FSA\Telegram\TelegramBotApi;
use FSA\Telegram\TelegramBotQuery as Query;
$api = new TelegramBotApi;
$query = new Query('TOKEN');
$result = $query->httpPostJson(
$api->sendMessage(123456789, 'Hello, World!')
);
var_dump($result);
При использовании use
у вас всегда будет возможность свободно заменить один класс на другой, например, если используемая вами библиотека была заброшена, но кто-то другой сделал её форк и, также, разместил его в публичный доступ. Достаточно просто изменить одну строчку в файле и ваш код будет использовать уже другую библиотеку. Главное, чтобы её поведение было именно таким, какое ожидает ваш код.
Наводим порядок в папке проекта
Когда к проекту подключается composer
, то в корне проекта появляется множество файлов и папок. Если ваш старый проект состоял целиком только из php файлов, которые могли быть размещены на веб-сервере, то стоит задуматься перенести их в отдельную папку внутри вашего проекта. Хорошей практикой является создание папки public/
. Именно в ней располагается корневой раздел веб-сервера.
Перенесём наш пример с отправкой сообщения в Телеграм из корня проекта в папку public/
, тогда код примет немного другой вид. За одно исправим потенциальную проблему с поиском файла autoload.php
:
<?php
require_once __DIR__.'/../vendor/autoload.php';
use FSA\Telegram\TelegramBotApi;
use FSA\Telegram\TelegramBotQuery as Query;
$api = new TelegramBotApi;
$query = new Query('TOKEN');
$result = $query->httpPostJson(
$api->sendMessage(123456789, 'Hello, World!')
);
var_dump($result);
В данном случае была изменена только одна строка с require_once
. Константа __DIR__
всегда указывает на каталог, где размещён файл, где используется эта директива. Далее указываем подъём на каталог выше и, далее, путь по файла autoload.php
. Даже не смотря на то, что веб-сервер не может отобразить файлы из папки выше чем public/
, интерпретатор php всегда может подключать, читать и использовать любые файлы на файловой системы сервера, к которым ему предоставлен доступ.
Благодаря тому, что корневой каталог веб-сервера расположен выше корневого раздела проекта, теперь не нужно бояться, что какой-нибудь разработчик, библиотекой vasya/superliba
которого вы пользуетесь, зальёт в свой проект php файл для взлома и сможет выполнить свой код просто вызвав файл https://example.com/vendor/vasya/superliba/hack_you.php
с вашего сервера. Это у него просто не получится, ввиду того, что папка vendor/
просто недоступна через веб-сервер. Конечно, у хакера Васи всё ещё остаются варианты взлома вашего сервера, поэтому подходите к выбору библиотек с осторожностью и не ставьте сомнительные или малопопулярные библиотеки.
Последний шаг — фреймворки
Похоже, вам осталось сделать последний шаг. Вы развиваетесь. Вы во всю используете composer
. Вам постоянно приходится копировать свой один и тот же код из проекта в проект. Вам это надоедает и вы начинаете писать свой каркас приложения, размещаете его на https://packagist.org. Проблемы идут одна за другой. Тут непонятно как сделать, здесь непонятно. Многочисленные создания объектов через new
. Надо от этого избавляться. Вы изучаете паттерны проектирования, изначально вообще ничего не понятно, но вы разбираетесь. Находите, что есть какой-то DI-контейнер. Нужна какая-то маршрутизация. Нужны адекватные ответы, в том числе и при ошибках в коде. Рано или поздно у вас может получиться свой собственный фреймворк. Вполне возможно, что он будет даже рабочий, будет использовать хорошие практики программирования, которые вы изучили по ходу его написания. Однако, со временем, поддерживать свой фреймворк становится всё сложнее и сложнее. Новые знания, переписанный код. И вы вновь переписываете свои приложения, который этот код используют.
И пока вы не начали изобретать свой фреймворк, мой вам совет — используйте готовый!!! Нет. Я не отговариваю от написания своего фреймворка для того, чтобы разобраться что и как работает. Это отличная практика. Но есть масса программистов, которые уже решали подобные задачи и они собрались и сделали так, чтобы это было удобно. Фреймворки — это по сути готовые каркасы приложения, которые решают большинство вопросов, которые раньше вы решали руками. Крупные фреймворки, вроде Symfony или Laravel поддерживают люди с хорошим знанием языка программирования и опытом хороших практик написания кода. Если новая версия фреймворка может сломать ваш код, то, как правило, есть инструменты, которые заранее, ещё до выхода ломающей версии, предупредят вас о тех участках кода, которые могут сломаться.
Ну и самый главный плюс использования качественных фреймворков — изучая фреймворк вы параллельно изучаете лучшие практики программирования. Вам легко будет ориентироваться в коде других проектов, которые написано на том же фреймворке, и, даже в коде приложений на другом фреймворке.
Конечно, на этом изучение PHP не заканчивается. Выходят новые версии языка, новые версии фреймворков, которые ещё больше упрощают жизнь разработчика. Но это уже совсем другая история.
Обратите внимание, что заметки могут обновляться со временем. Это может быть как исправление найденных ошибок, так и доработка содержания с целью более полного раскрытия темы. Информация об изменениях доступна в репозитории на github. Там же вы можете оставить в Issue ваши замечания по данной заметке.
Если данная заметка оказалась вам полезной, можете поблагодарить автора финансово.