Добро пожаловать

В сети много устаревшей информации, которая вводит новых пользователей PHP в заблуждение, распространяя плохие практики и небезопасный код. PHP: The Right Way - это легко читаемый, краткий справочник PHP по популярным стандартам кодирования, ссылки на авторитетные руководства по всему интернету и то, что участники считают лучшими практиками в настоящий момент. Не существует канонического способа использования PHP. Цель этого веб-сайта представить новым PHP разработчикам некоторые темы, которые они могли не знать, долгое время, и нацелить опытных профессионалов на свежие идеи в тех темах с которыми они работали на протяжении лет без какого либо пересмотра. Этот веб-сайт также не расскажет вам какими инструментами пользоваться, но предложит советы для множества вариантов, когда возможно объяснит разницу в подходах и вариантах использования.

Это живой документ и будет продолжать обновляться с более полезной информацией и примерами, как только они станут доступными.

Переводы

PHP: The Right Way переведено на множество различных языков:

Книга

Самая последняя версия PHP: The Right Way также доступна в PDF, EPUB и MOBI форматах. Перейти на Leanpub

Как принять участие

Помогите сделать этот веб-сайт лучшим ресурсом для новых PHP программистов! Поддержать на GitHub

Наверх

Начало

Используйте текущую стабильную версию (8.3)

Если вы начали работать с PHP, начните с текущего стабильного релиза PHP 8.3. PHP 8.x добавляет много новых особенностей чем более старые 7.x и 5.x версии. Движок был в значительной степени переписан, и PHP сегодня намного быстрее чем более старые версии. PHP 8 - это крупное обновление языка, содержащее много новых особенностей и оптимизаций.

Вам необходимо быстро попробовать обновиться до последней стабильной версии - PHP 7.4 уже End of Life. Обновление - это просто, тут не так много 8.1, 8.2, 8.3. Если вы не уверены в какой версии содержится функция или особенность, вы можете проверить PHP документацию на веб-сайте php.net.

Встроенный Веб-сервер

С PHP 5.4 или новее, вы можете начать изучение PHP без установки и настройки полноценного Веб-сервера. Для того чтобы запустить сервер, выполните следующую команду терминала в корневой директории вашего веб-проекта:

> php -S localhost:8000

macOS Установка

macOS поставляется с предустановленным PHP но это обычно немного более ранняя версия чем последний стабильный релиз. Есть несколько способов установить последнюю версию PHP в macOS.

Установка PHP через Homebrew

Homebrew - это пакетный менеджер для macOS который помогает вам легко установить PHP и различные расширения. Главный репозиторий Homebrew предоставляет “formulae” формулы для PHP 7.4, 8.0, 8.1, PHP 8.2 и PHP 8.3. Установите последнюю версию командой:

brew install php@8.3

Вы можете переключаться между Homebrew PHP версиями изменяя переменную PATH. Как альтернативу, вы можете использовать brew-php-switcher для переключения PHP версий автоматически.

Также вы можете переключаться вручную между PHP версиями, отсоединяя и присоединяя желаемую версию:

brew unlink php
brew link --overwrite php@8.2
brew unlink php
brew link --overwrite php@8.3

Установка PHP через MacPorts

Проект MacPorts - это инициатива open-source сообщества направленная на разработку простой в использовании системы для компиляции, установки, и обновления ПО с отрытым исходным кодом, на основе командной строки, X11 или Aqua в операционной системе macOS.

MacPorts поддерживает предварительно скомпилированные бинарники, поэтому вам не нужно пересобирать каждую зависимость из исходных tarball файлов, это спасет вас если в вашей системе не установлен какой-либо пакет..

Здесь вы можете установить php54, php55, php56, php70, php71, php72, php73, php74, php80, php81, php82 или php83 используя команду port install, например:

sudo port install php74
sudo port install php83

И вы можете запустить команду select чтобы переключиться на активную версию PHP:

sudo port select --set php php83

Установка PHP через phpbrew

phpbrew это инструмент для установки и управления несколькими версиями PHP. Это может быть реально полезным, когда два разных приложения/проекта требуют разных версий PHP, и вы не используете виртуальные машины.

Установка PHP через бинарный установщик Liip (deprecated)

Прим. переводчика: устарело, не рекомендуется к использованию.

Еще один популярный способ - это php-osx.liip.ch который обеспечивает методы установки в одну строку для версий с 5.3 по 7.3. Это не перезаписывает бинарники PHP установленные Apple, а устанавливает все в отдельные места (/usr/local/php5).

Сборка из исходников

Еще один метод, который дает вам контроль над версией PHP, которую вы устанавливаете, это сделай сам. В этом случае, также обязательно установите Xcode или заменитель “Command Line Tools for XCode” скачиваемый из Apple’s Developer Center.

Универсальные установщики

Перечисленные выше решения в основном обрабатывают сам PHP и не предоставляют такие вещи как Apache, Nginx или SQL-сервер. Решения “Все в одном” такие как MAMP или XAMPP установят для вас те другие части программного обеспечения и свяжут их все вместе, но простота настройки идет в ущерб гибкости.

Windows Установка

Вы можете скачать бинарники из windows.php.net/download. После извлечения PHP, рекомендуется установить PATH на коневую папку PHP (где находится php.exe) для того чтобы вы могли выполнять PHP из любого места.

Для изучения и локальной разработки, вы можете использовать встроенный веб-сервер с PHP 5.4+ так что вам не нужно беспокоиться об этой настройке. Если вы хотите что-то наподобие “все в одном”, которое включало бы полноценный веб-сервер а также MySQL, тогда такие инструменты как XAMPP, EasyPHP, OpenServer и WAMP помогут быстро настроить и запустить среду разработки Windows. Тем не менее, эти инструменты будут немного отличаться от production, так что будьте осторожны с различиями в средах, если вы работаете в Windows, а развертывание производите в Linux.

Если вам нужно запустить свою production систему в Windows, тогда IIS7 даст вам самую лучшую стабильность и производительность. Вы можете использовать phpmanager (GUI плагин для IIS7) для того чтобы упростить настройку и управление PHP. IIS7 поставляется со встроенным FastCGI и готов к работе, вам просто нужно настроить PHP обработчик. Для поддержки и дополнительных ресурсов есть выделенная область на iis.net for PHP.

Как правило, запуск вашего приложения в разных средах разработки и production может привести к появлению странных ошибок запуска. Если вы разрабатываете в Windows а развертывание осуществляете Linux (или на чем-нибудь не-Windows) тогда вам следует рассмотреть возможность использования Virtual Machine.

У Chris Tankersley есть очень полезный пост в блоге о том, какие инструменты он использует для PHP разработки в Windows.

Linux Установка

Большинство GNU/Linux дистрибутивов идут в составе с PHP доступным из официальных репозиториев, но эти пакеты обычно немного отстают от текущей стабильной версии. Есть несколько способов получить последние версии PHP в этих дистрибутивах. Например в Ubuntu или базирующихся на Debian GNU/Linux дистрибутивах, лучшей альтернативой родным пакетам являются предоставляемые и поддерживаемые Ondřej Surý, через его персональный архив пакетов (PPA) в Ubuntu и DPA/bikeshed в Debian. Инструкции для каждого из них вы найдете ниже. При этом вы всегда можете использовать контейнеры, скомпилировать исходный код PHP и т. д.

Дистрибутивы на базе Ubuntu

Для Ubuntu дистрибутивов, PPA by Ondřej Surý предоставляет поддерживаемые версии PHP вместе со многими расширениями PECL. Для добавления этого PPA в вашу систему, проделайте в терминале следующие шаги:

  1. Сначала добавьте PPA в источники программного обеспечения вашей системы с помощью команды:

    sudo add-apt-repository ppa:ondrej/php
    
  2. После добавления PPA обновите список пакетов вашей системы.:

    sudo apt update
    

Это гарантирует, что ваша система сможет получить доступ и установить последние пакеты PHP, доступные в PPA.

Вы можете переключаться между версиями PHP изменяя вашу переменную PATH. Как альтернативу вы можете использовать deb-sphp для автоматического переключения версии PHP.

Так же вы можете вручную переключаться между версиями PHP с помощью update-alternatives на нужную версию:

sudo update-alternatives --set php /usr/bin/php8.2
sudo update-alternatives --set phar /usr/bin/phar8.2
sudo update-alternatives --set phar.phar /usr/bin/phar.phar8.2

Дистрибутивы на базе Debian

Для дистрибутивов основанных на Debian, Ondřej Surý также bikeshed (Debian эквивалент PPA). Добавьте bikeshed в вашу ситему и обновите, следуя этим шагам:

  1. Убедитесь что имеете root доступ. Если нет то возможно потребуется использование sudo для выполнения следующих команд.

  2. Обновите ваш список системных пакетов:

    sudo apt-get update
    
  3. Установите lsb-release, ca-certificates, и curl:

    sudo apt-get -y install lsb-release ca-certificates curl
    
  4. Загрузите ключ подписи для репозитория:

    sudo curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg
    
  5. Добавьте репозиторий в источники программного обеспечения вашей системы:

    sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list'
    
  6. Наконец, еще раз обновите список пакетов вашей системы:

    sudo apt-get update
    

Выполнив эти шаги, ваша система сможет установить последние версии PHP-пакетов из bikeshed.

Общая структура каталогов

Распространенный вопрос среди тех кто начинает писать программы для Веб платформы, “куда положить мои вещи?” На протяжении многих лет ответ был неизменным “туда где DocumentRoot.” Хотя этот ответ и не полон, это отличное место для начала.

По соображениям безопасности, у посетителей сайта не должно быть доступа к конфигурационным файлам; поэтому, публичные скрипты хранятся в публичной директории а приватные настройки и данные хранятся за пределами этой директории.

Для каждой команды, CMS или фреймворка, в котором он работает, каждый из этих объектов использует стандартную структуру каталогов. Однако, если вы начинаете проект в одиночку, знание того, какую структуру файловой системы использовать, может быть сложной задачей.

Paul M. Jones сделал несколько фантастических открытий общей практики из десятков тысяч github проектов PHP. Он составил стандартную структуру файлов и каталогов Standard PHP Package Skeleton на основе этого исследования. В этой структуре каталогов, DocumentRoot должен указывать на public/, unit-тесты должны быть в tests/ директории, а сторонние библиотеки, установленные с помощью composer, принадлежат к vendor/ директории. Для остальных файлов и каталогов, соблюдение Standard PHP Package Skeleton будет иметь больший смысл для участников проекта.

Наверх

Руководство по стилю кода

PHP сообщество многочисленно и разнообразно, состоящее из бесчисленных библиотек, фреймворков и компонентов. PHP разработчики обычно выбирают несколько из них и объединяют их в один проект. Важно, чтобы код PHP придерживался (как можно ближе) к общему стилю, чтобы разработчики могли легко смешивать и сопоставлять различные библиотеки для своих проектов.

Framework Interop Group предложил и одобрил серию рекомендаций по оформлению. Не все из них связаны со стилем кода, но те, что есть PSR-1, PSR-12, PSR-4 и PER Coding Style. Эти рекомендации представляют собой, просто набор правил, принятых во многих проектах, таких как Drupal, Zend, Symfony, Laravel, CakePHP, phpBB, AWS SDK, FuelPHP, Lithium, и т.д. Вы можете использовать их для своих собственных проектов, или продолжать использовать свой стиль.

В идеале вы должны писать PHP-код, соответствующий известному стандарту. Это может быть любая комбинация PSR или один из стандартов кодирования, разработанных PEAR или Zend. Это означает, что другие разработчики могут легко читать ваш код и работать с ним, а приложения, реализующие компоненты, могут иметь согласованность даже при работе с большим количеством стороннего кода.

Вы можете использовать PHP_CodeSniffer для проверки кода на соответствие любой из этих рекомендаций, а также плагины для текстовых редакторов таких как Sublime Text чтобы получать обратную связь в режиме реального времени.

Вы можете исправить макет кода автоматически, используя один из следующих инструментов.:

И вы можете запустить phpcs вручную из оболочки командной строки:

phpcs -sw --standard=PSR1 file.php

Оно покажет ошибки и расскажет, как их исправить. Также может быть полезно включить команду phpcs в git pre-commit hook с параметром коммандной строки --filter=GitStaged. Таким образом, код содержащий нарушения выбранного стандарта, не смогут войти в репозиторий, пока эти нарушения не будут исправлены.

Если у вас есть PHP_CodeSniffer, вы можете автоматически исправить проблемы с макетом кода, о которых он сообщает, с помощью PHP Code Beautifier and Fixer.

phpcbf -w --standard=PSR1 file.php

Другим вариантом является использование PHP Coding Standards Fixer. Он покажет, какие ошибки были в структуре кода до их исправления.

php-cs-fixer fix -v --rules=@PSR1 file.php

Английский предпочтительнее для всех названий символов и кодовой инфраструктуры. Комментарии могут быть легко написаны на любом языке доступным для чтения всеми нынешними и будущими сторонами, которые могут работать над кодовой базой.

И наконец, хорошим дополнительным ресурсом для написания чистого PHP-кода является Clean Code PHP.

Наверх

Особенности языка

Парадигмы программирования

PHP гибкий, динамический язык который поддерживает различные техники программирования. Он резко эволюционировал на протяжении лет, в частности добавилась надежная объектно-ориентированная модель в PHP 5.0 (2004), анонимные функции и пространства имен в PHP 5.3 (2009), а так же трейты в PHP 5.4 (2012).

Объектно-Ориентированное Программирование

PHP имеет очень полный набор возможностей объектно-ориентированного программирования, включая поддержку классов, абстрактных классов, интерфейсов, наследования, конструкторов, клонирования, исключений и т. д.

Функциональное программирование

PHP поддерживает первоклассные функции, имеется ввиду что функция может быть присвоена переменной. Функции определенные пользователем и встроенные функции, и те и другие могут ссылками переменной и вызываться динамически. Функции могут передаваться как аргументы других функций (особенность называется Higher-order Functions) и функции могут возвращать другие функции.

Рекурсия, особенность которая позволяет функции вызывать саму себя, она поддерживается языком, но большая часть PHP кода сфокусировано на итерациях.

Новые анонимные функции (которые поддерживают замыкания) представлены начиная с PHP 5.3 (2009).

PHP 5.4 добавлена возможность привязки замыканий к области видимости объекта а также улучшена поддержка для вызовов так что они могут быть взаимозаменяемо использованы с анонимными функциями почти во всех случаях.

Мета программирование

PHP поддерживает различные формы мета-программирования через механизмы такие как Reflection API и Магические Методы. Есть много доступных Магических Методов как __get(), __set(), __clone(), __toString(), __invoke(), и т.д. что позволяют разработчикам подключаться к поведению класса. Разработчики Ruby часто говорят что PHP не хватает method_missing, но это доступно как __call() и __callStatic().

Пространства имен

Как было сказано выше, сообщество PHP состоит из большого количества разработчиков которые создают много кода. Это означает что код одной библиотеки PHP может использовать такое же имя класса как и в другой. Когда обе библиотеки используют одинаковые пространства имен, они сталкиваются и начинаются проблемы.

Пространства имен решают эту проблему. Как описано в PHP reference manual, пространства имен можно сравнить с каталогами операционной системы где namespace - файлы; два файла с одинаковыми именами могут сосуществовать в разных директориях. Также как, два PHP класса с одинаковыми именами могут сосуществовать в разных PHP пространствах имен. Это так же просто.

