PHP модуль — это просто
Недавно мы опубликовали визард для VisualStudio, с помощью которого можно создать экстеншн в пару кликов мыши. Теперь с помощью него мы напишем наши два первых расширения: «Привет, мир» и «вытащим иконку из exe».
Сразу прошу прощение, что очень сильно задержал статью, но жизненные обстоятельства вынудили это сделать, но они исключительно уважительные.
Итак, начнем.
1. Качаем «волшебника» для VS 2008.
По ссылке из темы VS wizard: PHP extension
Устанавливаем его, это произойдет автоматически.
2. Скачиваем необходимые для сборки файлы.
Нужны лишь исходники PHP и бинарники. Скачиваем 5.2.11 версию обоих файлов
Разархивируем php-5.2.11-Win32.zip в C:\PHPDEV\php-5.2.11-Win32 и php-5.2.11.tar.bz2 в C:\PHPDEV\php-5.2.11.
3. Запускаем VS, создаем новый проект.
И вводим его название. Пути настраивать не придется 😉
После этого видим главное окно студии, смотрим, что же там в файлах.
4. Создаем функции.
Как уже замечено, то скелет полностью создан, осталось лишь написать функции и прописать их.
В проекте есть тестовая функция, раскоментируем ее.
Для справки:
1) Заголовок функции должен быть в h файле. В виде PHP_FUNCTION(имя_функции).
2) Определение — в c файле.
3) Функция должна быть прописана в function_entry test_functions в c файле. В виде PHP_FE(имя_функции, NULL).
Как написать саму функцию, я расскажу позднее. А пока ограничимся этой:
6. Продвинутое создание функций.
Разберемся, как принимать значения из функций и передавать их.
Сложность заключается в том, что функция принимает и возвращает различные значения различных типов.
Рассмотрим пример, при котором принимается строка и целое и возвращается строка.
Как можно увидеть, здесь использованы следующие конструкции:
zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, строка_формата, адреса_дляполучаемых_значений)
RETURN_*;
Рассмотрим 2 таблицы, в первой указаны принимаемые PHP-типы и соответствующие им форматы и типы C.
Во второй возвращаемые значения с соответствующими конструкциями.
Дабы не утруждать себя, я прилагаю фотографии таблиц из книги, которую я всем советую прочитать.
Еще раз смотрим на примеры выше и понимаем, как все просто.
Кстати хочу обратитьт внимание, что выделение памяти ведется через e-аналоги функций c(emalloc, efree, erealloc), это нужно для того, чтобы GC PHP сам смог «прибраться».
7. Полезный пример. Вытащим иконку из exe.
Конечно можно написать это и на PHP, но работы будет больше. А тут уже есть необходимые заголовки.
Напишем код на C(писал одепт bl4de):
В файле pe.h мы видим использование кусков кода из библиотек windows: они нам помогут, а прямое их подключение невозможно, мы ведь пишем кроссплатформенное расширение, не так ли? 😉
В pe.c же пишем код. Как и понятно, мы будем оборачивать функцию void _extract_ico(char *filename, char *filenameOut).
Модульность в php-проекте
При разработке php-проекта часто возникает потребность разделить его на небольшие части — модули. Слово «модули» довольно расплывчато и в разных случаях может трактоваться по своему, но в данном случае речь идёт только о том, что модуль — это некий код, который может представлять собой единую сущность.
Программно модуль может быть набором классов, функций и файлов. Он может содержать html-код, css-стили, js-скрипты, изображения и т.п. То есть не важно как в реальности реализован модуль, главное это то, что мы можем его воспринимать как единое целое.
Хорошим примером модульности может служить CodeIgniter (1-3 версий). В нём модуль представляет собой «обертку» над классами или функциям. То есть вместо того, чтобы писать сложный код подключения, инициализации и т.п., используется универсальный загрузчик, например так:
«Loader» берёт на себя задачу по подключению файла класса и его инициализацию (и даже поддержку как singleton). Дальнейшее использование сводится к обращению к методам класса encrypt в достаточно простом варианте:
В данном примере encrypt очень простой класс, состоящий из одного файла, но гораздо чаще модуль состоит из множества классов и файлов. В этом разрезе становится очевидно, что модуль должен представлять собой уже каталог, где и будут размещаться все файлы модуля.
Структура каталогов модулей этого варианта может быть такой:
Такой подход используется например в Fuel PHP Framework при подключени модулей packages.
Главный момент здесь в том, что файл bootstrap.php выполняет роль «буфера», через который происходит «адаптация» классов модуля к php-проекту.
Приведу небольшой абстрактный пример. Например нам нужно вывести слайдер на какой-то странице сайта. Сам слайдер требует предопределенную html-разметку. Также он содержит базовые css-стили и jQuery-плагин. Модуль должен уметь не просто сгенерировать готовый html-код, но и быть управляемым, например поддерживать все параметры jQuery-плагина через php-переменные, а также дополнительные опции, вроде своих css-классов.
Понятно, что такой модуль должен состоять из каких-то php-классов или функций, но также в модуле следует разместить и все «сопутствующие» файлы: css и js, например:
Файл bootstrap.php может содержать только загрузчик классов модуля. Поэтому работа с модулем может выглядеть так:
Данный вариант хорош тем, что используется единый подход к работе с модулем. Но при этом возникает проблема для модулей, которые не имеют единой «точки входа». Например модуль представляет собой просто набор не связанных классов. Примером может послужить база данных. В зависимости от типа базы (MySQL, sqlite, pdo), будут использованы различные классы.
Обычно такие модули выделяют в специализированные каталоги, вроде «core», «database», «classes» и т.п. То есть формально это теже самые модули, но которые работают по другим «правилам». Назвать это проблемой нельзя, хотя такой подход ломает «стройность» проекта.
Но на самом деле, проблема не только в этом. На сегодняшний день существует огромное количество готовых php-проектов, решающих практически все задачи. То есть вместо того, чтобы «изобретать велосипед», можно воспользоваться уже готовой разработкой.
Чтобы оценить объем возможностей приведу как пример The PHP Package Repository, где размещены описания на почти 220 тысяч (. ) проектов. Этот репозиторий используется менеджером Composer, который довольно часто используется в php-фреймворках.
Таким образом, если мы хотим воспользоваться чужой разработкой, то возникает довольно серьезная проблема встраивания чужого проекта в свой модуль. Дело в том, что php-разработчики используют самые разные подходы из-за чего с каждым проектом приходится разбираться индивидуально.
Например, кто-то использует классы, кто-то только функции. Кто-то использует namespace, кто-то нет. Кто-то придерживается PSR-4, кто-то нет.
В итоге структура модуля получит примерно такой вид:
Каталог distr содержит дистрибутивы сторонних разработок, которые используются в данном модуле.
У такого подхода есть два серьезных минуса. Первый — если несколько модулей используют один и тот же дистрибутив, то его придётся дублировать или создавать опасную связь между модулями. Второй — если дистрибутив имеет новую версию, то в своих модулях необходимо как-то организовать их обновление. При большом количестве модулей это может оказаться проблемой.
Что получается в итоге?
Модули в php-проекте должны располагаться в отдельном каталоге и соответствовать стандарту PSR-4, что позволит упростить их использование (через автозагрузчик).
Вы можете скачать полный набор файлов. Классы простые для демонстрации, а для Composer’а я «прикрутил» парсер Parsedown. Вначале он вызывается сам по себе, а после из класса модуля.
Модульный PHP монолит: рецепт приготовления
Статья написана по мотивам моего доклада на митапе. В нем я рассказываю историю того, как мы взяли и не распилили монолит на микросервисы, и что сделали вместо этого.
На тот момент наша команда работала над приложением, начало которому было положено еще в 2009 году не искушенными в архитектуре студентами. К 2018 это уже был типичный big ball of mud (большой ком грязи), или, этакий «монолит-копролит», как выразился один наш коллега 🙂 Думаю, многим знакомо. Конкретно у нас это вызывало следующие проблемы:
Сложность независимого изменения компонентов приложения, соответственно сложность масштабирования разработки и хрупкость всей системы.
Проблемы с покрытием модульными тестами. Так как наша бизнес-логика зависела от деталей реализации, нам пришлось ввести тестовую базу данных, и модульные тесты перестали быть модульными, к тому же выполнялись долго.
Сложность перехода на другие инструменты, фреймворки, технологии. В 2018 году мы все еще использовали версию фреймворка 2009 года.
Моральная неудовлетворенность разработчиков, работающих с запутанным кодом, тоже не стоит сбрасывать со счетов.
В конце 2018 в корне поменялись бизнес-требования к одной из подсистем приложения, от которой зависели почти все остальные, и нам предстояло полностью ее переписать. Вот тут-то и обнажились все архитектурные проблемы, и это был шанс, чтобы решить их. А возможность параллельной разработки нескольких фич разными подкомандами вообще стало одним из главных требований бизнеса к новой архитектуре.
Вариант с вынесением подсистемы в микросервис(ы) и последующим распилом монолита казался очень заманчивым — попробовать что-то новое, модное, да ещё и решающее задачи бизнеса и проблемы существующего приложения. Тем более, что часть разработчиков уже прошли курсы по golang и просто рвались в бой. Но у нас не было готовой инфраструктуры для микросервисов, не было команды devops и опыта не только проектирования, но и поддержки микросервисной архитектуры. Все это требовало дополнительного времени (а вот его как раз и не было) и ресурсов, а уровень предсказуемости результата был невысок. К тому же очевидно, что и в этой архитектуре можно получить тот же big ball of mud, только распределенный.
Поэтому нам показалось достаточно разумным сначала применить принципы микросервисной архитектуры к монолиту:
провести строгие границы между подсистемами и определить четкие контракты взаимодействия;
упорядочить направления зависимостей;
Тогда если в будущем и возникнет реальная необходимость вынести какую-то из подсистем в микросервис, то это будет сделать гораздо проще. Так родилась идея модульного монолита, которая, конечно же, была не нова.
Итак, что же у нас получилось.
Декомпозиция. Domain-Driven Design.
Для разделения приложения на модули мы использовали принципы и приемы предметно-ориентированного проектирования (Domain-Driven Design).
Domain-Driven Design декларирует, что у каждого приложения есть предметная область. В каждой предметной области можно выделить небольшие подобласти, которые называются ограниченными контекстами. Например, в e-commerce приложении можно выделить следующие ограниченные контексты: customer, product catalog, ordering, shipping. Каждому контексту будет соответствовать свой модуль. Физически он будет представлять собой отдельную папку со своим namespace’ом. Замечу, что БД по-прежнему остаётся общей, но об этом чуть ниже.
Подробнее про Domain-Driven Design можно почитать в книгах Eric Evans «Domain-Driven Design: Tackling Complexity in the Heart of Software» и Vaughn Vernon «Implementing Domain-Driven Design».
Чистая архитектура
Как уже упоминалось, одной из причин проблем нашего монолита была сильная зависимость бизнес-логики от деталей реализации. Для ее устранения мы воспользовались принципами «чистой архитектуры» (Роберт Мартин «Чистая архитектура. Искусство разработки программного обеспечения»), выделив внутри каждого модуля слои: domain, application, infrustructure.
Итак, основные принципы чистой архитектуры:
приложение строится вокруг независимой от других слоев объектной модели;
внутренние слои определяют интерфейсы, внешние слои содержат реализации интерфейсов;
направление зависимостей — от внешних слоев к внутренним;
при пересечении границ слоя данные должны преобразовываться.
Правило зависимостей (Dependency Rule) — ключевое правило. Для достижения такого направления зависимостей нам на помощь придет принцип инверсии зависимостей (dependency inversion). И если в традиционной трехслойной архитектуре бизнес-логика непосредственно зависела от слоя доступа к данным, то в чистой архитектуре она зависит только от интерфейса, который определен в этом же слое, а его реализация находится в слое инфраструктуры. Таким образом бизнес-сервис не зависит от инфраструктуры. В рантайме, конечно, вместо интерфейса будет подставлена конкретная реализация, и этого можно добиться за счет механизма dependency injection, который предоставляют, наверно, все современные фреймворки.
Итак, чистая архитектура дает нам независимость бизнес-слоя от:
Соответственно, разработка модульных тестов сильно упрощается. Дополнительный бонус — ускорение выполнения модульных тестов, так как отсутствуют обращения к физической БД и шаги по ее инициализации тестовыми данными. Также мы получаем относительную простоту замены каких-либо реализаций.
При такой архитектуре структура проекта выглядит следующим образом:
Каждый модуль выделен в отдельную папку, также как и каждый слой внутри модуля выделен в отдельную папку. Есть папка, которую мы назвали Common, там находится библиотечный код, не специфичный для какого либо домена.
Взаимодействие модулей. Anti-corruption Layer.
Еще один шаблон, который мы использовали, это Anti-corruption Layer. Он также впервые был введен Эриком Эвансом в книге «Domain-Driven Design»:
Create an isolating layer to provide clients with functionality in terms of their own domain model. The layer talks to the other system through its existing interface, requiring little or no modification to the other system. Internally, the layer translates in both directions as necessary between the two models.
ACL наших модулей представлен адаптерами, которые обращаются напрямую к API другого модуля, но преобразуют входные данные в модели вызываемого модуля, а выходные данные в модели вызывающего модуля.
Например, есть модуль заказов Ordering и модуль доставки Shipping. Сервис доставки получает информацию о заказах по их ids от модуля заказов. Для этого в модуле Ordering мы выделяем API, который будет являться контрактом для взаимодействия с ним. Сам модуль получает данные из таблиц общей БД, компонует их в DTO и через API отдает модулю Shipping. Если мы будем напрямую использовать это DTO в модуле доставки, мы создадим очень жесткую связь. При изменении контракта модуля заказов нам придется менять все места использования моделей этого модуля в других модулях, чего нам хотелось бы избежать. К тому же в модуле Shipping нужны не все данные заказа, а только некоторые из них, например, информация о клиенте, о его адресах доставки, способе доставки. Поэтому в модуле доставки определяется и используется своя модель заказа. К этой модели адаптер и преобразует полученные данные. И, пожалуй, это самый сильный аргумент в пользу адаптеров, ведь многим разработчикам кажется оверинжинирингом писать адаптеры к собственному коду.
Ниже показан пример организации кода.
В модуле заказов есть папка Api, у нее есть своя модель OrderInfo, ApiInterface и реализация ApiInterface.
В модуле доставки на уровне инфраструктуры размещается папка Adapter. Там находятся классы, которые имеют право обращаться к другим модулям. Любые классы вне этой папки не имеют права взаимодействовать с другими модулями.
Важно учесть: так как у нас общая база данных, то все могут обращаться к ней напрямую. Но это создает жесткую связь по данным. Меняя структуру таблиц заказов, мы должны перепроверить, что остальные модули работают правильно, потому что мы могли их сломать. Чтобы избежать такой сильной зависимости, мы получаем «чужие» данные только через API модуля, напрямую в базу за ними не ходим.
На практике бывает сложно такое реализовать, особенно когда идет выборка для вьюшной модели, и там нужны данные из нескольких модулей. В этих случаях можно использовать CQRS.
Взаимодействие с внешним миром. API Gateways.
Клиентами нашего API могут быть внешние сервисы, мобильные приложения, UI. Для каждого из них мы выделили отдельный API Gateway, который может обращаться и собирать информацию из разных модулей через их API. Так как клиенты могут требовать данные от нескольких модулей, неразумно было размещать API Gateway непосредственно в модулях, поэтому мы их вынесли на верхний уровень.
Итоговая структура проекта
Контроль зависимостей
К сожалению, PHP не предоставляет механизмов контроля зависимостей между нашими модулями и инкапсуляции деталей их реализации. Без них очень сложно контролировать соблюдение принятых принципов, поэтому нужно реализовать этот контроль другими средствами. Мы используем статический анализатор кода deptrac для контроля зависимостей между классами. Он устанавливается через composer и запускается через командную строку. Проверка зависимостей встроена в наш CI/CD, и код, нарушающий архитектуру, не попадет ни в тестовое, ни в прод окружение.
Для настройки утилиты у нас есть два файла: один depfile-layers.yaml, контролирующий зависимости между слоями, и второй depfile-modules.yaml, описывающий зависимости между модулями приложения. В файле настроек зависимостей между слоями мы прописываем названия слоев и правила, по которым в этот слой попадает код:
Domain не может зависеть ни от чего;
Application только от Domain;
API от Domain и Application;
Infrastructure от всех внутренних слоев.
С настройкой зависимостей между модулями было немного сложнее, так как deptrac не поддерживает такое понятие, как «модуль». Поэтому нам пришлось использовать модули как отдельные слои, для которых прописано правило, что они не могут зависеть друг от друга. В исключения добавлена папка Adapter, в которой лежат классы предохранительного слоя, и только через них мы можем взаимодействовать с другими модулями.
Ослабление связей. Event-Driven Design.
Разделив приложение на модули, мы все еще имеем сильную связанность по коду между модулями, так как им постоянно нужно взаимодействовать друг с другом. Рассмотрим ослабление связей на примере онлайн-заказа.
Клиент размещает заказ, он обращается к модулю заказов, модуль заказов обращается к платежному модулю, потом обратно возвращается к модулю заказов, чтобы подтвердить, что платеж прошел; модуль заказов обращается к модулю доставки, чтобы он доставил клиенту заказ. На лицо сильная зависимость модулей друг от друга.
Для решения проблемы мы можем внести в нашу инфраструктуру брокер сообщений и общаться между собой посредством событий.
Наша бизнес-модель может генерировать доменные события. Например, модуль заказов говорит о том, что он создал заказ, и забывает об этом. Событие попадает в очередь сообщений и все, кто заинтересован в этом событии, подписываются на него и производят действия, которые им нужны. В данном случае модуль оплаты требует просто оплатить заказ, потом генерирует свое событие, что заказ оплачен или не оплачен. На него подписан модуль заказов, он его соответствующим образом обрабатывает. С модулем доставки все то же самое.
Таким образом, мы ослабили зависимости тем, что теперь у нас модуль заказов ничего не знает о том, что существуют другие модули. Он просто умеет генерировать события и обрабатывать события других контекстов, которые ему интересны.
Также мы теперь можем легко добавить новый модуль, не затрагивая модуль заказов. Например, нам нужно отправлять клиентам уведомления о событиях (заказ оплачен, подтвержден и т.д.) и для этого у нас появляется модуль уведомлений, который подписывается на интересующие его события.
Важный момент. Для того чтобы участники действительно не были связаны по коду, нужно использовать так называемую weak-schema serialization, то есть простые форматы данных, такие как json, xml. В сериализованных событиях не должно быть никаких названий классов.
Например, класс OrderPlaced
может быть сериализован как json:
Заключение
Итак, подведем итог. Какие приемы мы использовали, чтобы избавиться от big ball of mud:
Разделение приложения на модули с использованием ограниченных контекстов DDD;
Разделение модулей на слои с использованием чистой архитектуры;
Взаимодействие модулей через предохранительный слой;
Ограничение на доступ к таблицам БД;
Ослабление связей с использованием событийно-ориентированной архитектуры;
Эффективная организация кодовой базы;
Контроль архитектуры с помощью deptrac.
Когда подходит такая архитектура?
Нет необходимости в независимом масштабировании отдельных частей приложения;
Нет средств, времени, опыта, знаний для развертывания микросервисной архитектуры;
Для стабилизации границ модулей перед выделением их в микросервисы;
Размеры команды разработки не мешают работать в рамках монолита.
Также можно посмотреть оценку зависимости стоимости разработки от количества и сложности фич, приведенную Sander Mac. Как мы видим, стоимость внедрения микросервисной архитектуры на начальном этапе очень высока. Самая низкая стоимость у монолита. Посередине находится модульный монолит.
Источник изображения: https://twitter.com/Sander_Mak/status/852459340493709313
В заключение хотелось бы привести цитату:
If you can’t build a monolith, what makes you think microservices are the answer?
На нижегородском PHP-митапе 24 апреля 2021 года Валентин Удальцов (ведущий каналов Пых и PHP Point) выступал с докладом «Как структурировать код, чтобы не получить большой ком грязи», в котором предлагает аналогичные подходы к организации монолита.
PHP модуль — это всё ещё просто. Часть вторая
Пока nerezus сочиняет статью о встраивании PHP, я постараюсь продолжить его рассказ о написании расширений. Рассказано будет далеко не всё, поскольку я считаю, что сложность наращивать надо постепенно, иначе материал будет трудноусвояемым и совсем не питательным. В связи с этим я всё-таки не расскажу в этот раз, как подменить операторы в классе, кто захочет, может почитать исходники модуля Operator от Сары Големон — основного автора какой бы то ни было информации о разработке расширений PHP.
Поскольку разработку я веду исключительно в линуксе, то писать мы будем без всяких хитрых аддонов к Visual Studio, ручками, с нуля 🙂 А что, лучше сразу разобраться, а потом уже упрощать свой труд.
Первым делом нам надо будет написать два файла: config.m4 и Makefile. Первый нужен для сборки нашего расширения, а второй… чтобы эта самая сборка не загадила наш каталог с исходниками 🙂
1. config.m4
2. Makefile
3. Создаём mystring.h-файл
Что нам теперь надо? Как и в любой приличной программе на C/C++ — заголовочный файл и файл с кодом. Начнём с заголовочного. Файл у нас опять-таки будет простой, меньше 40 строк. Пройдёмся по нему.
Строками 4-5 мы объявим название и версию нашего расширения. Нужно это с единственной целью — вывести данную ценную информацию позже в инфо о модуле.
Строка 7 задаёт нам имя класса. Конечно, писать «mystring» короче, чем PHP_MYSTRING_CLASS_NAME, но зато в любой момент сменить имя класса можно будет в одном единственном месте. Например, переименовать класс в «Mystring» — с большой буквы, как подобает приличным людям.
Строки 9-11 инклюдят файл config.h, который сгенерируется в процессе сборке — сами мы его писать не будем.
Дальше инклюдим стандартные заголовки и объявляем функции. Остановимся здесь подробнее. Функции PHP_MINIT_FUNCTION и PHP_MSHUTDOWN_FUNCTION — это функции, выполняющиеся при загрузке и выгрузке модуля. Помимо них бывают *_GINIT_* и *_GSHUTDOWN_* — функции для глобального запуска/выгрузки PHP, и *_RINIT_* и *_RSHUTDOWN_* — при активации/деактивации модуля. Функция PHP_MINFO_FUNCTION — это функция, выполняющаяся при вызове phpinfo(), она сгенерирует нам информацию о модуле.
Дальше мы объявим несколько методов — помните, в первой части вы объявляли их при помощи макроса PHP_FUNCTION и они были глобальными? Здесь мы делаем по сути то же самое, только макрос принимает два параметра — класс и имя его метода.
Ну и под конец 35-ой строкой объявим наш модуль.
Больше нам собственно в заголовочном файле ничего не нужно — переходим к коду.
4. Пишем код модуля — mystring.cc
Здесь, код посложнее. К сожалению, не удалось его поместить прямо в статью — хабр протестовал — поэтому я выложил сразу весь код сюда, здесь же мы рассмотрим только его содержимое.
Строки 1-27
Пока всё просто — объявляем наш будущий класс, и декларируем наш модуль. Обратите внимание — мы указали его название, версию, а также функции MINIT, MSHUTDOWN и MINFO. Глобальных функций мы в этот раз не объявляем, RINIT и RSHUTDOWN нам тоже не нужны, всё остальное можно считать дефолтным.
Строки 29-49
Теперь мы подставили макрос, говорящий PHP, что наш модуль можно скомпилировать как расширение, а не только как часть PHP. Затем объявили набор функций класса. Для каждой функции указывается 4 параметра: имя класса, имя функции, указатель на структуру arg_info и параметры доступа. Внятного описания структуры arg_info я ни разу не встречал, поэтому мы будем по старинке передавать NULL, а проверять параметры уже в самой функции.
Основные параметры доступа — это, разумеется, ZEND_ACC_PUBLIC, ZEND_ACC_PROTECTED и ZEND_ACC_PRIVATE. Дополнительно к ним можно указать ZEND_ACC_STATIC или ZEND_ACC_ALLOW_STATIC (такой метод можно использовать и как статический, и как обычный). Для конструкторов, деструкторов, а также оператора clone (в PHP объявляется как функция __clone) надо указывать соответственно флаги ZEND_ACC_CTOR, ZEND_ACC_DTOR и ZEND_ACC_CLONE.
Впрочем, полный список вы можете посмотреть в заголовочных файлах, на моей машине это файл /usr/include/php5/Zend/zend_compile.h
Обратите внимание, что после каждого метода, описанного с помощью PHP_ME() запятую ставить не нужно. А в конце всегда должен быть массив из трёх NULL‘ов.
А тем временем мы едем дальше.
Строки 51-76
Определили три объявленных ранее метода — загрузка, выгрузка и информация о модуле. Из всех трёх интерес нам представляет только один: загрузка модуля. Для начала мы должны зарегистрировать наш объявленный класс.
Затем мы в зарегистрированном классе создаём свойство, для чего передаём указатель на зарегистрированный класс, название свойства, длину этого названия (об этом строчкой позже), значение по умолчанию и опять-таки параметры доступа. В конце мы должны обязательно написать волшебное слово TSRMLS_CC. Это слово надо упомянуть особо. Чтобы внутри функции работать с текущим состоянием PHP — знать, где мы сейчас находимся, иметь информацию о классах и т.д. и т.п., надо данную информацию передавать. Для этого при объявлении функции надо в конце написать макрос TSRMLS_DC, а при вызове — TSRMLS_CC. Запятую перед макросом в обоих случаях ставить не нужно.
Теперь вернемся к длине названия свойства. Информация об объявлении свойств в разных источниках весьма скудна и противоречива. Так, в руководстве той же Сары Големон упоминается, что если свойство объявлено как приватное, оно должно иметь название вида «\0имя_класса\0имя_свойства», для чего в PHP существует метод zend_mangle_property_name(), конструирующий подобное название для protected и private свойств. На практике же оказалось, что если использовать zend_mangle_property_name, PHP при обращении к свойству грязно ругается, в то время как в вышеупомянутом вызове — всё замечательно. Имейте в виду, если будете читать литературу по теме.
Строки 78-106
Давайте теперь определим стандартные методы класса. Поехали по пунктам.
Функция getThis() возвращает нам указатель на объект класса, из которого мы вызвали метод. Для конструктора и деструктора мы проверяем этот указатель — нам не хочется, чтобы кто-то вызывал данные методы статически. Затем в конструкторе мы — просто чтобы показать вам данную функцию — меняем уже задекларированное свойство «str». Для этого мы передаем тип объекта (класс), указатель на сам объект, имя и длину имени свойства, а также новое значение. Обратите внимание, что мы используем *_stringl(), а не *_string() функцию — она требует передать еще и длину нового значения. В принципе она используется в случаях, когда в строке может присутствовать символ ‘\0’, или когда мы наоборот хотим записать только первые N символов от строки, но из-за того что она работает чуть быстрее, чем *_string() вариант, мы используем её и в такой тривиальной ситуации- нам же не жалко лишний параметр указать.
Аналогично мы используем макрос RETURN_STRINGL в функции приведения класса к строке. Обратите внимание на последний параметр «1» — он означает, что мы хотим передать не сам объект, а его копию. Мало ли что потом с нашей строкой захотят делать.
Строки 108-133
Давайте опишем метод добавления строки.
Первым делом мы проверяем, передали ли нам строку в качестве параметра — это уже рассматривалось в первой части.
Затем мы считываем из нашего объекта уже храняющуюся там строку. Обратите внимание, что хотя мы создавали свойство как string, указатель нам вернулся на zval. Всё дело в том, что абсолютно все типы данных в php представляются типом zval. Если вам интересно, как он устроен, смотрите файл /usr/include/php5/Zend/zend.h на предмет определения struct _zval_struct и union _zvalue_value.
Наконец, мы выделяем память под новую строку и копируем туда исходную строку и «добавку». Затем пользуемся уже знакомым методом zend_update_property_stringl() и записываем новое значение. Вместо malloc, calloc и free в PHP принято пользоваться зендовскими аналогами emalloc, ecalloc и efree.
Готово, новое значение лежит там, где надо, можно вернуть NULL. Домашнее задание: догадаться, как вернуть сам объект, чтобы можно было составлять цепочки вида $a->append(«a»)->append(«b»)…
Строки 135-148
Ну и наконец реализуем метод compare. В нём мы не будем реализовывать настоящее сравнивание (чёрт знает, почему я решил обозвать метод так), а выведем нашу строку — должна же она хоть где-то выводиться — и покажем чуть-чуть магии.
Для начала мы считали и вывели нашу строку str. Синтаксис функции zend_printf(), как вы могли заметить, абсолютно идентичен стандартному printf(). А вот дальше… что это? Да, это мы легко и непринужденно взяли и выполнили кусок PHP-кода. В качестве параметров передаём строку кода, указатель на переменную, куда положить результат выполнения, а также название нашего скрипта. Чтобы ничего не выдумывать, я просто написал myvardump.
Итоги
mystring
upchk Попячсо!
object(mystring)#1 (1) <
[«str:private»]=>
string(21) «upchk Попячсо!»
>
Первая строка — это мы вывели наш класс, приведенный к строке (см. __toString()). Вторая — вывели уже измененную строку str. А оставшиеся три строки — это результат нашего zend_eval_string — дамп класса. Наблюдаем наше созданное приватное свойство — всё честно.
