Introduction
Following recent post comparing package module concept supported by other languages I took an opportunity to gather my thoughts and write own vision of syntax for that concept in PHP.
Currently in PHP package term is used only by Composer — tool for dependency management — the only thing about such use is that the package itself is a virtual thing living only for dependency management and has no meaning at runtime.
In languages like: Python, Java, Rust; module has name which is also a part of symbol path name used to resolve specific module symbols.
- in Rust modules — when use foo::bar; is used bar symbol from foo module is imported to local symbol table. All symbols with no explicit visibility are considered private thus public symbols require explicit pub visibility modifier so they can be imported through use declaration.
- in Python modules — when from foo import bar is used symbol bar from foo module is imported to local symbol table. It is possible to import all desired symbol names using __init__.py with __all__ table which includes all symbols to import using from foo import * statement.
- in PHP namespaces — when use Foo\Bar; is used class Foo\Bar is used to resolve usage of Bar class in code. Although PHP use declaration looks similar to other languages, namespaces in PHP are simple prefixes with no meaning.
Module identity.
Looking at given other language examples can observe that part of import/use declaration uses module name as a prefix. This doesn’t happen in PHP cause namespaces are thin object prefixes to do that extracting package from use declaration is needed and cannot be done without providing an additional FQN delimiter which splits module / package name from symbol name while resolving symbol names and additional package declaration in each file consisting with proper package name.
Using package declaration in source file could give parser desired package name and symbol name, for eg.:
const FOO_BAR = 1; function foo(Bar $bar) <>
Looking for proper delimiter a : could be a good example for that. For eg.:
This concept is nearly compatible with current PHP syntax and provides no breaking change. There could be an issue when trying to use group use syntax simply looks weird.
Other concept could follow Python semantics:
Going further might wanna try with grouping from statement, for eg.:
$bar = new Bar(); foo($bar); echo FOO_BAR;
Although using from keyword to indicate package to reflect on looks pretty neat we still have an issue when trying to resolve FQN name without explicit use clause.
This is the case where a : delimiter may help and this is already considered syntax error so won’t break already existing code.
Conclusion
When looking for proper package syntax in use this looks quite easy to achieve. The issue could be in a way of declaring package either it would be through a specified language construct like in Python or Java or by sort of a separate configuration file like in Rust
IMHO it has very big potential and proposes almost non-invasive transformation way.
Модульность в php-проекте
При разработке php-проекта часто возникает потребность разделить его на небольшие части — модули. Слово «модули» довольно расплывчато и в разных случаях может трактоваться по своему, но в данном случае речь идёт только о том, что модуль — это некий код, который может представлять собой единую сущность.
Программно модуль может быть набором классов, функций и файлов. Он может содержать html-код, css-стили, js-скрипты, изображения и т.п. То есть не важно как в реальности реализован модуль, главное это то, что мы можем его воспринимать как единое целое.
Хорошим примером модульности может служить CodeIgniter (1-3 версий). В нём модуль представляет собой «обертку» над классами или функциям. То есть вместо того, чтобы писать сложный код подключения, инициализации и т.п., используется универсальный загрузчик, например так:
«Loader» берёт на себя задачу по подключению файла класса и его инициализацию (и даже поддержку как singleton). Дальнейшее использование сводится к обращению к методам класса encrypt в достаточно простом варианте:
$encrypted_string = $this->encrypt->encode('My secret message');
В данном случае encrypt можно рассматривать именно как модуль CodeIgniter. Если бы речь шла о простом классе, то нам нужно было бы самостоятельно вызвать new , предварительно прописав подключение файла и т.д.
В данном примере encrypt очень простой класс, состоящий из одного файла, но гораздо чаще модуль состоит из множества классов и файлов. В этом разрезе становится очевидно, что модуль должен представлять собой уже каталог, где и будут размещаться все файлы модуля.
В таком случае подключение модуля становится не такой простой задачей, ведь среди всех файлов нужно найти его «точку входа». В разных php-фреймоворках задача решается различными способами. Самый простой — это размещение в каталоге модуля предопределенного файла, например bootstrap.php . То есть когда нужно выполнить подключение модуля, то загрузчик ищет этот «начальный» файл. Такой файл необходим, поскольку модуль может быть достаточно сложным и нужно обеспечить его корректную инициализацию. Также в этом файле обычно размещают и автозагрузчик классов.
Структура каталогов модулей этого варианта может быть такой:
modules/ module1/ . каталоги и файлы модуля. bootstrap.php module2/ . каталоги и файлы модуля. bootstrap.php
Такой подход используется например в Fuel PHP Framework при подключени модулей packages.
Главный момент здесь в том, что файл bootstrap.php выполняет роль «буфера», через который происходит «адаптация» классов модуля к php-проекту.
Приведу небольшой абстрактный пример. Например нам нужно вывести слайдер на какой-то странице сайта. Сам слайдер требует предопределенную html-разметку. Также он содержит базовые css-стили и jQuery-плагин. Модуль должен уметь не просто сгенерировать готовый html-код, но и быть управляемым, например поддерживать все параметры jQuery-плагина через php-переменные, а также дополнительные опции, вроде своих css-классов.
Понятно, что такой модуль должен состоять из каких-то php-классов или функций, но также в модуле следует разместить и все «сопутствующие» файлы: css и js, например:
modules/ MySlider/ js/ . js-файлы . css/ . css-файлы . functions/ . файлы модуля . MySlider.php — например основной класс bootstrap.php — «точка входа»
Файл bootstrap.php может содержать только загрузчик классов модуля. Поэтому работа с модулем может выглядеть так:
load_module('MySlider'); // здесь и подключается bootstrap.php $slider = new MySlider(); . дальше работа с объектом $slider
Данный вариант хорош тем, что используется единый подход к работе с модулем. Но при этом возникает проблема для модулей, которые не имеют единой «точки входа». Например модуль представляет собой просто набор не связанных классов. Примером может послужить база данных. В зависимости от типа базы (MySQL, sqlite, pdo), будут использованы различные классы.
Обычно такие модули выделяют в специализированные каталоги, вроде «core», «database», «classes» и т.п. То есть формально это теже самые модули, но которые работают по другим «правилам». Назвать это проблемой нельзя, хотя такой подход ломает «стройность» проекта.
Но на самом деле, проблема не только в этом. На сегодняшний день существует огромное количество готовых php-проектов, решающих практически все задачи. То есть вместо того, чтобы «изобретать велосипед», можно воспользоваться уже готовой разработкой.
Чтобы оценить объем возможностей приведу как пример The PHP Package Repository, где размещены описания на почти 220 тысяч (. ) проектов. Этот репозиторий используется менеджером Composer, который довольно часто используется в php-фреймворках.
Таким образом, если мы хотим воспользоваться чужой разработкой, то возникает довольно серьезная проблема встраивания чужого проекта в свой модуль. Дело в том, что php-разработчики используют самые разные подходы из-за чего с каждым проектом приходится разбираться индивидуально.
Например, кто-то использует классы, кто-то только функции. Кто-то использует namespace, кто-то нет. Кто-то придерживается PSR-4, кто-то нет.
В итоге структура модуля получит примерно такой вид:
modules/ MySlider/ distr/ . файлы сторонних дистрибутивов . MySlider.php — например основной класс bootstrap.php — «точка входа»
Каталог distr содержит дистрибутивы сторонних разработок, которые используются в данном модуле.
У такого подхода есть два серьезных минуса. Первый — если несколько модулей используют один и тот же дистрибутив, то его придётся дублировать или создавать опасную связь между модулями. Второй — если дистрибутив имеет новую версию, то в своих модулях необходимо как-то организовать их обновление. При большом количестве модулей это может оказаться проблемой.
Очевидным решением будет вынос всех сторонних дистрибутивов в отдельный каталог. Здесь самое время ещё раз упомянуть добрым словом Composer, который и решает данную задачу. С помощью специального файла конфигурации Composer не просто скачивает все необходимые дистрибутивы, но и создаёт для них специальный автозагрузчик классов. Дистрибутивы Composer сохраняет в каталог vendor , где будет готовый файл autoload.php .
При такой схеме модуль может уже не иметь bootstrap.php (если используется Composer и подключен его autoload.php ). При этом уже не нужны никакие load_module() .
Модули в php-проекте должны располагаться в отдельном каталоге и соответствовать стандарту PSR-4, что позволит упростить их использование (через автозагрузчик).
При этом каталог vendor нужно оставить для Composer. Поскольку он автоматически формирует autoload.php , то в своем проекте достаточно будет лишь проверить наличие этого файла и подключить его.
Все дистрибутивы, которые могут использоваться повторно, но не относящиеся к Composer, нужно выделить отдельно, например в каталог distr . Здесь придётся как-то решить вопрос с автозагрузкой, но индивидуально для каждого дистрибутива, поскольку они слишком уж «разношерстные».
vendor/ . каталог Composer'а autoload.php distr/ . каталоги дистрибутивов свои и чужие, вне Composer'а autoload.php — для тех, которые не соответствуют PSR-4 modules/ — соответствие PSR-4 module1/ . каталоги и файлы модуля module1.php module2/ . каталоги и файлы модуля module2.php index.php — основной файл php-проекта composer.json — конфигурация Composer'а
В заключении немного кода для index.php , чтобы стало понятно как это может работать в реальности.
); // MY APPLICATION SAMPLE // modules $o1 = new Module1('Module1'); // submodule $o2 = new Module1\SubModule1('SubModule1'); // distr/Project1 $o3 = new Project1('distr/Project1'); // module use distr/Project1 $o4 = new Module1\MProject1('MProject1 use Project1'); // composer Parsedown $Parsedown = new Parsedown(); echo $Parsedown->text('Hello _Parsedown_!'); // module use composer Parsedown $o5 = new Module1\MParsedown('This is _MParsedown_!'); # end of file
- Вначале задаются константы для каталогов.
- После определяются автозагрузчики классов.
- Дальше показаны примеры использования в разных вариантах.
Вы можете скачать полный набор файлов. Классы простые для демонстрации, а для Composer’а я «прикрутил» парсер Parsedown. Вначале он вызывается сам по себе, а после из класса модуля.
PHP Modules (PHP Extensions)
PHP modules are extensions mostly written in C language. They can be compiled with PHP to enable static loading (as part of the binary file) or dynamic loading (with the php.ini directive: extension=modulename.so).
There are different types of modules – core extensions, bundled extensions that are still part of the PHP package and fully external extensions. The last ones are not part of the PHP core and not included in the package.
Shared hosting and Managed VPS servers by SuperHosting.BG support a vast number of PHP modules. Some of them are static and do not require activation to start being used. To enable PHP dynamically loading modules, you need to access cPanel » PHP Manager by SuperHosting » PHP modules management.
The phpinfo function allows you to view all statically loading modules. All content from the Configure Command field, starting with –enable or –with is a compiled PHP module.
The dynamically loading modules (modulename.so) can be viewed in cPanel » PHP Manager by SuperHosting » PHP modules management.
PECL Extensions
There is a repository for PHP extensions called PECL (PHP Extension Community Library). Other PHP extensions exist, as they are different from PECL, but PECL is the official repository for the most popular and widely used PHP extensions.
Help documentation for PECL extensions is available on the official PHP website.
Some PECL extensions have been integrated in the PHP core, i.e. filter (pecl/php.net), json (pecl/php.net), xmlreader (pecl/php.net) and many others.
PECL modules differ from PEAR packages, although they use the same distribution system (PEAR).