Важно использовать пространства имен для вашего кода, чтобы его могли использовать другие разработчики, не опасаясь конфликтов с другими библиотеками.

Один из рекомендуемых способов использования пространства имен описан в PSR-4, который нацелен на обеспечение стандарта согласования файла, класса и пространства имен позволяющих легко встраиваемый (plug-and-play) код.

В октябре 2014 PHP-FIG объявили устаревшим предыдущий стандарт автозагрузки: PSR-0. Оба PSR-0 и PSR-4 все еще вполне пригодны для использования. Последний требует PHP 5.3, и так много PHP 5.2-only проектов используют PSR-0.

Если вы собираетесь использовать стандарт автозагрузки в новых приложениях или пакетах, смотрите в сторону PSR-4.

Стандартная библиотека PHP

Стандартная библиотека PHP (SPL) упакована вместе с PHP и предоставляет коллекции классов и интерфейсов. Это сделано в первую очередь для часто используемых структур данных классов (стек, очередь, куча, и т.д.), и итераторов которые могут проходить по этим структурам данных или вашим собственным классам которые предоставляют SPL интерфейсы.

Интерфейс командной строки

PHP был создан чтобы писать веб-приложения, но он так же полезен для создания скриптовых программ с интерфейсом командной строки (CLI). Программы командной строки PHP могут помочь автоматизировать общие задачи такие как тестирование, разворачивание, и администрирование приложений.

Программы CLI PHP powerful потому что вы можете использовать код вашего приложения напрямую без необходимости создания и защиты графического веб-интерфейса для него. Просто убедитесь в том что не положили ваши CLI PHP скрипты в корень вашей общедоступной директории!

Попробуйте запустить PHP из командной строки:

> php -i

Параметр -i выведет на экран вашу конфигурацию PHP также как функция phpinfo().

Параметр -a предоставляет интерактивную среду, похоже на ruby’s IRB или на интерактивную среду python. Есть также ряд других полезных параметров командной строки.

Давайте напишем простую “Hello, $name” CLI программу. Попробуйте создать файл с именем hello.php как показано ниже.

<?php
if ($argc !== 2) {
    echo "Usage: php hello.php <name>" . PHP_EOL;
    exit(1);
}
$name = $argv[1];
echo "Hello, $name" . PHP_EOL;

PHP установит две специальные переменные, основываясь на аргументах с которыми вы выполнили скрипт. $argc - это целочисленная переменная содержащая аргумент count (количество) и $argv - это переменная - массив содержащий значение value каждого из аргументов. Первый аргумент - это всегда имя файла PHP скрипта, в данном случае hello.php.

Выражение exit() используется с не нулевым (non-zero) числом чтобы дать понять командной оболочке когда команда завершилась неудачей. Обычно используемые коды выходов могут быть найдены здесь.

Чтобы запустить наш скрипт ниже, из командной строки:

> php hello.php
Usage: php hello.php <name>
> php hello.php world
Hello, world

Xdebug

Один из самых полезных инструментов при разработке программного обеспечения является правильный отладчик. Он позволяет отслеживать выполнение вашего кода и контролировать содержимое в стеке. Xdebug, отладчик PHP, может быть использован разными IDE для обеспечения точек останова и проверки стека. Также могут быть доступны такие инструменты как PHPUnit и KCacheGrind для выполнения анализа покрытия и профилирование кода.

Если вы оказались в безвыходной ситуации, готовы прибегнуть к var_dump()/print_r(), и все ещё не можете найти решения -

If you find yourself in a bind, willing to resort to var_dump()/print_r(), and you still can’t find the solution - возможно, вам нужно использовать отладчик.

Установка Xdebug может быть сложной, но одна из самых важных особенностей это “Удалённая Отладка” - если вы разрабатываете код локально и потом тестируете это внутри виртуальной машины или на другом сервере, Удаленная Отладка это особенность которую вы захотите включить сразу.

Традиционно вы будете изменять файл Apache VHost или .htaccess со следующими значениями:

php_value xdebug.remote_host 192.168.?.?
php_value xdebug.remote_port 9000

Свойства “remote host” и “remote port”, будут соответствовать вашему локальному компьютеру и порту, который вы настроили в своей среде IDE для прослушивания. Тогда вам просто нужно перевести вашу IDE в режим «прослушивания подключений» и загрузить URL-адрес:

http://your-website.example.com/index.php?XDEBUG_SESSION_START=1

Теперь ваша IDE будет перехватывать текущее состояние во время выполнения скрипта, позволяя вам устанавливать точки останова и проверять значения в памяти.

Графические отладчики упрощают пошаговое выполнение кода, проверку переменных и оценку кода в реальном времени. Многие IDE имеют встроенную или подключаемую поддержку графической отладки с помощью Xdebug. MacGDBp — это бесплатный автономный графический интерфейс Xdebug с открытым исходным кодом для macOS.

Наверх

Управление зависимостями

Существует множество PHP-библиотек, фреймворков и компонентов на выбор. Ваш проект, скорее всего, будет использовать несколько из них — это зависимости проекта. До недавнего времени в PHP не было хорошего способа управления этими зависимостями проекта. Даже если вы управляли ими вручную, вам все равно приходилось беспокоиться об автозагрузчиках. Это больше не проблема.

В настоящее время существует две основные системы управления пакетами для PHP — Composer и PEAR. Composer в настоящее время является самым популярным менеджером пакетов для PHP, однако долгое время PEAR был основным, используемым менеджером пакетов. Знать историю PEAR — хорошая идея, так как вы все равно можете найти упоминания о нем, даже если никогда им не пользовались.

Composer и Packagist

Composer — рекомендуемый менеджер зависимостей для PHP. Перечислите зависимости вашего проекта в файле composer.json и, с помощью нескольких простых команд Composer автоматически загрузит зависимости вашего проекта и настроит автозагрузку для вас. Composer аналогичен NPM в мире node.js или Bundler в мире Ruby.

Существует множество библиотек PHP, совместимых с Composer и готовых к использованию в вашем проекте. Эти “пакеты” перечислены в Packagist, официальном репозитории совместимых с Composer библиотек PHP.

Как установить Composer

Самый безопасный способ скачать composer — это следовать официальной инструкции. Оно будет проверять установщик на повреждения и подлинность. Программа установки устанавливает двоичный файл composer.phar в ваш текущий рабочий каталог.

Мы рекомендуем устанавливать Composer глобально (напр. единственная копия в /usr/local/bin). Для этого выполните следующую команду:

mv composer.phar /usr/local/bin/composer

Примечание. Если вышеуказанное не удается из-за разрешений, добавьте префикс sudo.

Чтобы запустить локально установленный Composer, вы должны использовать php composer.phar, глобально - просто composer.

Установка в Windows

Для пользователей Windows самый простой способ приступить к работе — использовать программу установки ComposerSetup, которая выполняет глобальную установку и настраивает ваш $PATH так, чтобы вы могли просто вызвать composer из любой директории в вашей командной строке.

Как определить и установить зависимости

Composer отслеживает зависимости вашего проекта в файле с именем composer.json. Вы можете управлять этим вручную если хотите, или использовать сам Composer. Команда composer require добавляет зависимость проекта и если у вас нет файла composer.json, он будет создан. Вот пример, который добавляет Twig как зависимость вашего проекта.

composer require twig/twig:^2.0

В качестве альтернативы, команда composer init поможет вам создать полный файл composer.json для вашего проекта. В любом случае, как только вы создали свой файл composer.json, вы можете сказать Composer, чтобы он загрузил и установил свои зависимости в каталог vendor/. Это также относится к загруженным вами проектам, в которых уже есть файл composer.json:

composer install

Затем добавьте эту строку в основной файл PHP вашего приложения; это скажет PHP использовать автозагрузчик Composer для зависимостей вашего проекта.

<?php
require 'vendor/autoload.php';

Теперь вы можете использовать зависимости вашего проекта, и они будут автоматически загружаться по запросу.

Обновление ваших зависимостей

Composer создает файл с именем composer.lock, в котором хранится точная версия каждого пакета, загруженного при первом запуске composer install. Если вы делитесь своим проектом с другими, убедитесь, что файл composer.lock включен, чтобы при запуске composer install они получили те же версии, что и вы. Чтобы обновить свои зависимости, запустите composer update. Не используйте composer update при развертывании, а только composer install, иначе вы можете получить разные версии пакетов в рабочей среде.

Это наиболее полезно, когда вы гибко определяете требования к версии. Например, требование к версии ~1.8 означает “все, что новее 1.8.0, но меньше 2.0.x-dev”. Вы также можете использовать подстановочный знак *, как в 1.8.*. Теперь команда Composer composer update обновит все ваши зависимости до самой новой версии, которая соответствует ограничениям, которые вы определили.

Уведомления об обновлении

Чтобы получать уведомления о новых выпусках версий, вы можете подписаться на libraries.io, веб-службу, которая может отслеживать зависимости и отправлять вам оповещения об обновлениях.

Проверка ваших зависимостей на предмет проблем с безопасностью

Local PHP Security Checker — это инструмент командной строки, который проверит ваш файл composer.lock и сообщит, нужно ли вам обновить какие-либо из ваших зависимостей.

Обработка глобальных зависимостей с помощью Composer

Composer также может обрабатывать глобальные зависимости и их двоичные файлы. Использование простое - все, что вам нужно сделать, это добавить к вашей команде префикс «global». Например, если вы хотите установить PHPUnit и сделать его доступным по всему миру, вы должны выполнить следующую команду:

composer global require phpunit/phpunit

Это создаст папку ~/.composer, в которой находятся ваши глобальные зависимости. Чтобы бинарные файлы установленных пакетов были доступны везде, вы должны добавить папку ~/.composer/vendor/bin в свою переменную $PATH.

PEAR

Менеджер пакетов-ветеран, которым пользуются некоторые PHP-разработчики, называется PEAR. Он ведет себя аналогично Composer, но имеет некоторые заметные отличия.

PEAR требует, чтобы каждый пакет имел определенную структуру, а это означает, что автор пакета должен подготовить его для использования с PEAR. Использование проекта, не подготовленного для работы с PEAR, невозможно.

PEAR устанавливает пакеты глобально, что означает, что после их установки они становятся доступными для всех проектов на этом сервере. Это может быть хорошо, если многие проекты полагаются на один и тот же пакет с одной и той же версией, но может привести к проблемам, если между двумя проектами возникнут конфликты версий.

Как установить PEAR

Вы можете установить PEAR, загрузив установщик .phar и запустив его. В документации PEAR есть подробные инструкции по установке для каждой операционной системы.

Если вы используете Linux, вы также можете взглянуть на менеджер пакетов вашего дистрибутива. Debian и Ubuntu, например, имеют подходящий пакет php-pear.

Как установить пакет

Если пакет указан в списке пакетов PEAR, вы можете установить его, указав официальное имя:

pear install foo

Если пакет размещен на другом канале, вам необходимо сначала «обнаружить» этот канал, а также указать его при установке. См. Использование документации канала для получения дополнительной информации по этой теме.

Обработка зависимостей PEAR с помощью Composer

Если вы уже используете Composer и хотите также установить некоторый код PEAR, вы можете использовать Composer для обработки зависимостей PEAR. PEAR репозитории больше не поддерживаются напрямую Composer версии 2, так что вы должны вручную добавлять репозиторий для установки PEAR пакетов:

{
    "repositories": [
        {
            "type": "package",
            "package": {
                "name": "pear2/pear2-http-request",
                "version": "2.5.1",
                "dist": {
                    "url": "https://github.com/pear2/HTTP_Request/archive/refs/heads/master.zip",
                    "type": "zip"
                }
            }
        }
    ],
    "require": {
        "pear2/pear2-http-request": "*"
    },
    "autoload": {
        "psr-4": {"PEAR2\\HTTP\\": "vendor/pear2/pear2-http-request/src/HTTP/"}
    }
}

Первый раздел "repositories" будет использоваться, чтобы сообщить Composer, что он должен “инициализировать” (или “обнаружить” в терминологии PEAR) репозиторий pear. Затем раздел require будет добавлять префикс к имени пакета следующим образом:

pear-channel/package

Префикс “pear” жестко запрограммирован, чтобы избежать конфликтов, поскольку канал pear может быть таким же, как, например, имя поставщика другого пакета, тогда короткое имя канала (или полный URL-адрес) может использоваться для ссылки на канал, в котором находится пакет.

Когда этот код будет установлен, он будет доступен в вашем каталоге vendor и автоматически доступен через автозагрузчик Composer:

vendor/pear2/pear2-http-request/pear2/HTTP/Request.php

Чтобы использовать этот пакет PEAR, просто укажите его следующим образом:

<?php
require __DIR__ . '/vendor/autoload.php';

use PEAR2\HTTP\Request;

$request = new Request();

Наверх

Практики написания кода

Основы

PHP — это обширный язык, который позволяет программистам всех уровней создавать код не только быстро, но и эффективно. Однако, продвигаясь по языку, мы часто забываем основы, которые мы сначала изучили (или упустили из виду), в пользу коротких путей и/или вредных привычек. Чтобы помочь справиться с этой распространенной проблемой, этот раздел призван напомнить программистам об основных методах кодирования в PHP.

Дата и время

В PHP есть класс DateTime, который поможет вам при чтении, записи, сравнении или вычислении даты и времени. Помимо DateTime, в PHP есть много функций, связанных с датой и временем, но он предоставляет хороший объектно-ориентированный интерфейс для наиболее распространенных применений. DateTime может обрабатывать часовые пояса, но это выходит за рамки этого краткого введения.

Чтобы начать работу с DateTime, преобразуйте необработанную строку даты и времени в объект с помощью фабричного метода createFromFormat() или выполните команду new DateTime, чтобы получить текущую дату и время. Используйте метод format(), чтобы преобразовать DateTime обратно в строку для вывода.

<?php
$raw = '22. 11. 1968';
$start = DateTime::createFromFormat('d. m. Y', $raw);

echo 'Start date: ' . $start->format('Y-m-d') . PHP_EOL;

Вычисление с помощью DateTime возможно с классом DateInterval. DateTime имеет такие методы, как add() и sub(), которые принимают DateInterval в качестве аргумента. Не пишите код, который ожидает одинаковое количество секунд каждый день. И переход на летнее время, и изменение часового пояса нарушат это предположение. Вместо этого используйте интервалы дат. Для вычисления разницы дат используйте метод diff(). Он вернет новый DateInterval, который очень легко отобразить.

<?php
// create a copy of $start and add one month and 6 days
$end = clone $start;
$end->add(new DateInterval('P1M6D'));

$diff = $end->diff($start);
echo 'Difference: ' . $diff->format('%m month, %d days (total: %a days)') . PHP_EOL;
// Difference: 1 month, 6 days (total: 37 days)

Вы можете использовать стандартные сравнения объектов DateTime:

<?php
if ($start < $end) {
    echo "Start is before the end!" . PHP_EOL;}

Последний пример для демонстрации класса DatePeriod. Он используется для повторения повторяющихся событий. Он может принимать два объекта DateTime, начало и конец, а также интервал, в течение которого он будет возвращать все промежуточные события.

<?php
// output all thursdays between $start and $end
$periodInterval = DateInterval::createFromDateString('first thursday');
$periodIterator = new DatePeriod($start, $periodInterval, $end, DatePeriod::EXCLUDE_START_DATE);
foreach ($periodIterator as $date) {
    // output each date in the period
    echo $date->format('Y-m-d') . ' ';
}

Популярным расширением PHP API является Carbon. Он наследует все в классе DateTime, поэтому требует минимальных изменений кода, но дополнительные функции включают поддержку локализации, дополнительные способы добавления, вычитания и форматирования объекта DateTime, а также средства для тестирования вашего кода путем имитации даты и времени по вашему выбору.

Шаблоны проектирования

Когда вы создаете свое приложение, полезно использовать общие шаблоны в вашем коде и общие шаблоны для общей структуры вашего проекта. Использование общих шаблонов полезно, потому что это значительно упрощает управление вашим кодом и позволяет другим разработчикам быстро понять, как все сочетается друг с другом.

Если вы используете фреймворк, то большая часть кода более высокого уровня и структуры проекта будут основаны на этом фреймворке, поэтому многие решения по шаблонам принимаются за вас. Но выбор лучших шаблонов для кода, который вы строите поверх фреймворка, по-прежнему зависит от вас. Если, с другой стороны, вы не используете фреймворк для создания своего приложения, вам нужно найти шаблоны, которые лучше всего подходят для типа и размера приложения, которое вы создаете.

Вы можете узнать больше о шаблонах проектирования PHP и увидеть рабочие примеры по адресу:

https://refactoring.guru/design-patterns/php

Работа с UTF-8

Этот раздел был первоначально написан Alex Cabal по адресу Лучшие практики PHP и использовались в качестве основы для наших собственных рекомендаций по UTF-8.

Здесь нет краткости. Будьте внимательны, подробны и последовательны

В данный момент PHP не поддерживает Unicode на низком уровне. Есть несколько способов обеспечения корректной обработки строк UTF-8, но это не легко, и требуется копаться, почти во всех уровнях веб-приложения, начиная с HTML до SQL и PHP. Мы будем стремиться к краткому практическому изложению.

UTF-8 на уровне PHP

Базовые операции со строками, такие как конкатенация двух строк и присваивание строк к переменным, не требуют ничего специально для UTF-8. Однако, большинство строковых функций, как strpos() и strlen(), требуют специальных решений. Эти функции часто имеют приставку mb_*: например, mb_strpos() and mb_strlen(). Эти mb_* строки доступны вам через Multibyte String Extension, и специально разработаны для операций со строками Unicode.

Вы должны использовать mb_* функции каждый раз когда оперируете над Unicode строками. Например если вы будете использовать substr() с UTF-8 строкой, существует большой риск, того, что результат будет содержать некоторые искаженные полу-символы. Правильной для использования функцией будет, мультибайтовая пара, mb_substr().

Сложная часть - постоянно помнить об использовании mb_* функций. Если вы забудете даже один раз,ваша Unicode строка имеет шанс быть искаженной при дальнейшей обработке.

Не все строковые функции имеют дополнение mb_*. Если его нет, для того что вы хотите, тогда вам просто не повезло.

Необходимо использовать функцию mb_internal_encoding() в начале каждого, написанного вами скрипта PHP (или в начале глобально подключенного скрипта), и прямо следом функцию mb_http_output(), если ваш скрипт выводится в браузер. Четкое определение кодировки ваших скриптов спасет вас от большого количества головной боли.

Более того, многие PHP функции которые оперируют со строками имеют необязательный параметр, позволяющий вам определять кодировку символа. Вы должны всегда четко указывать UTF-8, когда предоставляется возможность. Например, htmlentities() имеет параметр для кодировки символа, и вы всегда должны указывать UTF-8, если имеете дело с такими строками. Обратите внимание что начиная с PHP 5.4.0, UTF-8 кодировка по-умолчанию для htmlentities() и htmlspecialchars().

И наконец, если вы создаете распространяемое приложение и не можете быть уверены что расширение mbstring будет включено, тогда рассмотрите использование symfony/polyfill-mbstring пакета Composer. Он будет использовать mbstring, когда доступно, и вернется к не UTF-8 функциям если нет.

UTF-8 на уровне Базы Данных

Если ваш PHP скрипт обращается к MySQL, есть шанс что ваши строки будут храниться как не-UTF-8 строки в базе данных даже если вы следовали всем предостережениям выше.

Чтобы быть уверенным что ваши строки предаются от PHP к MySQL как UTF-8, убедитесь в том что ваша база данных и таблицы установлены в utf8mb4 набор символов и сопоставление, и то что вы используете набор символов utf8mb4 в строке подключения PDO. Смотрите пример ниже. Это критически важно.

Обратите внимание что вы должны использовать набор символов utf8mb4 для полноценной поддержки UTF-8, а не utf8! Смотрите дальнейшее чтение чтобы узнать почему.

UTF-8 на уровне браузера

Используйте mb_http_output() функцию чтобы обеспечить UTF-8 вывод вашего PHP скрипта в браузер.

Затем браузер должен будет сообщить ответом HTTP, что эту страницу следует рассматривать как UTF-8. Сегодня принято устанавливать набор символов в заголовке ответа HTTP следующим образом:

<?php
header('Content-Type: text/html; charset=UTF-8')

Исторический подход к этому заключался в том, чтобы включить тег charset <meta> в тег <head> вашей страницы.

<?php
// Tell PHP that we're using UTF-8 strings until the end of the script
mb_internal_encoding('UTF-8');
$utf_set = ini_set('default_charset', 'utf-8');
if (!$utf_set) {
    throw new Exception('could not set default_charset to utf-8, please ensure it\'s set on your system!');
}

// Tell PHP that we'll be outputting UTF-8 to the browser
mb_http_output('UTF-8');

// Our UTF-8 test string
$string = 'Êl síla erin lû e-govaned vîn.';

// Transform the string in some way with a multibyte function
// Note how we cut the string at a non-Ascii character for demonstration purposes
$string = mb_substr($string, 0, 15);

// Connect to a database to store the transformed string
// See the PDO example in this document for more information
// Note the `charset=utf8mb4` in the Data Source Name (DSN)
$link = new PDO(
    'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
    'your-username',
    'your-password',
    array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_PERSISTENT => false
    )
);

// Store our transformed string as UTF-8 in our database
// Your DB and tables are in the utf8mb4 character set and collation, right?
$handle = $link->prepare('insert into ElvishSentences (Id, Body, Priority) values (default, :body, :priority)');
$handle->bindParam(':body', $string, PDO::PARAM_STR);
$priority = 45;
$handle->bindParam(':priority', $priority, PDO::PARAM_INT); // explicitly tell pdo to expect an int
$handle->execute();

// Retrieve the string we just stored to prove it was stored correctly
$handle = $link->prepare('select * from ElvishSentences where Id = :id');
$id = 7;
$handle->bindParam(':id', $id, PDO::PARAM_INT);
$handle->execute();

// Store the result into an object that we'll output later in our HTML
// This object won't kill your memory because it fetches the data Just-In-Time to
$result = $handle->fetchAll(\PDO::FETCH_OBJ);

// An example wrapper to allow you to escape data to html
function escape_to_html($dirty){
    echo htmlspecialchars($dirty, ENT_QUOTES, 'UTF-8');
}

header('Content-Type: text/html; charset=UTF-8'); // Unnecessary if your default_charset is set to utf-8 already
?><!doctype html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>UTF-8 test page</title>
    </head>
    <body>
        <?php
        foreach($result as $row){
            escape_to_html($row->Body);  // This should correctly output our transformed UTF-8 string to the browser
        }
        ?>
    </body>
</html>

Дальнейшее чтение

Интернационализация (i18n) и Локализация (l10n)

Предупреждение для новичков: i18n и l10n — это нумеронимы, своего рода аббревиатура, где числа используются для сокращения слов — в нашем случае интернационализация становится i18n, а локализация — l10n.

Прежде всего, нам нужно определить эти два похожих понятия и другие связанные вещи:

Распространенные способы реализации

Самый простой способ интернационализировать софт PHP - это использование массива файлов и строк в шаблонах, таких как <h1><?=$TRANS['title_about_page']?></h1>. Однако, этот способ, вряд ли рекомендован для серьезных проектов, так как это предполагает некоторые проблемы в обслуживании на пути - некоторые могут появиться в самом начале, такие как множественность.Так что, пожалуйста, не пробуйте это, если ваш проект будет состоять из более чем пары страниц.

Самый классический и часто используемый способ для i18n и l10n - это Unix инструмент называющийся gettext. Он берет своё начало с 1995 и все еще является совершенной реализацией для перевода программного обеспечения. С ним достаточно легко начать работать, пока все еще обладающий недюжинной силой, инструмент поддержки. О Gettext мы поговорим здесь. Так же , чтобы помочь вам не запутаться в командной строке, мы представим замечательное GUI приложение которое может быть использовано для легкого обновления вашего l10n исходника.

Другие инструменты

Есть общеиспользуемые библиотеки которые поддерживают Gettext и другие реализации i18n. Некоторые из них могут показаться более простыми в установке или поддержке дополнительных возможностей или i18n файловых форматов. В этом документе, мы фокусируемся на инструментах поставляемых с ядром PHP, но здесь, для полноты, мы перечислим и другие:

Другие фреймворки также включают в себя i18n модули, но они не доступны за пределами их кодовой базы:

Если вы решите использовать одну из библиотек, не предоставляющих экстракторов, вы можете использовать форматы gettext, чтобы вы могли использовать исходную цепочку инструментов gettext (включая Poedit), как описано в оставшейся части главы.

Gettext

Установка

Возможно, вам потребуется установить Gettext и связанную с ним библиотеку PHP с помощью вашего менеджера пакетов, такого как apt-get или yum. После установки включите его, добавив extension=gettext.so (Linux/Unix) или extension=php_gettext.dll (Windows) в ваш php.ini.

Здесь мы также будем использовать Poedit для создания файлов перевода. Вы, вероятно, найдете его в менеджере пакетов вашей системы; он доступен для Unix, macOS и Windows, а также может быть бесплатно загружен на их веб-сайте.

Структура

Типы файлов

Есть три файла, с которыми вы обычно имеете дело при работе с gettext. Основными из них являются файлы PO (Portable Object) и MO (Machine Object), первый из которых представляет собой список читаемых «переведенных объектов», а второй — соответствующий двоичный файл, который должен интерпретироваться gettext при выполнении локализации. Существует также файл POT (шаблон), который просто содержит все существующие ключи из ваших исходных файлов и может использоваться в качестве руководства для создания и обновления всех файлов PO. Эти файлы шаблонов не являются обязательными: в зависимости от инструмента, который вы используете для выполнения l10n, вы вполне можете обойтись только файлами PO/MO. У вас всегда будет одна пара файлов PO/MO для каждого языка и региона, но только одна POT для каждого домена.

Домены

В некоторых случаях в больших проектах вам может понадобиться разделить переводы, когда одни и те же слова имеют разное значение в зависимости от контекста. В этих случаях вы разделяете их на разные домены. По сути, это именованные группы файлов POT/PO/MO, где имя файла является указанным доменом перевода. Малые и средние проекты обычно для простоты используют только один домен; его имя произвольное, но мы будем использовать «main» для наших примеров кода. Например, в проектах Symfony домены используются для разделения перевода сообщений проверки.

Locale коды

Locale - это просто код который идентифицирует версию одного языка. Это определено следующими ISO 639-1 и ISO 3166-1 alpha-2 спецификациями: две строчные буквы для языка, необязательно с последующим подчеркиванием и двумя заглавными буквами обозначающими код страны или региона. Для редких языков, используются три буквы.

Для некоторых говорящих часть страны может показаться избыточной. На самом деле некоторые языки имеют диалекты в разных странах, например, австрийский немецкий (de_AT) или бразильский португальский (pt_BR). Вторая часть используется для различия этих диалектов - когда ее нет, она рассматривается как «общая» или «гибридная» версия языка.

Структура каталогов

Чтобы использовать Gettext, нам нужно будет придерживаться определенной структуры папок. Во-первых, вам нужно будет выбрать произвольный корень для ваших файлов l10n в исходном репозитории. Внутри у вас будет папка для каждой необходимой локали и фиксированная папка LC_MESSAGES, которая будет содержать все ваши пары PO/MO. Пример:

<project root>
 ├─ src/
 ├─ templates/
 └─ locales/
    ├─ forum.pot
    ├─ site.pot
    ├─ de/
    │  └─ LC_MESSAGES/
    │     ├─ forum.mo
    │     ├─ forum.po
    │     ├─ site.mo
    │     └─ site.po
    ├─ es_ES/
    │  └─ LC_MESSAGES/
    │     └─ ...
    ├─ fr/
    │  └─ ...
    ├─ pt_BR/
    │  └─ ...
    └─ pt_PT/
       └─ ...

Множественные формы

Как мы уже говорили во введении, разные языки могут иметь разные правила множественного числа. Однако gettext в очередной раз спасает нас от этой неприятности. При создании нового файла .po вам нужно будет объявить правила множественного числа для этого языка, и переведенные части, чувствительные к множественному числу, будут иметь другую форму для каждого из этих правил. При вызове Gettext в коде вам нужно будет указать номер, относящийся к предложению, и он выработает правильную форму для использования - даже с использованием подстановки строк, если это необходимо.

Правила множественного числа включают в себя количество доступных множественных чисел и логическую проверку с n, которая определяет, в какое правило попадает данное число (начиная с 0). Например:

Теперь, когда вы поняли, как работают правила множественного числа, а если нет, ознакомьтесь с более подробным объяснением в учебнике LingoHub, вы можете скопировать те, которые вам нужны, из списка вместо того, чтобы писать их от руки.

При вызове Gettext для локализации предложений со счетчиками вам также нужно будет указать соответствующий номер. Gettext определит, какое правило должно действовать, и использует правильную локализованную версию. Вам нужно будет включить в файл .po разные предложения для каждого определенного правила множественного числа.

Пример реализации

После всей этой теории давайте перейдем к практике. Вот выдержка из файла .po - не обращайте внимания на его формат, но вместо этого обратите внимание на общее содержание; позже вы научитесь легко его редактировать:

msgid ""
msgstr ""
"Language: pt_BR\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

msgid "We are now translating some strings"
msgstr "Nós estamos traduzindo algumas strings agora"

msgid "Hello %1$s! Your last visit was on %2$s"
msgstr "Olá %1$s! Sua última visita foi em %2$s"

msgid "Only one unread message"
msgid_plural "%d unread messages"
msgstr[0] "Só uma mensagem não lida"
msgstr[1] "%d mensagens não lidas"

Первая секция работает как заголовок, имея пустые поля msgid и msgstr. В нем описывается кодировка файла, формы множественного числа и другие менее важные вещи. Второй раздел переводит простую строку с английского на бразильский португальский, а третий делает то же самое, но использует замену строки из sprintf, поэтому перевод может содержать имя пользователя и дату посещения. Последний раздел представляет собой пример форм множественного числа, отображающих версию единственного и множественного числа как msgid на английском языке и их соответствующие переводы как msgstr 0 и 1 (после числа, заданного правилом множественного числа). Там также используется замена строки, поэтому номер можно увидеть прямо в предложении, используя %d. Формы множественного числа всегда имеют два msgid (единственное и множественное число), поэтому рекомендуется не использовать сложный язык в качестве источника перевода.

Обсуждение l10n ключей

Как вы могли заметить, мы используем в качестве идентификатора источника фактическое предложение на английском языке. Этот msgid используется во всех ваших файлах .po, что означает, что другие языки будут иметь тот же формат и те же поля msgid, но переведенные строки msgstr.

Говоря о ключах перевода, здесь есть две основные «школы»:

  1. msgid как настоящее предложение. Основные преимущества:

    • если есть части программного обеспечения, не переведенные на какой-либо язык, отображаемый ключ все равно будет иметь какое-то значение. Пример: если вы переводите наизусть с английского на испанский, но вам нужна помощь в переводе на французский, вы можете опубликовать новую страницу с отсутствующими французскими предложениями, и вместо этого части веб-сайта будут отображаться на английском языке;
    • переводчику намного легче понять, что происходит, и сделать правильный перевод на основе msgid;
    • оно дает вам “бесплатный” l10n для одного языка - исходного;
    • Единственный недостаток: если вам нужно изменить фактический текст, вам нужно будет заменить один и тот же msgid в нескольких языковых файлах.
  2. msgid как уникальный, структурированный ключ. Он будет структурированно описывать роль предложения в приложении, включая шаблон или часть, в которой находится строка, а не ее содержимое.

    • это отличный способ организовать код, отделив текстовое содержимое от логики шаблона.
    • однако это может создать проблемы для переводчика, который упустит контекст. Файл исходного языка потребуется в качестве основы для других переводов. Пример: в идеале у разработчика должен быть файл en.po, который переводчики будут читать, чтобы понять, что писать, например, в fr.po.
    • отсутствующие переводы будут отображать бессмысленные клавиши на экране (top_menu.welcome вместо Привет, пользователь! на указанной непереведенной французской странице). Это хорошо, поскольку заставило бы перевод быть завершенным перед публикацией, но плохо, поскольку проблемы с переводом были бы ужасно ужасными в интерфейсе. Некоторые библиотеки, тем не менее, включают возможность указать данный язык как “резервный”, имея поведение, аналогичное другому подходу.

В руководстве по Gettext предпочтение отдается первому подходу, так как в целом он проще для переводчиков и пользователей в случае возникновения проблем. Так же и здесь будем работать. Тем не менее, документация Symfony отдает предпочтение переводу на основе ключевых слов, что позволяет вносить независимые изменения во все переводы, не затрагивая при этом шаблоны.

Повседневное использование

В типичном приложении вы должны использовать некоторые функции Gettext при написании статического текста на своих страницах. Затем эти предложения появляются в файлах .po, переводятся, компилируются в файлы .mo и затем используются Gettext при рендеринге фактического интерфейса. Учитывая это, давайте свяжем то, что мы обсуждали до сих пор, в пошаговом примере:

1. Пример файла шаблона, включающего несколько различных вызовов gettext

<?php include 'i18n_setup.php' ?>
<div id="header">
    <h1><?=sprintf(gettext('Welcome, %s!'), $name)?></h1>
    <!-- code indented this way only for legibility -->
    <?php if ($unread): ?>
        <h2><?=sprintf(
            ngettext('Only one unread message',
                     '%d unread messages',
                     $unread),
            $unread)?>
        </h2>
    <?php endif ?>
</div>

<h1><?=gettext('Introduction')?></h1>
<p><?=gettext('We\'re now translating some strings')?></p>

2. Образец установочного файла (i18n_setup.php, как указано выше), выбор правильной локали и настройка Gettext

<?php
/**
 * Verifies if the given $locale is supported in the project
 * @param string $locale
 * @return bool
 */
function valid($locale) {
   return in_array($locale, ['en_US', 'en', 'pt_BR', 'pt', 'es_ES', 'es']);
}

//setting the source/default locale, for informational purposes
$lang = 'en_US';

if (isset($_GET['lang']) && valid($_GET['lang'])) {
    // the locale can be changed through the query-string
    $lang = $_GET['lang'];    //you should sanitize this!
    setcookie('lang', $lang); //it's stored in a cookie so it can be reused
} elseif (isset($_COOKIE['lang']) && valid($_COOKIE['lang'])) {
    // if the cookie is present instead, let's just keep it
    $lang = $_COOKIE['lang']; //you should sanitize this!
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
    // default: look for the languages the browser says the user accepts
    $langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
    array_walk($langs, function (&$lang) { $lang = strtr(strtok($lang, ';'), ['-' => '_']); });
    foreach ($langs as $browser_lang) {
        if (valid($browser_lang)) {
            $lang = $browser_lang;
            break;
        }
    }
}

// here we define the global system locale given the found language
putenv("LANG=$lang");

// this might be useful for date functions (LC_TIME) or money formatting (LC_MONETARY), for instance
setlocale(LC_ALL, $lang);

// this will make Gettext look for ../locales/<lang>/LC_MESSAGES/main.mo
bindtextdomain('main', '../locales');

// indicates in what encoding the file should be read
bind_textdomain_codeset('main', 'UTF-8');

// if your application has additional domains, as cited before, you should bind them here as well
bindtextdomain('forum', '../locales');
bind_textdomain_codeset('forum', 'UTF-8');

// here we indicate the default domain the gettext() calls will respond to
textdomain('main');

// this would look for the string in forum.mo instead of main.mo
// echo dgettext('forum', 'Welcome back!');
?>

3. Подготовка перевода к первому запуску

Одним из больших преимуществ Gettext по сравнению с настраиваемыми пакетами i18n является обширный и мощный формат файлов. “О, чувак, это довольно сложно понять и отредактировать вручную, простой массив был бы проще!” Не заблуждайтесь, такие приложения, как Poedit, здесь, чтобы помочь - много. Вы можете скачать программу с их веб-сайта, она бесплатна и доступна для всех платформ. Это довольно простой инструмент, к которому можно привыкнуть, и в то же время очень мощный — он использует все функции, доступные в Gettext. Это руководство основано на PoEdit 1.8.

При первом запуске вы должны выбрать “File > New…” из меню. Вам сразу же будет предложено выбрать язык: здесь вы можете выбрать/отфильтровать язык, на который хотите перевести, или использовать тот формат, который мы упоминали ранее, например en_US или pt_BR.

Теперь сохраните файл, используя ту же структуру каталогов, о которой мы упоминали. Затем вы должны нажать “Extract from sources”, и здесь вы настроите различные параметры для задач извлечения и перевода. Вы сможете найти все это позже через “Catalog > Properties”:

После установки этих точек он просканирует ваши исходные файлы, чтобы найти все вызовы локализации. После каждого сканирования PoEdit будет отображать сводку того, что было найдено и что было удалено из исходных файлов. Новые записи будут загружены в таблицу перевода пустыми, и вы начнете вводить локализованные версии этих строк. Сохраните его, и файл .mo будет (повторно)скомпилирован в ту же папку и та-да: ваш проект интернационализирован.

4. Перевод строк

Как вы, возможно, уже заметили, есть два основных типа локализованных строк: простые и во множественном числе. Первые имеют просто два поля: исходную и локализованную строку. Исходная строка не может быть изменена, так как Gettext/Poedit не имеет права изменять ваши исходные файлы - вам следует изменить сам источник и повторно отсканировать файлы. Совет: вы можете щелкнуть правой кнопкой мыши строку перевода, и она подскажет вам исходные файлы и строки, в которых используется эта строка. С другой стороны, строки во множественном числе включают в себя два поля для отображения двух исходных строк и вкладки, позволяющие настраивать различные окончательные формы.

Всякий раз, когда вы изменяете свои источники и вам нужно обновить переводы, просто нажмите Refresh, и Poedit повторно просканирует код, удалив несуществующие записи, объединив те, которые были изменены, и добавив новые. Он также может попытаться угадать некоторые переводы, основываясь на других сделанных вами переводах. Эти предположения и измененные записи получат маркер “Fuzzy”, указывающий на то, что они нуждаются в проверке, и отобразятся в списке золотыми. Это также полезно, если у вас есть команда переводчиков, и кто-то пытается написать что-то, в чем он не уверен: просто отметьте Fuzzy, и кто-то другой проверит позже.

Наконец, рекомендуется оставить отмеченным “View > Untranslated entries first”, так как это поможет вам много не забыть, ни одной записи. Из этого меню вы также можете открывать части пользовательского интерфейса, которые позволяют при необходимости оставлять контекстную информацию для переводчиков.

Tips & Tricks

Возможные проблемы с кэшированием

Если вы используете PHP как модуль Apache (mod_php), у вас могут возникнуть проблемы с кешированием файла .mo. Это происходит при первом чтении, а затем, чтобы обновить его, может потребоваться перезагрузка сервера. На Nginx и PHP5 обычно требуется всего пара обновлений страницы, чтобы обновить кеш перевода, а на PHP7 это требуется редко.

Дополнительные вспомогательные функции

Многие предпочитают использовать _() вместо gettext(). Многие пользовательские библиотеки i18n из фреймворков также используют что-то похожее на t(), чтобы сделать переведенный код короче. Однако это единственная функция, которая имеет ярлык. Возможно, вы захотите добавить в свой проект некоторые другие, такие как __() или _n() для ngettext(), или, может быть, причудливую _r(), которая объединит вызовы gettext() и sprintf(). Другие библиотеки, такие как php-gettext от Gettext, также предоставляют подобные вспомогательные функции.

В этих случаях, нужно дать инструкции Gettext утилите, как извлекать строки из этих новых функций. Не бойтесь; это очень легко. Это просто поле в .po файле, или экран Settings в Poedit. В редакторе, эта опция находится внутри “Catalog > Properties > Source keywords”. Помните: Gettext уже знает функции по-умолчанию для многих языков, так что не бойтесь, что этот список выглядит пустым. Необходимо включить там спецификации этих новых функций, следуя определенному формату:

После включения этих новых правил в файл .po, новое сканирование принесет ваши новые строки так же просто, как и раньше.

Рекомендации

Наверх

Внедрение зависимости

Из Wikipedia:

Внедрение зависимости - это шаблон проектирования ПО который позволяет устранение сложных зависимостей и делает возможным менять их, как в режиме реального времени так и на стадии компиляции.

Это высказывание делает концепцию более сложной, чем она на самом деле есть. Внедрение Зависимости предоставляет компонент с его зависимостями также через внедрение конструктора, вызовы метода или установку свойств. Это так легко.

Основная концепция

Мы можем продемонстрировать концепцию, простым и примитивным примером. У нас есть класс Database, который требует адаптер для общения с базой данных. Here we have a Database class that requires an adapter to speak to the database. Мы реализуем адаптер в конструкторе и создаем жесткую зависимость. Это делает тестирование сложным и означает что класс Database, очень тесно связан с адаптером.

<?php
namespace Database;

class Database
{
    protected $adapter;

    public function __construct()
    {
        $this->adapter = new MySqlAdapter;
    }
}

class MysqlAdapter {}

Этот код может быть переработан с использованием Внедрения Зависимости и следовательно ослабит зависимости. Здесь мы внедряем зависимость в конструктор и используем продвижение свойства конструктора, для того, чтобы оно было доступно во всем классе:

<?php
namespace Database;

class Database
{
    public function __construct(protected MySqlAdapter $adapter)
    {

    }
}

class MysqlAdapter {}

Теперь мы отдаем классу Database его зависимость а не создаем его самого. Мы даже могли бы создать метод который принимал бы аргумент зависимости и устанавливал его этим способом, или если свойство $adapter было public, мы могли бы установить его напрямую.

Комплексная проблема

Если вы когда либо читали о Внедрении зависимости, то вероятнее всего знакомы с терминами “Инверсия управления” или “Принцип инверсии зависимостей”. These are the complex problems that Dependency Injection solves.

Инверсия управления

Инверсия управления как говорится, “инвертирует управление” системы, сохраняя организационное управление полноценно отделяясь от наших объектов. В терминологии Инверсии управления, означает потерю наших зависимостей управляя и реализуя их где либо в системе.

На протяжении нескольких лет, PHP фреймворками была достигнута Инверсия Управления, однако, стоял вопрос, какую часть управления мы инвертируем, и где? Например, MVC фреймворки обычно предоставляют супер объект или, основной контроллер которые другие контроллеры должны расширять, получая доступ к их зависимостям. Это и есть Инверсия управления, однако, вместо потери зависимостей, этот метод просто перемещает их.

Внедрение зависимости позволяет нам более элегантно решить эту проблему, внедряя нужные зависимости, только тогда, когда мы нуждаемся в них, без необходимости сложных зависимостей вообще.

S.O.L.I.D

Принцип единственной ответственности

Принцип единственной ответственности об актерах и высокоуровневой архитектуре. Это состояние когда “Класс A должен иметь только одну причину для изменения.” Это значит, что каждый класс должен иметь ответственность только над одной частью функциональности предоставляемой ПО. Огромнейшая польза от этого подхода в том что он включает переиспользование улучшенного кода. Спроектировав наш класс для выполнения только одной задачи, мы можем использовать (или переиспользовать) его в любой другой программе, не изменяя.

Принцип Открытости/Закрытости

Принцип открытости/закрытости касается проектирования классов и расширений функций. В нем говорится, что “программные объекты (классы, модули, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации”. Это означает, что мы должны проектировать наши модули, классы и функции таким образом, чтобы, когда потребуется новая функциональность, мы не должны были изменять наш существующий код, а скорее писать новый код, который будет использоваться существующим кодом. С практической точки зрения это означает, что мы должны писать классы, которые реализуют интерфейсы и придерживаются их, а затем указывать тип для этих интерфейсов, а не для конкретных классов.

Самым большим преимуществом этого подхода является то, что мы можем очень легко расширить наш код с поддержкой чего-то нового, не изменяя существующий код, а это означает, что мы можем сократить время контроля качества, а риск негативного воздействия на приложение существенно снижается. Мы можем развертывать новый код быстрее и с большей уверенностью.

Принцип замены Лисков

Принцип замены Лисков касается подтипов и наследования. В нем говорится, что “дочерние классы никогда не должны нарушать определения типов родительского класса”. Или, говоря словами Robert C. Martin, “подтипы должны быть взаимозаменяемыми для своих базовых типов”.

Например, если у нас есть интерфейс FileInterface, который определяет метод embed(), и у нас есть классы Audio и Video, которые оба реализуют интерфейс FileInterface, то мы можем ожидать, что использование Метод embed() всегда будет делать то, что мы намеревались. Если позже мы создадим класс PDF или класс Gist, которые реализуют интерфейс FileInterface, мы уже будем знать и понимать, что будет делать метод embed(). Самым большим преимуществом этого подхода является то, что у нас есть возможность создавать гибкие и легко настраиваемые программы, потому что, когда мы меняем один объект типа (например, FileInterface) на другой, нам не нужно ничего менять в нашей программе.

Принцип разделения интерфейса

Принцип разделения интерфейса (ISP) касается связи бизнес-логики с клиентами. В нем говорится, что “Ни один клиент не должен зависеть от методов, которые он не использует”. Это означает, что вместо единого монолитного интерфейса, который должны реализовать все соответствующие классы, мы должны вместо этого предоставить набор меньших интерфейсов, зависящих от концепции, один или несколько из которых реализует соответствующий класс.

Например, классу Car или Bus будет интересен метод SteeringWheel(), а классу Motorcycle или Tricycle — нет. И наоборот, классу Motorcycle или Tricycle будет интересен метод handlebars(), а классу Car или Bus — нет. Нет необходимости, чтобы все эти типы транспортных средств поддерживали как steeringWheel(), так и handlebars(), поэтому мы должны разделить исходный интерфейс.

Принцип инверсии зависимости

Принцип инверсии зависимостей заключается в удалении жестких ссылок между отдельными классами, чтобы можно было использовать новые функции, передавая другой класс. В нем говорится, что нужно “Зависеть от абстракций. Не зависеть от конкретики”. Проще говоря, это означает, что наши зависимости должны быть интерфейсами/контрактами или абстрактными классами, а не конкретными реализациями. Мы можем легко реорганизовать приведенный выше пример, чтобы следовать этому принципу.

<?php
namespace Database;

class Database
{
    public function __construct(protected AdapterInterface $adapter)
    {

    }
}

interface AdapterInterface {}

class MysqlAdapter implements AdapterInterface {}

Теперь у класса Database есть несколько преимуществ, зависящих от интерфейса, а не от конкретизации.

Учтите, что мы работаем в команде, а над адаптером работает коллега. В нашем первом примере нам пришлось бы ждать, пока указанный коллега закончит работу над адаптером, прежде чем мы сможем должным образом смоделировать его для наших модульных тестов. Теперь, когда зависимость представляет собой интерфейс/контракт, мы можем с радостью создать макет этого интерфейса, зная, что наш коллега создаст адаптер на основе этого контракта.

Еще большее преимущество этого метода заключается в том, что наш код стал намного более масштабируемым. Если через год мы решим, что хотим перейти на базу данных другого типа, мы можем написать адаптер, который реализует исходный интерфейс и внедряет его вместо этого, больше не потребуется рефакторинг, поскольку мы можем гарантировать, что адаптер следует контракт, установленный интерфейсом.

Контейнеры

Первая вещь которую вы должны понять о Контейнерах Внедрения Зависимостей, что они не тоже самое что и Внедрение Зависимости. Контейнер - это удобная утилита которая помогает нам реализовывать Внедрение Зависимости, более того, они могут быть и часто неправильно используются для реализации анти-паттерна, Служба Местоположения. Внедряет DI контейнер как Службу Локатор в ваши классы возможно создает сложную зависимость в контейнере чем зависимость которую вы заменяете. Также делает ваш код намного менее прозрачным и в конечном итоге более сложным для тестирования.

Самые современные фреймворки имеют свои собственные Контейнеры Внедрения Зависимостей которые позволяют вам соединять ваши зависимости вместе через конфигурацию. Что это значит на практике, то что вы можете писать код приложения, такой же чистый и разъединённый как это встроенно в фреймворках.

Дальнейшее чтение

Наверх

Базы данных

Много раз ваш PHP код будет использовать базу данных для хранения информации. У вас есть несколько способов соединения и взаимодействия с базой данных. Рекомендованным способом до PHP 5.1.0 было использование родных драйверов, таких как mysqli, pgsql, mssql, и т.д.

Родные драйверы хороши если вы используете только одну базу данных в вашем приложении, но если например, вы используете MySQL и немного MSSQL, или вам необходимо подключиться к базе данных Oracle, то тогда вы не сможете использовать одни и те же драйверы. Вам потребуется изучить новые фирменные API для каждой базы данных — и это может быть глупо.

Расширение MySQL

Расширение mysql для PHP невероятно старое и было заменено двумя другими расширениями:

Разработка mysql была не только остановлена много лет назад но и была официально удалена в PHP 7.0.

Чтобы обезопасить вас от ковыряния в настройках php.ini и увидеть какой модуль вы используете, один из вариантов, воспользоваться поиском для mysql* в вашем редакторе. Ели какая либо из функций таких как mysql_connect() и mysql_query() появятся, то тогда mysql используется.

Даже если вы еще не используете PHP 7.x или более позднии версии, позаботитесь об обновлении как можно скорее, иначе это приведет к большим трудностям, когда обновление PHP произойдет. Лучший вариант — заменить использование mysql на mysqli или PDO в своих приложениях в соответствии с собственным графиком разработки, чтобы потом не торопиться.

Если вы обновляетесь с mysql до mysqli, остерегайтесь ленивых руководств по обновлению, которые предлагают вам просто найти и заменить mysql_* на mysqli_*. Это не только грубое упрощение, но и упущенные преимущества, предоставляемые mysqli, такие как привязка параметров, которая также предлагается в PDO.

Расширение PDO

PDO — это библиотека абстракции подключения к базе данных — встроен в PHP начиная с версии 5.1.0 — который обеспечивает общий интерфейс для общения со многими различными базами данных. Например, вы можете использовать практически идентичный код для взаимодействия с MySQL или SQLite:

<?php
// PDO + MySQL
$pdo = new PDO('mysql:host=example.com;dbname=database', 'user', 'password');
$statement = $pdo->query("SELECT some_field FROM some_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);

// PDO + SQLite
$pdo = new PDO('sqlite:/path/db/foo.sqlite');
$statement = $pdo->query("SELECT some_field FROM some_table");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo htmlentities($row['some_field']);

PDO не будет переводить ваши SQL-запросы или эмулировать отсутствующие функции; это исключительно для подключения к нескольким типам баз данных с одним и тем же API.

Что еще более важно, PDO позволяет вам безопасно вводить чужие входные данные (например, ID) в ваши запросы SQL, не беспокоясь об атаках путем внедрения SQL в базу данных. Это возможно с помощью операторов PDO и связанных параметров.

Предположим, PHP-скрипт получает числовой идентификатор в качестве параметра запроса. Этот идентификатор следует использовать для извлечения записи пользователя из базы данных. Это wrong способ сделать это:

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$pdo->query("SELECT name FROM users WHERE id = " . $_GET['id']); // <-- NO!

Это ужасный код. Вы вставляете необработанный параметр запроса в запрос SQL. Это позволит вам взломать систему в мгновение ока, используя практику под названием SQL Инъекции. Только представьте, что хакер передает оригинальный параметр “id”, вызывая URL-адрес типа http://domain.com/?id=1%3BDELETE+FROM+users. Это установит для переменной $_GET['id'] значение 1;DELETE FROM users, что удалит всех ваших пользователей! Вместо этого вы должны дезинфицировать ввод идентификатора, используя параметры, привязанные к PDO.

<?php
$pdo = new PDO('sqlite:/path/db/users.db');
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = :id');
$id = filter_input(INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT); // <-- filter your data first
(see [Data Filtering](/php-the-right-way/#data_filtering)), especially important for INSERT, UPDATE, etc.
$stmt->bindParam(':id', $id, PDO::PARAM_INT); // <-- Automatically sanitized for SQL by PDO
$stmt->execute();

Это правильный код. Он использует связанный параметр в операторе PDO. Это ускользает от внешнего входного ID до того, как он будет введен в базу данных, предотвращая потенциальные атаки SQL-инъекций.

Для записей, таких как INSERT или UPDATE, особенно важно сначала по-прежнему фильтровать ваши данные и очищать их для других вещей (удаление HTML-тегов, JavaScript и т. д.). PDO будет очищать его только для SQL, а не для вашего приложения.

Вы также должны знать, что соединения с базой данных используют ресурсы, и не было ничего необычного в исчерпании ресурсов, если соединения не были неявно закрыты, однако это было более распространено в других языках. Используя PDO, вы можете неявно закрыть соединение, уничтожив объект, убедившись, что все оставшиеся ссылки на него удалены, т.е. установлены в NULL. Если вы не сделаете это явно, PHP автоматически закроет соединение, когда ваш сценарий завершится - если, конечно, вы не используете постоянные соединения.

Взаимодействие с базами данных

Когда разработчики впервые начинают изучать PHP, они часто заканчивают тем, что смешивают взаимодействие с базой данных с логикой представления, используя код, который может выглядеть следующим образом:

<ul>
<?php
foreach ($db->query('SELECT * FROM table') as $row) {
    echo "<li>".$row['field1']." - ".$row['field1']."</li>";
}
?>
</ul>

Это плохая практика по разным причинам, в основном из-за того, что ее трудно отлаживать, трудно тестировать, трудно читать, и она будет выводить много полей, если вы не установите там ограничение.

Хотя есть много других решений для этого — в зависимости от того, предпочитаете ли вы ООП или функциональное программирование — должен быть какой-то элемент разделения.

<?php
function getAllFoos($db) {
    return $db->query('SELECT * FROM table');
}

$results = getAllFoos($db);
foreach ($results as $row) {
    echo "<li>".$row['field1']." - ".$row['field1']."</li>"; // BAD!!
}

Это хорошее начало. Поместите эти два элемента в два разных файла, и вы получите четкое разделение.

Создайте класс для размещения этого метода, и у вас есть “Модель”. Создайте простой файл .php, чтобы поместить в него логику представления, и у вас будет “Представление”, которое очень близко к [MVC] - общей архитектуре ООП для большинства фреймворков.

foo.php

<?php
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8mb4', 'username', 'password');

// Make your model available
include 'models/FooModel.php';

// Create an instance
$fooModel = new FooModel($db);
// Get the list of Foos
$fooList = $fooModel->getAllFoos();

// Show the view
include 'views/foo-list.php';

models/FooModel.php

<?php
class FooModel
{
    public function __construct(protected PDO $db)
    {

    }

    public function getAllFoos() {
        return $this->db->query('SELECT * FROM table');
    }
}

views/foo-list.php

<?php foreach ($fooList as $row): ?>
    <li><?= $row['field1'] ?> - <?= $row['field1'] ?></li>
<?php endforeach ?>

По сути, это то же самое, что и большинство современных фреймворков, хотя и немного более ручное. Возможно, вам не нужно делать все это каждый раз, но смешивание слишком большого количества логики представления и взаимодействия с базой данных может стать реальной проблемой, если вы когда-нибудь захотите unit-тестирование своего приложения.

Слои абстракций

Многие фреймворки предоставляют свой собственный уровень абстракции, который может располагаться или не располагаться поверх PDO. Они часто будут эмулировать функции одной системы баз данных, отсутствующие в другой, оборачивая ваши запросы в методы PHP, предоставляя вам реальную абстракцию базы данных, а не просто абстракцию соединения, которую предоставляет PDO. Это, конечно, добавит немного накладных расходов, но если вы создаете переносимое приложение, которое должно работать с MySQL, PostgreSQL и SQLite, то небольшие накладные расходы будут оправданы ради чистоты кода.

Некоторые уровни абстракции были построены с использованием стандартов пространств имен PSR-0 или PSR-4, поэтому их можно установить в любое приложение, которое вам нравится:

Наверх

Шаблоны

Шаблоны обеспечивают удобный способ отделения логики контроллера и домена от логики представления. Шаблоны обычно содержат HTML-код вашего приложения, но могут также использоваться для других форматов, таких как XML. Шаблоны часто называют “представлениями”, которые составляют часть второго компонента model-view-controller (MVC) шаблон архитектуры программного обеспечения.

Преимущества

Основным преимуществом использования шаблонов является четкое разделение, которое они создают между логикой представления и остальной частью вашего приложения. Шаблоны несут исключительную ответственность за отображение форматированного контента. Они не несут ответственности за поиск данных, сохранение или другие более сложные задачи. Это приводит к более чистому и удобочитаемому коду, что особенно полезно в командной среде, где разработчики работают над кодом на стороне сервера (контроллеры, модели), а дизайнеры работают над кодом на стороне клиента (разметкой).

Шаблоны также улучшают организацию кода представления. Шаблоны обычно помещаются в папку «представления», каждый из которых определяется в одном файле. Этот подход поощряет повторное использование кода, когда большие блоки кода разбиваются на более мелкие повторно используемые части, часто называемые частичными. Например, верхний и нижний колонтитулы вашего сайта могут быть определены как шаблоны, которые затем включаются до и после каждого шаблона страницы.

Наконец, в зависимости от библиотеки, которую вы используете, шаблоны могут обеспечить большую безопасность, автоматически скрывая пользовательский контент. Некоторые библиотеки даже предлагают песочницу, где разработчикам шаблонов предоставляется доступ только к переменным и функциям из белого списка.

Простые PHP-шаблоны

Простые шаблоны PHP — это просто шаблоны, в которых используется собственный код PHP. Это естественный выбор, поскольку PHP сам по себе является языком шаблонов. Это просто означает, что вы можете комбинировать PHP-код с другим кодом, например HTML. Это выгодно для PHP-разработчиков, так как нет необходимости изучать новый синтаксис, они знают доступные им функции, а их редакторы кода уже имеют встроенную подсветку синтаксиса PHP и автодополнение. Кроме того, простые шаблоны PHP, как правило, работают очень быстро, так как не требуют этапа компиляции.

Каждый современный PHP-фреймворк использует какую-то систему шаблонов, большинство из которых по умолчанию использует простой PHP. Вне фреймворков такие библиотеки, как Plates или Aura.View, упрощают работу с простыми шаблонами PHP, предлагая современные функции шаблонов, такие как наследование, макеты и расширения.

Простой пример обычного PHP-шаблона

Использование библиотеки Plates.

<?php // user_profile.php ?>

<?php $this->insert('header', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->escape($name)?></p>

<?php $this->insert('footer') ?>

Пример простых шаблонов PHP с использованием наследования

Использование библиотеки Plates.

<?php // template.php ?>

<html>
<head>
    <title><?=$title?></title>
</head>
<body>

<main>
    <?=$this->section('content')?>
</main>

</body>
</html>
<?php // user_profile.php ?>

<?php $this->layout('template', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->escape($name)?></p>

Скомпилированные шаблоны

Хотя PHP превратился в зрелый объектно-ориентированный язык, он не сильно улучшился как язык шаблонов. Скомпилированные шаблоны, такие как Twig, Brainy или Smarty*, заполняют этот пробел, предлагая новый синтаксис, специально предназначенный для создания шаблонов. От автоматического экранирования до наследования и упрощенных структур управления — скомпилированные шаблоны разработаны так, чтобы их было проще писать, чище читать и безопаснее использовать. Скомпилированные шаблоны можно даже использовать на разных языках, Mustache является хорошим примером этого. Поскольку эти шаблоны должны быть скомпилированы, производительность снижается незначительно, однако при правильном кэшировании это минимально.

*Хотя Smarty предлагает автоматическое экранирование, эта функция НЕ включена по умолчанию.

Простой пример скомпилированного шаблона

Использование библиотеки Twig.

{% include 'header.html' with {'title': 'User Profile'} %}

<h1>User Profile</h1>
<p>Hello, {{ name }}</p>

{% include 'footer.html' %}

Пример скомпилированных шаблонов с использованием наследования

Использование библиотеки Twig.

// template.html

<html>
<head>
    <title>{% block title %}{% endblock %}</title>
</head>
<body>

<main>
    {% block content %}{% endblock %}
</main>

</body>
</html>
// user_profile.html

{% extends "template.html" %}

{% block title %}User Profile{% endblock %}
{% block content %}
    <h1>User Profile</h1>
    <p>Hello, {{ name }}</p>
{% endblock %}

Дальнейшее чтение

Статьи и учебные пособия

Библиотеки

Наверх

Ошибки и исключения

Ошибки

Во многих языках программирования с большим количеством исключений всякий раз, когда что-то пойдет не так, будет выдано исключение. Это, безусловно, жизнеспособный способ сделать что-то, но PHP — это язык программирования с “легким исключением”. Хотя у него есть исключения, и большая часть ядра начинает использовать их при работе с объектами, большая часть самого PHP будет пытаться продолжать обработку независимо от того, что происходит, если только не произойдет фатальная ошибка.

Например:

$ php -a
php > echo $foo;
Notice: Undefined variable: foo in php shell code on line 1

Это всего лишь ошибка уведомления, и PHP с радостью продолжит работу. Это может сбивать с толку тех, кто работает с языками с “тяжелыми исключениями”, потому что, например, ссылка на отсутствующую переменную в Python вызовет исключение:

$ python
>>> print foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined

Единственная реальная разница в том, что Python сходит с ума из-за любой мелочи, так что разработчики могут быть уверены, что любая потенциальная проблема или пограничный случай обнаружены, в то время как PHP продолжит обработку, если не произойдет что-то экстремальное, и в этот момент он выдаст ошибку. ошибка и сообщите об этом.

Серьезность ошибки

PHP имеет несколько уровней серьезности ошибок. Тремя наиболее распространенными типами сообщений являются ошибки, уведомления и предупреждения. Они имеют разные уровни серьезности; E_ERROR, E_NOTICE и E_WARNING. Ошибки — это фатальные ошибки во время выполнения, обычно они вызваны ошибками в вашем коде и должны быть исправлены, поскольку они приводят к остановке выполнения PHP. Уведомления — это консультативные сообщения, вызванные кодом, который может вызывать или не вызывать проблемы во время выполнения скрипта, выполнение не останавливается. Предупреждения являются не фатальными ошибками, выполнение скрипта не будет остановлено.

Другим типом сообщений об ошибках, сообщаемых во время компиляции, являются сообщения E_STRICT. Эти сообщения используются для предложения изменений в вашем коде, чтобы обеспечить наилучшее взаимодействие и совместимость с будущими версиями PHP.

Изменение поведения отчетов об ошибках PHP

Изменение поведения отчетов об ошибках PHP Отчеты об ошибках можно изменить с помощью настроек PHP и/или вызовов функций PHP. Используя встроенную функцию PHP error_reporting(), вы можете установить уровень ошибок на время выполнения скрипта, передав одну из предопределенных констант уровня ошибок, что означает, что если вы хотите видеть только ошибки и предупреждения, но не уведомления - то вы можете настроить это:

<?php
error_reporting(E_ERROR | E_WARNING);

Вы также можете контролировать, будут ли ошибки отображаться на экране (удобно для разработки) или скрыты и зарегистрированы (удобно для производства). Дополнительную информацию об этом можно найти в разделе Отчеты об ошибках.

Встроенное подавление ошибок

Вы также можете указать PHP подавлять определенные ошибки с помощью оператора контроля ошибок @. Вы помещаете этот оператор в начало выражения, и любая ошибка, являющаяся прямым результатом выражения, замалчивается.

<?php
echo @$foo['bar'];

Это выведет $foo['bar'], если он существует, но просто вернет null и ничего не напечатает, если переменная $foo или ключ 'bar' не существует. Без оператора контроля ошибок это выражение могло бы создать ошибку PHP Notice: Undefined variable:foo или PHP Notice: Undefined index: bar.

Это может показаться хорошей идеей, но есть несколько нежелательных компромиссов. PHP обрабатывает выражения, использующие @, менее эффективно, чем выражения без @. Преждевременная оптимизация может быть корнем всех аргументов программирования, но если производительность особенно важна для вашего приложения/библиотеки, важно понимать влияние оператора контроля ошибок на производительность.

Во-вторых, оператор контроля ошибок полностью проглатывает ошибку. Ошибка не отображается, и ошибка не отправляется в журнал ошибок. Кроме того, стандартные / производственные PHP-системы не имеют возможности отключить оператор контроля ошибок. Хотя вы можете быть правы в том, что ошибка, которую вы видите, безобидна, другая, менее безобидная ошибка будет такой же бесшумной.

Если есть способ избежать оператора подавления ошибок, вы должны рассмотреть его. Например, наш код выше можно было бы переписать так:

<?php
// Null Coalescing Operator
echo $foo['bar'] ?? '';

Один из случаев, когда подавление ошибок может иметь смысл, это когда fopen() не может найти файл для загрузки. Вы можете проверить существование файла, прежде чем пытаться его загрузить, но если файл будет удален после проверки и до fopen() (что может показаться невозможным, но это может случиться), тогда fopen() вернет false и выдаст ошибку. Этот потенциально это то, что PHP должен решить, но это один из случаев, когда подавление ошибок может показаться единственным правильным решением.

Ранее мы упоминали, что в стандартной системе PHP нет возможности отключить оператор контроля ошибок. Однако Xdebug имеет ini-настройку xdebug.scream, которая отключает оператор контроля ошибок. Вы можете установить это через файл php.ini следующим образом.

xdebug.scream = On

Вы также можете установить это значение во время выполнения с помощью функции ini_set

<?php
ini_set('xdebug.scream', '1')

Это наиболее полезно, когда вы отлаживаете код и подозреваете, что информационная ошибка подавляется. Используйте scream с осторожностью и как временный инструмент отладки. Есть много кода библиотеки PHP, который может не работать с отключенным оператором контроля ошибок.

ErrorException

PHP способен быть ”насыщенным исключениями” языком программирования, и для переключения требуется всего несколько строк кода. В основном вы можете бросать ваши “ошибки” как “исключения” используя класс ErrorException, который расширяет класс Exception.

Эта общая практика реализована большим количеством современных фреймворков, таких как Symfony и Laravel. В режиме отладки (или режиме разработки) оба этих фреймворка будут отображать хорошие и понятные трассировки стека.

Также есть несколько доступных пакетов для улучшенной обработки и отчетности ошибок и исключений. Как Whoops!, который идет вместе с установкой Laravel по-умолчанию, и может быть использован вообще с любым фреймворком.

Выкидывая ошибки как исключения при разработке, вы можете обрабатывать их лучше чем обычной ситуации, и если вы видите исключение во время разработки, вы можете оборачивать его в конструкцию catch с определенными инструкциями о том как обработать ситуацию. Каждое исключение которое вы мгновенно отлавливаете, делает ваше приложение немного более надежным.

Больше подробной информации об этом и о том как использовать ErrorException для обработки ошибок можно найти по ссылкам ErrorException Class.

Исключения

Исключения являются стандартной частью большинства популярных языков программирования, но PHP-программисты часто упускают их из виду.

Такие языки, как Ruby, очень сильно насыщенны исключениями, поэтому всякий раз, когда что-то идет не так, например, сбой HTTP-запроса или запрос БД, или даже если ресурс изображения не может быть найден, Ruby (или используемые gems) выдает исключение на экран, что означает, что вы сразу узнаете, что произошла ошибка.

PHP сам по себе довольно слаб с этим, и вызов file_get_contents() обычно просто дает вам FALSE и предупреждение. Многие старые PHP-фреймворки, такие как CodeIgniter, просто вернут false, запишут сообщение в свои проприетарные журналы и, возможно, позволят вам использовать такой метод, как $this->upload->get_error(), чтобы увидеть, что пошло не так. Проблема здесь в том, что вам нужно искать ошибку и проверять документы, чтобы увидеть, какой метод ошибки используется для этого класса, вместо того, чтобы делать это предельно очевидным.

Другая проблема — когда классы автоматически выбрасывают ошибку на экран и выходят из процесса. Когда вы делаете это, вы мешаете другому разработчику динамически обрабатывать эту ошибку. Исключения должны создаваться, чтобы разработчики знали об ошибке; затем они могут выбрать, как с этим справиться. Например.:

<?php
$email = new Fuel\Email;
$email->subject('My Subject');
$email->body('How the heck are you?');
$email->to('guy@example.com', 'Some Guy');

try
{
    $email->send();
}
catch(Fuel\Email\ValidationFailedException $e)
{
    // The validation failed
}
catch(Fuel\Email\SendingFailedException $e)
{
    // The driver could not send the email
}
finally
{
    // Executed regardless of whether an exception has been thrown, and before normal execution resumes
}

SPL Исключения

Общий класс Exception предоставляет разработчику очень мало контекста отладки; однако, чтобы исправить это, можно создать специализированный тип Exception, создав подкласс общего класса Exception:

<?php
class ValidationException extends Exception {}

Это означает, что вы можете добавить несколько блоков catch и по-разному обрабатывать разные исключения. Это может привести к созданию множества пользовательских исключений, некоторых из которых можно было бы избежать с помощью исключений SPL, представленных в расширении SPL.

Если, например, вы используете магический метод __call() и запрошен недопустимый метод, то вместо того, чтобы генерировать стандартное исключение, которое является расплывчатым, или создавать специальное исключение только для этого, вы можете просто throw new BadMethodCallException;.

Наверх

Безопасность

Лучшие ресурсы которые я нашел по PHP безопасности The 2018 Guide to Building Secure PHP Software by Paragon Initiative.

Безопасность Веб-приложения

Очень важно для каждого разработчика PHP изучить основы безопасности веб-приложений, которые можно разбить на несколько широких тем:

  1. Разделение кода и данных.
    • Когда данные выполняются как код, вы получаете SQL-инъекцию, межсайтовый скриптинг, включение локальных/удаленных файлов и т. д.
    • Когда код печатается в виде данных, вы получаете утечку информации (раскрытие исходного кода или, в случае программ на C, достаточно информации, чтобы обойти ASLR).
  2. Логика приложения.
    • Отсутствуют элементы управления аутентификацией или авторизацией.
    • Проверка ввода.
  3. Рабочая среда.
    • Версии PHP.
    • Сторонние библиотеки.
    • Операционная система.
  4. Криптографические недостатки.

Есть плохие люди, готовые и желающие использовать ваше веб-приложение. Важно, чтобы вы приняли необходимые меры предосторожности для повышения безопасности вашего веб-приложения. К счастью, замечательные люди из The Open Web Application Security Project (OWASP) составили исчерпывающий список известных проблем с безопасностью и способы защиты от них. Это необходимо прочитать разработчику, заботящемуся о безопасности. Survive The Deep End: PHP Security Padraic Brady — еще одно хорошее руководство по безопасности веб-приложений для PHP.

Хэширование пароля

В конце концов каждый создает приложение PHP, которое зависит от входа пользователя в систему. Имена пользователей и пароли хранятся в базе данных и позже используются для аутентификации пользователей при входе в систему.

Важно, чтобы вы правильно делали hash паролей перед их сохранением. Хеширование и шифрование — это две очень разные вещи, которые часто путают.

Хеширование — необратимая односторонняя функция. Это создает строку фиксированной длины, которую невозможно обратить вспять. Это означает, что вы можете сравнить хэш с другим, чтобы определить, пришли ли они оба из одной и той же исходной строки, но вы не можете определить исходную строку. Если пароли не хэшируются и к вашей базе данных обращается неавторизованная третья сторона, все учетные записи пользователей теперь скомпрометированы.

В отличие от хеширования, шифрование обратимо (если у вас есть ключ). Шифрование полезно в других областях, но это плохая стратегия для безопасного хранения паролей.

Пароли также должны быть индивидуально salted путем добавления случайной строки к каждому паролю перед хешированием. Это предотвращает атаки по словарю и использование «радужных таблиц» (обратный список криптографических хэшей для обычных паролей).

Хеширование и добавление соли жизненно важны, так как часто пользователи используют один и тот же пароль для нескольких сервисов, а качество пароля может быть низким.

Кроме того, вам следует использовать специализированный алгоритм_хеширования_пароля, а не быструю универсальную криптографическую хеш-функцию (например, SHA256). Краткий список приемлемых алгоритмов хеширования паролей (по состоянию на июнь 2018 г.) для использования:

К счастью, в настоящее время PHP делает это легко.

Хеширование паролей с password_hash

В PHP 5.5 был введен password_hash(). В настоящее время он использует BCrypt, самый сильный алгоритм, который в настоящее время поддерживается PHP. Однако в будущем он будет обновлен для поддержки большего количества алгоритмов по мере необходимости. Библиотека password_compat была создана для обеспечения прямой совместимости с PHP >= 5.3.7.

Ниже мы хешируем строку, а затем проверяем хеш на новую строку. Поскольку наши две исходные строки различаются (“secret-password” и “bad-password”), этот вход не удастся.

<?php
require 'password.php';

$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // Correct Password
} else {
    // Wrong password
}

password_hash() позаботится о добавлении соли для вас. Соль хранится вместе с алгоритмом и “расценивается” как часть хэша. password_verify() извлекает это, чтобы определить, как проверить пароль, поэтому вам не нужно отдельное поле базы данных для хранения ваших солей.

Фильтрация данных (Data Filtering)

Никогда (никогда) не доверяйте чужому вводу, введенному в ваш PHP-код. Всегда очищайте и проверяйте внешний ввод перед его использованием в коде. Функции filter_var() и filter_input() могут очищать текст и проверять текстовые форматы (например, адреса электронной почты).

Внешний ввод может быть любым: $_GET и $_POST формируют входные данные, некоторые значения в суперглобальном массиве $_SERVER и тело HTTP-запроса через fopen('php://input', 'r'). Помните, что внешний ввод не ограничивается данными формы, отправленными пользователем. Загруженные и выгруженные файлы, значения сеанса, данные cookie и данные сторонних веб-сервисов также являются посторонними входными данными.

Хотя сторонние данные можно хранить, комбинировать и обращаться к ним позже, они по-прежнему являются внешними входными данными. Каждый раз, когда вы обрабатываете, выводите, объединяете или включаете данные в свой код, спрашивайте себя, правильно ли фильтруются данные и можно ли им доверять.

Данные могут фильтроваться по-разному в зависимости от их назначения. Например, когда нефильтрованный посторонний ввод передается в вывод HTML-страницы, он может выполнять HTML и JavaScript на вашем сайте! Это известно как межсайтовый скриптинг (XSS) и может быть очень опасной атакой. Один из способов избежать XSS — очистить все пользовательские данные перед их выводом на вашу страницу, удалив теги HTML с помощью функции strip_tags() или экранировав символы со специальным значением в соответствующие объекты HTML с помощью htmlentities() или функции htmlspecialchars().

Другой пример — передача параметров для выполнения в командной строке. Это может быть чрезвычайно опасно (и, как правило, это плохая идея), но вы можете использовать встроенную функцию escapeshellarg() для очистки аргументов выполняемой команды.

Последний пример — прием внешнего ввода для определения файла для загрузки из файловой системы. Это можно использовать, изменив имя файла на путь к файлу. Вам нужно удалить "/", "../", null bytes или другие символы из пути к файлу, чтобы он не мог загружать скрытые, непубличные или конфиденциальные файлы.

Очищение (Sanitization)

Очищение удаляет (или экранирует) недопустимые или небезопасные символы из внешнего ввода.

Например, вам необходимо очистить чужой ввод, прежде чем подключать ввод в HTML или вставлять его в необработанный SQL запрос. Когда вы используете связанные параметры с PDO, оно будет обеззараживать ввод для вас.

Иногда требуется разрешить несколько безопасных HTML тегов на входе, когда вкладываете их в HTML страницу. Это очень сложно сделать и многие избегают этого, используя более ограниченное форматирование, такое как Markdown или BBCode, хотя библиотеки белого списка, такие как HTML Purifier существуют по этой причине.

Смотреть очищающие фильтры

Десериализация (Unserialization)

Опасно unserialize() данные от пользователей или других недоверенных ресурсов

Опасно unserialize() данные от пользователей или других ненадежных источников. Это может позволить злоумышленникам создавать экземпляры объектов (с определяемыми пользователем свойствами), чьи деструкторы будут выполняться, даже если сами объекты не используются. Поэтому вам следует избегать десериализации ненадежных данных.

Bспользуйте безопасный стандартный формат обмена данными, такой как JSON (через json_decode и json_encode), если вам нужно передать пользователю сериализованные данные.

Валидация (Validation)

Валидация гарантирует, что иностранный ввод соответствует вашим ожиданиям. Например, вы можете захотеть подтвердить адрес электронной почты, номер телефона или возраст при обработке заявки на регистрацию.

Смотреть Фильтры валидации данных

Файлы конфигурации

При создании файлов конфигурации для ваших приложений рекомендуется использовать один из следующих методов:

Register Globals

ПРИМЕЧАНИЕ: Начиная с PHP 5.4.0 параметр register_globals был удален и больше не может использоваться. Это включено только в качестве предупреждения для всех, кто находится в процессе обновления устаревшего приложения.

Когда параметр конфигурации register_globals включен, он делает несколько типов переменных (включая переменные из $_POST, $_GET и $_REQUEST) доступными в глобальной области вашего приложения. Это может легко привести к проблемам с безопасностью, поскольку ваше приложение не может эффективно определить, откуда поступают данные.

Например: $_GET['foo'] будет доступен через $foo, который может переопределять объявленные переменные.

Если вы используете PHP < 5.4.0, убедитесь, что функция register_globals выключена.

Отчет об ошибках

Регистрация ошибок может быть полезна для поиска проблемных мест в вашем приложении, но она также может предоставить информацию о структуре вашего приложения для внешнего мира. Чтобы эффективно защитить ваше приложение от проблем, которые могут быть вызваны выводом этих сообщений, вам необходимо по-разному настроить сервер для разработки и производства (в реальном времени).

Разработка

Чтобы показать все возможные ошибки во время разработки, настройте следующие параметры в файле php.ini:

display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On

Передача значения -1 покажет все возможные ошибки, даже если в будущих версиях PHP будут добавлены новые уровни и константы. Константа E_ALL также ведет себя так, начиная с PHP 5.4. - php.net

Константа уровня ошибки E_STRICT была введена в 5.3.0 и не является частью E_ALL, однако она стала частью E_ALL в 5.4.0. Что это значит? С точки зрения сообщения о каждой возможной ошибке в версии 5.3 это означает, что вы должны использовать либо -1, либо E_ALL | E_STRICT.

Отчет о каждой возможной ошибке по версии PHP

Производство

Чтобы скрыть ошибки в вашей производственной среде, настройте файл php.ini следующим образом:

display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On

При использовании этих настроек ошибки по-прежнему будут регистрироваться в журналах ошибок веб-сервера, но не будут отображаться пользователю. Дополнительные сведения об этих настройках см. в руководстве по PHP:

Наверх

Тестирование

Написание автоматических тестов для вашего PHP-кода считается лучшей практикой и может привести к созданию хорошо построенных приложений. Автоматические тесты — отличный инструмент, позволяющий убедиться, что ваше приложение не сломается, когда вы вносите изменения или добавляете новые функции, и их нельзя игнорировать.

Существует несколько различных типов инструментов тестирования (или фреймворков), доступных для PHP, которые используют разные подходы — все они пытаются избежать ручного тестирования и необходимости в больших командах обеспечения качества, просто чтобы убедиться, что последние изменения не нарушили существующую функциональность.

Разработка через тестирование

Из Wikipedia:

Разработка через тестирование (TDD) — это процесс разработки программного обеспечения, основанный на повторении очень короткого цикла разработки: сначала разработчик пишет неудачный автоматизированный тестовый пример, который определяет желаемое улучшение или новую функцию, а затем создает код для прохождения этого теста. и, наконец, реорганизует новый код в соответствии с приемлемыми стандартами. Кент Бек, которому приписывают разработку или «повторное открытие» этой техники, заявил в 2003 году, что TDD поощряет простые конструкции и внушает доверие.

Существует несколько различных типов тестирования, которые вы можете выполнить для своего приложения::

Unit Тестирование

Модульное тестирование — это подход к программированию, гарантирующий, что функции, классы и методы работают должным образом, с момента их создания на протяжении всего цикла разработки. Проверяя значения, входящие и исходящие из различных функций и методов, вы можете убедиться, что внутренняя логика работает правильно. Используя Dependency Injection и создавая “фиктивные” классы и заглушки, вы можете убедиться, что зависимости правильно используются для еще лучшего покрытия тестами.

Когда вы создаете класс или функцию, вы должны создать модульный тест для каждого поведения, которое они должны иметь. На самом базовом уровне вы должны убедиться, что он ошибается, если вы отправляете ему неверные аргументы, и убедитесь, что он работает, если вы отправляете ему действительные аргументы. Это поможет гарантировать, что при внесении изменений в этот класс или функцию позже в цикле разработки старая функциональность продолжит работать должным образом. Единственной альтернативой этому может быть var_dump() в файле test.php, который нет возможности создать приложение - большое или маленькое.

Еще одно применение модульных тестов — вклад в открытый исходный код. Если вы можете написать тест, который показывает неработающую функциональность (т. е. дает сбой), а затем исправить его и показать, что тест пройден, вероятность того, что патчи будут приняты, гораздо выше. Если вы запускаете проект, который принимает запросы на вытягивание, вы должны предложить это как требование.

PHPUnit — де-факто среда тестирования для написания модульных тестов для PHP-приложений, но есть несколько альтернатив:

Интеграционное тестирование

Из Wikipedia:

Интеграционное тестирование (иногда называемое интеграцией и тестированием, сокращенно «I&T») — это этап тестирования программного обеспечения, на котором отдельные программные модули объединяются и тестируются как группа. Это происходит после модульного тестирования и перед проверочным тестированием. Интеграционное тестирование принимает в качестве входных данных модули, прошедшие модульное тестирование, группирует их в более крупные агрегаты, применяет тесты, определенные в плане интеграционного тестирования, к этим агрегатам и предоставляет на выходе интегрированную систему, готовую к системному тестированию.

Многие из тех же инструментов, которые можно использовать для модульного тестирования, можно использовать и для нтеграционного тестирования, поскольку используются многие из тех же принципов.

Функциональное тестирование

Иногда также известное как приемочное тестирование, функциональное тестирование состоит из использования инструментов для создания автоматических тестов, которые фактически используют ваше приложение, а не просто проверки того, что отдельные блоки кода ведут себя правильно и что отдельные блоки могут правильно общаться друг с другом. Эти инструменты обычно работают с реальными данными и имитируют реальных пользователей приложения.

Инструменты Функционального Тестирования

Развитие, основанное на поведении

Существует два разных типа разработки, ориентированной на поведение (BDD): SpecBDD и StoryBDD. SpecBDD фокусируется на техническом поведении кода, а StoryBDD фокусируется на бизнес-поведении или функциях или взаимодействиях. В PHP есть фреймворки для обоих типов BDD.

С помощью StoryBDD вы пишете удобочитаемые истории, описывающие поведение вашего приложения. Затем эти истории можно запускать как настоящие тесты для вашего приложения. Фреймворк, используемый в PHP-приложениях для StoryBDD, — это Behat, который вдохновлен проектом Ruby Cucumber и реализует Gherkin DSL для описания поведения функций.

С помощью SpecBDD вы пишете спецификации, описывающие, как должен вести себя ваш фактический код. Вместо тестирования функции или метода вы описываете, как должна вести себя эта функция или метод. PHP предлагает для этой цели фреймворк PHPSpec. Эта структура основана на проекте RSpec для Ruby.

BDD ссылки

Дополнительные инструменты тестирования

Помимо отдельных фреймворков для тестирования и поведения, существует также ряд универсальных фреймворков и вспомогательных библиотек, полезных для любого предпочтительного подхода.

Ссылки на инструменты

Наверх

Серверы и развертывание

Приложения PHP можно развертывать и запускать на производственных веб-серверах несколькими способами.

Платформа как сервис (PaaS)

PaaS предоставляет системную и сетевую архитектуру, необходимую для запуска приложений PHP в Интернете. Это практически не требует настройки для запуска PHP-приложений или PHP-фреймворков.

В последнее время PaaS стал популярным методом развертывания, размещения и масштабирования PHP-приложений любого размера. Вы можете найти список поставщиков PHP PaaS “Платформа как услуга” в нашем разделе ресурсов.

Виртуальные или выделенные серверы

Если вы знакомы с системным администрированием или заинтересованы в его изучении, виртуальные или выделенные серверы дадут вам полный контроль над производственной средой вашего приложения.

nginx и PHP-FPM

PHP через встроенный в PHP диспетчер процессов FastCGI (FPM) очень хорошо сочетается с nginx, который представляет собой легкий высокопроизводительный веб-сервер. Он использует меньше памяти, чем Apache, и может лучше обрабатывать больше одновременных запросов. Это особенно важно для виртуальных серверов, у которых не так много свободной памяти.

Apache и PHP

PHP и Apache имеют долгую совместную историю. Apache легко настраивается и имеет множество доступных модулей для расширения функциональности. Это популярный выбор для общих серверов и простая настройка для PHP-фреймворков и приложений с открытым исходным кодом, таких как WordPress. К сожалению, по умолчанию Apache использует больше ресурсов, чем nginx, и не может обрабатывать столько посетителей одновременно.

Apache есть несколько возможных конфигураций для запуска PHP. Наиболее распространенным и простым в настройке является prefork MPM с mod_php. Хотя это не самое эффективное использование памяти, это самое простое в работе и использовании. Это, вероятно, лучший выбор, если вы не хотите слишком углубляться в аспекты администрирования сервера. Обратите внимание, что если вы используете mod_php, вы ДОЛЖНЫ использовать prefork MPM.

В качестве альтернативы, если вы хотите выжать из Apache больше производительности и стабильности, вы можете воспользоваться той же системой FPM, что и nginx, и запустить worker MPM или event MPM с mod_fastcgi или mod_fcgid. Эта конфигурация будет значительно более эффективной с точки зрения использования памяти и намного быстрее, но ее настройка потребует больше усилий.

Если вы используете Apache 2.4 или более позднюю версию, вы можете использовать mod_proxy_fcgi, чтобы получить отличную производительность, которую легко настроить.

Общие серверы

Популярность PHP зависит от общих серверов. Трудно найти хост без установленного PHP, но убедитесь, что это последняя версия. Общие серверы позволяют вам и другим разработчикам развертывать веб-сайты на одном компьютере. Положительным моментом этого является то, что он стал дешевым товаром. Недостатком является то, что вы никогда не знаете, какой шум собираются создать ваши соседние арендаторы; загрузка сервера или открытие дыр в безопасности являются основными проблемами. Если бюджет вашего проекта позволяет избежать общих серверов, вы должны это сделать.

Убедитесь, что ваши общие серверы предлагают последние версии PHP.

Создание и развертывание вашего приложения

Если вы обнаружите, что вручную вносите изменения в схему базы данных или запускаете тесты перед обновлением файлов (вручную), подумайте дважды! С каждой дополнительной ручной задачей, необходимой для развертывания новой версии вашего приложения, увеличивается вероятность потенциально фатальных ошибок. Независимо от того, имеете ли вы дело с простым обновлением, комплексным процессом сборки или даже стратегией непрерывной интеграции, автоматизация сборки — ваш друг.

Среди задач, которые вы, возможно, захотите автоматизировать, следующие:

Инструменты развертывания

Инструменты развертывания можно описать как набор сценариев, которые выполняют общие задачи развертывания программного обеспечения. Инструмент развертывания не является частью вашего программного обеспечения, он воздействует на ваше программное обеспечение ‘извне’.

Существует множество инструментов с открытым исходным кодом, которые помогут вам в автоматизации сборки и развертывании, некоторые из них написаны на PHP, а другие нет. Это не должно удерживать вас от их использования, если они лучше подходят для конкретной работы. Вот несколько примеров:

Phing может управлять процессом упаковки, развертывания или тестирования из XML-файла сборки. Phing (который основан на Apache Ant) предоставляет богатый набор задач, обычно необходимых для установки или обновления веб-приложения, и может быть дополнен дополнительными пользовательскими задачами, написанными на PHP. Это надежный и надежный инструмент, который существует уже давно, однако этот инструмент может показаться немного старомодным из-за того, как он работает с конфигурацией (XML-файлы).

Capistrano — это система для программистов среднего и продвинутого уровня, позволяющая выполнять команды структурированным, повторяемым образом на одной или нескольких удаленных машинах. Он предварительно настроен для развертывания приложений Ruby on Rails, однако с его помощью вы можете успешно развертывать системы PHP. Успешное использование Capistrano зависит от практических знаний Ruby и Rake.

Ansistrano — это пара ролей Ansible для простого управления процессом развертывания (развертывание и откат) для таких приложений, как PHP, Python и Ruby. Это порт Ansible для Capistrano. Он уже используется довольно многими PHP-компаниями.

Deployer — это инструмент развертывания, написанный на PHP. Это просто и функционально. Функции включают в себя параллельный запуск задач, атомарное развертывание и поддержание согласованности между серверами. Доступны рецепты общих задач для Symfony, Laravel, Zend Framework и Yii. Статья Юнеса Рафи Простое развертывание приложений PHP с помощью Deployer — отличное руководство по развертыванию вашего приложения с помощью этого инструмента.

Magallanes — еще один инструмент, написанный на PHP с простой настройкой в ​​файлах YAML. Он поддерживает несколько cерверов и сред, атомарное развертывание и имеет несколько встроенных задач, которые вы можете использовать для общих инструментов и сред.

Дальнейшее чтение

Подготовка сервера

Управление серверами и их настройка могут оказаться непростой задачей при наличии большого количества серверов. Существуют инструменты для решения этой проблемы, поэтому вы можете автоматизировать свою инфраструктуру, чтобы убедиться, что у вас есть нужные серверы и что они правильно настроены. Они часто интегрируются с более крупными поставщиками облачного хостинга (Amazon Web Services, Heroku, DigitalOcean и т. д.) для управления экземплярами, что значительно упрощает масштабирование приложения.

Ansible — это инструмент, который управляет вашей инфраструктурой через файлы YAML. С ним легко начать работу, и он может управлять сложными и крупномасштабными приложениями. Существует API для управления облачными экземплярами, и он может управлять ими через динамическую инвентаризацию с использованием определенных инструментов.

Puppet — это инструмент со своим языком и типами файлов для управления серверами и конфигурациями. Его можно использовать в режиме мастер/клиент или в режиме «без мастера». В режиме «мастер/клиент» клиенты будут опрашивать центральные мастера на наличие новой конфигурации с заданными интервалами и при необходимости обновлять себя. В режиме без мастера вы можете вносить изменения в свои узлы.

Chef — это мощная платформа системной интеграции на основе Ruby, с помощью которой вы можете создать всю свою серверную среду или виртуальные блоки. Он хорошо интегрируется с Amazon Web Services через их сервис под названием OpsWorks.

Дальнейшее чтение

Непрерывная интеграция

Непрерывная интеграция — это практика разработки программного обеспечения, при которой члены команды часто интегрируют свою работу, обычно каждый человек интегрирует по крайней мере ежедневно, что приводит к множеству интеграций в день. Многие команды считают, что такой подход приводит к значительному сокращению проблем интеграции и позволяет команде быстрее разрабатывать целостное программное обеспечение. – Martin Fowler

Существуют разные способы реализации непрерывной интеграции для PHP. Travis CI проделал огромную работу, сделав непрерывную интеграцию реальностью даже для небольших проектов. Travis CI — это размещенная служба непрерывной интеграции. Может быть интегрировано с GitHub и предлагает первоклассную поддержку многих языков, включая PHP. GitHub имеет непрерывную интеграцию рабочих процессов с GitHub Actions.

Дальнейшее чтение

Наверх

Виртуализация

Запуск вашего приложения в разных средах разработки и производства может привести к появлению странных ошибок при запуске. Также сложно поддерживать актуальность различных сред разработки с одной и той же версией для всех библиотек, используемых при работе с командой разработчиков.

Если вы разрабатываете для Windows и выполняете развертывание в Linux (или в любой другой системе, отличной от Windows) или разрабатываете в команде, вам следует рассмотреть возможность использования виртуальной машины. Это звучит сложно, но помимо широко известных сред виртуализации, таких как VMware или VirtualBox, существуют дополнительные инструменты, которые могут помочь вам настроить виртуальную среду за несколько простых шагов.

Vagrant

Vagrant помогает вам создавать виртуальные ящики поверх известных виртуальных сред и настраивает эти среды на основе одного файла конфигурации. Эти ящики можно настроить вручную, или вы можете использовать программное обеспечение для «подготовки», такое как Puppet или Chef, чтобы сделать это за вас. Подготовка базового блока — отличный способ убедиться, что несколько блоков настроены одинаково, и избавляет вас от необходимости поддерживать сложные списки команд «настройки». Вы также можете «уничтожить» свою базовую коробку и воссоздать ее без множества ручных операций, что упрощает создание «новой» установки.

Vagrant создает папки для совместного использования вашего кода между вашим хостом и вашей виртуальной машиной, что означает, что вы можете создавать и редактировать свои файлы на своем хост-компьютере, а затем запускать код внутри своей виртуальной машины.

Docker

Docker — облегченная альтернатива полноценной виртуальной машине — называется так потому, что все дело в «контейнерах». Контейнер — это строительный блок, который в простейшем случае выполняет одну конкретную работу, т.е. запуск веб-сервера. «Образ» — это пакет, который вы используете для сборки контейнера — у Docker есть полный репозиторий.

Типичное приложение LAMP может иметь три контейнера: веб-сервер, процесс PHP-FPM и MySQL. Как и в случае с общими папками в Vagrant, вы можете оставить файлы приложений там, где они есть, и указать Docker, где их найти.

Вы можете создавать контейнеры из командной строки (см. пример ниже) или, для простоты обслуживания, создать файл docker-compose.yml для своего проекта, указав, какие из них создавать и как они взаимодействуют друг с другом.

Docker может помочь, если вы разрабатываете несколько веб-сайтов и хотите разделить их, установив каждый на свою виртуальную машину, но у вас нет необходимого дискового пространства или времени, чтобы поддерживать все в актуальном состоянии. Это эффективно: установка и загрузка выполняются быстрее, вам нужно хранить только одну копию каждого образа, как бы часто он ни использовался, контейнерам требуется меньше оперативной памяти и они используют одно и то же ядро ​​ОС, поэтому вы можете иметь больше серверов, работающих одновременно, и это занимает много времени. секунд, чтобы остановить и запустить их, не нужно ждать полной загрузки сервера.

Пример: запуск ваших PHP-приложений в Docker

После установки docker на вашем компьютере вы можете запустить веб-сервер с помощью одной команды. Ниже будет загружена полнофункциональная установка Apache с последней версией PHP, сопоставление /path/to/your/php/files с корнем документа, который вы можете просмотреть по адресу http://localhost:8080:

docker run -d --name my-php-webserver -p 8080:80 -v /path/to/your/php/files:/var/www/html/ php:apache

Это инициализирует и запустит ваш контейнер. -d заставляет его работать в фоновом режиме. Чтобы остановить и запустить его, просто запустите docker stop my-php-webserver и docker start my-php-webserver (остальные параметры снова не нужны).

Узнать больше о Docker

Приведенная выше команда показывает быстрый способ запуска базового сервера. Вы можете сделать гораздо больше (и тысячи готовых образов в Docker Hub). Потратьте время на изучение терминологии и прочтите Руководство пользователя Docker, чтобы извлечь из него максимальную пользу, и не запускайте случайно загруженный код, не проверив его безопасность — неофициальные образы могут не иметь последней версии патчей безопасности. Если вы сомневаетесь, придерживайтесь официальных репозиториев.

Сайт PHPDocker.io автоматически сгенерирует все файлы, необходимые для полнофункционального стека LAMP/LEMP, включая выбранную вами версию PHP и расширения.

Наверх

Кеширование

PHP сам по себе довольно быстр, но узкие места могут возникнуть, когда вы устанавливаете удаленные соединения, загружаете файлы и т. д. К счастью, существуют различные инструменты для ускорения определенных частей вашего приложения или уменьшения количества раз выполнения этих различных трудоемких задач. нужно бежать.

Opcode Cache

Когда файл PHP выполняется, он должен быть сначала скомпилирован в opcodes (инструкции машинного языка для ЦП). Если исходный код не изменился, коды операций будут такими же, поэтому этот шаг компиляции становится пустой тратой ресурсов процессора.

Кэш opcode операций предотвращает избыточную компиляцию, сохраняя opcodes в памяти и повторно используя их при последовательных вызовах. Обычно он сначала проверяет подпись или время модификации файла, если были какие-либо изменения.

Вполне вероятно, что кеш opcode операции значительно улучшит скорость вашего приложения. Начиная с PHP 5.5 есть встроенный — Zend OPcache. В зависимости от вашего пакета/дистрибутива PHP он обычно включен по умолчанию — проверьте opcache.enable и вывод phpinfo(), чтобы убедиться. Для более ранних версий есть расширение PECL.

Подробнее о кэшах opcodes:

Кэширование объектов

Бывают случаи, когда может быть полезно кэшировать отдельные объекты в вашем коде, например, с данными, получение которых дорого, или вызовами базы данных, результат которых вряд ли изменится. Вы можете использовать программное обеспечение для кэширования объектов, чтобы хранить эти фрагменты данных в памяти для чрезвычайно быстрого доступа в дальнейшем. Если вы сохраняете эти элементы в хранилище данных после их извлечения, а затем извлекаете их непосредственно из кэша для последующих запросов, вы можете добиться значительного повышения производительности, а также снизить нагрузку на серверы баз данных.

Многие из популярных решений для кэширования байт-кода также позволяют кэшировать пользовательские данные, так что есть еще больше причин воспользоваться ими. APCu и WinCache предоставляют API для сохранения данных из вашего PHP-кода в их кэш-память.

Наиболее часто используемыми системами кэширования объектов памяти являются APCu и memcached. APCu — отличный выбор для кэширования объектов, он включает в себя простой API для добавления ваших собственных данных в кэш памяти и очень прост в настройке и использовании. Единственное реальное ограничение APCu заключается в том, что он привязан к серверу, на котором установлен. С другой стороны, Memcached устанавливается как отдельная служба, и к ней можно получить доступ по сети, а это означает, что вы можете хранить объекты в сверхбыстром хранилище данных в центральном расположении, и множество различных систем могут извлекать из него данные.

Обратите внимание, что при запуске PHP как (Fast-) CGI-приложения внутри вашего веб-сервера каждый процесс PHP будет иметь свой собственный кеш, т. е. данные APCu не будут использоваться совместно вашими рабочими процессами. В этих случаях вы можете вместо этого рассмотреть возможность использования memcached, так как он не привязан к процессам PHP.

В сетевой конфигурации APCu обычно превосходит memcached по скорости доступа, но memcached сможет масштабироваться быстрее и дальше. Если вы не ожидаете, что ваше приложение будет запущено на нескольких серверах, или вам не нужны дополнительные функции, которые предлагает memcached, тогда APCu, вероятно, будет вашим лучшим выбором для кэширования объектов.

Пример логики с использованием APCu:

<?php
// check if there is data saved as 'expensive_data' in cache
$data = apcu_fetch('expensive_data');
if ($data === false) {
    // data is not in cache; save result of expensive call for later use
    apcu_add('expensive_data', $data = get_expensive_data());
}

print_r($data);

Обратите внимание, что до версии PHP 5.5 было расширение APC которое предоставляло как кеш объектов, так и кеш байт-кода. APCu — это проект по переносу кеша объектов APC на PHP 5.5+, поскольку PHP теперь имеет встроенный кеш байт-кода (OPcache).

Узнайте больше о популярных системах кэширования объектов

Наверх

Документирование кода

PHPDoc

PHPDoc — это неофициальный стандарт для комментирования PHP-кода. Доступно множество различных tags. Полный список тегов и примеров можно найти в PHPDoc manual.

Ниже приведен пример того, как вы можете задокументировать класс с помощью нескольких методов;

<?php
/**
 * @author A Name <a.name@example.com>
 * @link https://www.phpdoc.org/docs/latest/index.html
 */
class DateTimeHelper
{
    /**
     * @param mixed $anything Anything that we can convert to a \DateTime object
     *
     * @throws \InvalidArgumentException
     *
     * @return \DateTime
     */
    public function dateTimeFromAnything($anything)
    {
        $type = gettype($anything);

        switch ($type) {
            // Some code that tries to return a \DateTime object
        }

        throw new \InvalidArgumentException(
            "Failed Converting param of type '{$type}' to DateTime object"
        );
    }

    /**
     * @param mixed $date Anything that we can convert to a \DateTime object
     *
     * @return void
     */
    public function printISO8601Date($date)
    {
        echo $this->dateTimeFromAnything($date)->format('c');
    }

    /**
     * @param mixed $date Anything that we can convert to a \DateTime object
     */
    public function printRFC2822Date($date)
    {
        echo $this->dateTimeFromAnything($date)->format('r');
    }
}

Документация для класса в целом имеет тег @author и тег @link. Тег @author используется для документирования автора кода и может повторяться для документирования нескольких авторов. Тег @link используется для ссылки на веб-сайт, указывающий на связь между веб-сайтом и кодом.

Внутри класса у первого метода есть тег @param, документирующий тип, имя и описание параметра, передаваемого методу. Кроме того, он имеет теги @return и @throws для документирования типа возвращаемого значения и любых исключений, которые могут быть выброшены соответственно.

Второй и третий методы очень похожи и имеют один тег @param, как и первый метод. Важным отличием блока документации второго и третьего методов является включение/исключение тега @return. @return void явно сообщает нам, что возврата нет; Исторически пропуск оператора @return void также приводит к тому же действию (без возврата).

Наверх

Ресурсы

Из источника

Люди, на которых стоит подписаться

Трудно найти интересных и знающих членов сообщества PHP, когда вы только начинаете. Вы можете найти сокращенный список членов сообщества PHP, с которого можно начать:

PHP PaaS Providers

Фреймворки

Вместо того, чтобы изобретать велосипед, многие PHP-разработчики используют фреймворки для создания веб-приложений. Фреймворки абстрагируются от многих низкоуровневых проблем и предоставляют полезные, простые в использовании интерфейсы для выполнения общих задач.

Вам не нужно использовать фреймворк для каждого проекта. Иногда правильный путь — простой PHP, но если вам нужен фреймворк, то доступны три основных типа:

Микрофреймворки, по сути, представляют собой оболочку для максимально быстрой маршрутизации HTTP-запроса к обратному вызову, контроллеру, методу и т. д., и иногда они поставляются с несколькими дополнительными библиотеками для помощи в разработке, такими как базовые оболочки базы данных и тому подобное. Они широко используются для создания удаленных HTTP-сервисов.

Многие фреймворки добавляют значительное количество функций поверх того, что доступно в микрофреймворке; они называются платформами полного стека. Они часто поставляются в комплекте с ORM, пакетами аутентификации и т. д.

Компонентные фреймворки представляют собой наборы специализированных и одноцелевых библиотек. Разрозненные фреймворки на основе компонентов можно использовать вместе, чтобы создать микро- или фреймворк с полным стеком.

Компоненты

Как упоминалось выше, «Компоненты» — это еще один подход к общей цели создания, распространения и реализации общего кода. Существуют различные репозитории компонентов, два основных из которых:

С обоими этими репозиториями связаны инструменты командной строки, помогающие в процессах установки и обновления, и более подробно они описаны в разделе Dependency Management.

Существуют также фреймворки, основанные на компонентах, и поставщики компонентов, которые вообще не предлагают фреймворков. Эти проекты предоставляют еще один источник пакетов, которые в идеале практически не зависят от других пакетов или конкретных фреймворков.

Например, вы можете использовать FuelPHP Validation package, не используя саму структуру FuelPHP.

Illuminate components Laravel станут лучше отделены от фреймворка Laravel. На данный момент выше перечислены только те компоненты, которые лучше всего отделены от фреймворка Laravel.

Другие полезные ресурсы

Cheatsheets

Больше best practices

Новости сообщества PHP и веб-разработчиков

Вы можете подписаться на еженедельные информационные бюллетени, чтобы быть в курсе новых библиотек, последних новостей, событий и общих объявлений, а также периодически публикуемых дополнительных ресурсов:

Есть также еженедельники на других платформах, которые могут вас заинтересовать; вот список некоторых.

PHP universe

Video Tutorials

YouTube Channels

Книги

Есть много книг по PHP; к сожалению, некоторые из них сейчас довольно старые и уже не точны. В частности, избегайте книг по “PHP 6”, версии, которой теперь никогда не будет. Следующим основным выпуском PHP после 5.6 был «PHP 7» частично из-за этого.

Этот раздел призван стать живым документом для рекомендуемых книг по разработке PHP в целом. Если вы хотите, чтобы ваша книга была добавлена, отправьте PR, и она будет рассмотрена на предмет актуальности.

Бесплатные книги

Платные книги

Наверх

Сообщество

Сообщество PHP столь же разнообразно, сколь и велико, и его члены готовы и желают поддерживать новых PHP-программистов. Подумайте о том, чтобы присоединиться к вашей локальной группе пользователей PHP (PUG) или посетить более крупные конференции PHP, чтобы узнать больше о передовых методах, показанных здесь. Вы можете общаться в IRC на канале #phpc на irc.freenode.com и следить за учетной записью @phpc в Twitter или Mastodon. Отправляйтесь туда, знакомьтесь с новыми разработчиками, изучайте новые темы и, прежде всего, заводите новых друзей! Другие ресурсы сообщества включают StackOverflow.

Читать официальный календарь событий PHP

PHP User Groups

Если вы живете в большом городе, скорее всего, поблизости есть группа пользователей PHP. Вы можете легко найти местный PUG на PHP.ug. Альтернативными источниками могут быть Meetup.com или поиск группа пользователей php рядом со мной с помощью вашей любимой поисковой системы (например, Google). Если вы живете в маленьком городке, местного PUG может и не быть; если это так, начните один!

Особо следует отметить две глобальные группы пользователей: NomadPHP и PHPWomen. NomadPHP два раза в месяц предлагает групповые онлайн-встречи пользователей с презентациями ведущих спикеров PHP-сообщества. PHPWomen — это неэксклюзивная группа пользователей, изначально ориентированная на женщин в мире PHP. Членство открыто для всех, кто поддерживает более разнообразное сообщество. PHPWomen предоставляет сеть для поддержки, наставничества и обучения и в целом способствует созданию «дружественной к женщинам» и профессиональной атмосферы.

Читать о User Groups на PHP Wiki

PHP Конференции

Сообщество PHP также организовывает крупнейшие региональные и национальные конференции в многих странах по всему миру. Хорошо известные участники PHP сообщества обычно выступают на этих крупнейших событиях, так что это большая возможность научиться прямо у лидеров индустрии.

Найти PHP конференцию

ElePHPants

ElePHPant замечательный талисман проекта PHP со слоном в его дизайне. Первоначально был спроектирован для проекта PHP в 1998 Vincent Pontier - духовный отец тысяч elePHPants по всему миру и 10-ю годами позже также родилась восхитительная игрушка плюшевого слонёнка. Сегодня elePHPants представляется на множестве PHP конференции с множеством PHP разработчиков и на их компьютерах для веселья и вдохновения.

Интервью с Vincent Pontier