Как написать библиотеку на php

Время на прочтение
6 мин

Количество просмотров 5.9K

иллюстрация © GOLTS
иллюстрация © GOLTS

Прежде чем приступать к написанию кода, нам нужно решить, что именно такого полезного мы можем создать, какие функции будет выполнять библиотека. Довольно популярным является написание библиотек, упрощающих работу с тем или иным API. Зачастую API представляют из себя большой список различных методов, работающих не только через GET method http-протокола. И это доставляет сложность при работе с ним у программистов: нужно постоянно учитывать все нюансы обращения к методу, его ответа, а еще может присутствовать аутентификация при работе и тд.

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

Наша основная цель научиться создавать и публиковать lib для composer, а не реализация крутого и огромного решения, поэтому я выбрал API, которое уже имеет официальную php client library, но все же его вполне можно дополнить, и тем самым упростить использование. Я говорю о Почте от Google: Gmail API. Если перейти на официальную страницу настройки и использования этого API https://developers.google.com/gmail/api/quickstart/php, то мы увидим, что она предполагает все же некоторую подготовительную работу прежде, чем можно будет приступить к непосредственной работе с почтой.

Посмотрим на листинг в пункте «Step 2: Set up the sample», там мы видим подключение composer зависимостей, затем функцию getClient(), которая создает подключение к сервису почты, и только после нее идет пример кода работы с почтовыми лейблами. Как минимум эту функцию мы можем вынести за скобки нашего основного веб-приложения в библиотеку, которую сделать доступной для установки на любой проект, где понадобится работа с Gmail. Конечно, в реальном мире разработки создавать библиотеку только ради упрощения одной функции идея не самая лучшая, но мы учимся, к тому же вполне вероятно, что во время работы мы войдем во вкус и придумаем, что еще можно упростить, ускорить и вынести в библиотеку кроме этой функции настройки и подключения.

Создание библиотеки

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

mkdir gmail && cd gmail
composer init

Composer предлагает нам ввести данные о создаваемом пакете:

Welcome to the Composer config generator  

This command will guide you through creating your composer.json config.

Package name (<vendor>/<name>) [zhukmax/gmail]: 
Description []: Package for easy using Gmail Api
Author [ZhukMax <zhukmax@ya.ru>, n to skip]: 
Minimum Stability []: dev
Package Type (e.g. library, project, metapackage, composer-plugin) []: library
License []: Apache-2.0

Define your dependencies.

Would you like to define your dependencies (require) interactively [yes]? no
Would you like to define your dev dependencies (require-dev) interactively [yes]? no

{
    "name": "zhukmax/gmail",
    "description": "Package for easy using Gmail Api",
    "type": "library",
    "license": "Apache-2.0",
    "authors": [
        {
            "name": "ZhukMax",
            "email": "zhukmax@ya.ru"
        }
    ],
    "minimum-stability": "dev",
    "require": {}
}

Do you confirm generation [yes]?
Include the Composer autoloader with: require 'vendor/autoload.php';

Обратим внимание на название пакета (Package name), оно должно быть уникальным для того, чтобы можно было разместиться в packagist. Так же мы видим, что composer в квадратных скобках [ ] предлагает ответ на свой же вопрос, и если нас он устраивает, то просто нажимаем на клавишу Enter, иначе после двоеточия пишем свой вариант. Для лицензии проекта я выбрал Apache второй версии, открытую лицензию, отлично подходящую для opensource публикуемых программных решений, так же есть крайне популярные MIT и GNU GPL (https://ru.wikipedia.org/wiki/GNU_General_Public_License). В поле Description одним предложением описываем свою библиотеку, а в Author можно перечислить авторов, мой Composer уже знает меня, и поэтому подставил по умолчанию. На вопросы о зависимостях (dependencies) я пока что ответил отрицательно, мы поставим нужные зависимости позже, а также добавим кое-что вручную прям в файл composer.json, в котором и хранятся все настройки проекта.

В конце после вопроса «Do you confirm generation [yes]?» мы нажали Enter, и в корне проекта появился файл composer.json. Откроем его и пропишем зависимость от php, я выбрал версию не ниже 7.1, так как седьмая версия выпущена уже более пяти лет. Но если вы предполагаете, что вашей библиотекой будут пользоваться на проектах с php 5, тогда можно проставить «>=5.6.0». Далее запускаем команду установки зависимости от официальной Gmail API библиотеки:

composer require google/apiclient:^2.0

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

{
    "name": "zhukmax/gmail",
    "description": "Package for easy using Gmail Api",
    "type": "library",
    "license": "Apache-2.0",
    "authors": [
        {
            "name": "ZhukMax",
            "email": "zhukmax@ya.ru"
        }
    ],
    "minimum-stability": "dev",
    "require": {
        "php": ">=7.1.0",
        "google/apiclient": "^2.0",
        "ext-json": "*"
    },
    "autoload": {
        "psr-4": {
            "Zhukmax\Gmail\": "src/"
        }
    }
}

Заходим на github под своим аккаунтом (если нужно зарегестрируйтесь) и создаем новый репозиторий с названием gmail, так же стоит указать описание, лицензию и другие настройки.

Новый репозиторий на github

Новый репозиторий на github

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

git init
git remote add origin https://github.com/ZhukMax/gmail.git
git config checkout.defaultRemote origin
git pull origin main

Открываем папку с нашим проектов в любимом редакторе коде, я лично предпочитаю PhpStrorm, но вполне подойдет даже Notepad++. Создаем директорию src, в которой и будет располагаться основной код. Как видно из листинга выше, мы прописали именно эту папку для всех классов в namespace ZhukmaxGmail.

Создаем директорию src

Создаем директорию src

На шаге два из официальной инструкции к работе с Gmail API (https://developers.google.com/gmail/api/quickstart/php#step_2_set_up_the_sample) нам требуется создать файл, в котором размещаем некоторые конфигурационные данные, функцию авторизации и сам код работы с API. Как раз конфигурацию и авторизацию мы и перенесем в наш пакет, чтобы при использовании можно было не заморачиваться с этим кодом и сразу заниматься тем, что необходимо на проекте. Поэтому создаем класс авторизации в src и добавляем в него функцию авторизации:

touch src/Auth.php

Класс авторизации

Класс авторизации

Пока что наш код не будет работать, так как функции авторизации нужны настройки: название приложения, путь к директории для хранения токена, путь к файлу credentials.json, который можно получить в консоли разработчика Google. А еще этот код можно улучшить и упростить работу с ним для конечного разработчика. Для начала создадим публичный конструктор и метод, который будет отдавать готовый к работе сервис, либо выбрасывать исключение (Exception), если произошла какая-то ошибка, а уже созданный сделаем приватным:

<?php

private $name;
private $scope;
private $credentialsPath;
private $tokenPath;

public function __construct(array $params)
{
    if (!$params['credentials']) {
      throw new Exception("Path to credentials is required");
    }
    $this->credentialsPath = $params['credentials'];

    if (!$params['token']) {
      throw new Exception("Path to token is required");
    }
    $this->tokenPath = $params['token'];
    $this->name = $params['name'] ?? 'Gmail API PHP';
    $this->scope = $params['scope'] ?? Google_Service_Gmail::GMAIL_READONLY;
}

public function getService()
{
    $client = self::getClient();
    return new Google_Service_Gmail($client);
}

Добавляем путь к токену

Добавляем путь к токену

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

<?php

private function makeNewToken(Google_Client $client)
{
    // Request authorization from the user.
    $authUrl = $client->createAuthUrl();
    printf("Open the following link in your browser:n%sn", $authUrl);
    print 'Enter verification code: ';
    $authCode = trim(fgets(STDIN));

    // Exchange authorization code for an access token.
    $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
    $client->setAccessToken($accessToken);

    // Check to see if there was an error.
    if (array_key_exists('error', $accessToken)) {
      throw new Exception(join(', ', $accessToken));
    }
}

private function saveToken(Google_Client $client)
{
    if (!file_exists(dirname($this->tokenPath))) {
      mkdir(dirname($this->tokenPath), 0700, true);
    }
    file_put_contents($this->tokenPath, json_encode($client->getAccessToken()));
}

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

Публикация библиотеки

Когда код библиотеки готов сделаем коммит и отправим все правки в репозиторий на github (или другой выбранный вами ранее):

git push origin main

Теперь скопируем ссылку на репозиторий:

На сайте packagist.org после авторизации переходим в раздел Submit, вставляем GitHub URL — ссылку на репозиторий и нажимаем Check и затем Submit

Готово, наша библиотка отправлена в публичное пространство и доступна для установок и использования.

Результат

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

Репозиторий с кодом из статьи лежит по ссылке.

Прежде чем приступать к написанию кода, нам нужно решить, что именно такого полезного мы можем создать, какие функции будет выполнять библиотека. Довольно популярным является написание библиотек, упрощающих работу с тем или иным API. Зачастую API представляют из себя большой список различных методов, работающих не только через GET method http-протокола. И это доставляет сложность при работе с ним у программистов: нужно постоянно учитывать все нюансы обращения к методу, его ответа, а еще может присутствовать аутентификация при работе и тд.

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

Наша основная цель научиться создавать и публиковать lib для composer, а не реализация крутого и огромного решения, поэтому я выбрал API, которое уже имеет официальную php client library, но все же его вполне можно дополнить, и тем самым упростить использование. Я говорю о Почте от Google: Gmail API. Если перейти на официальную страницу настройки и использования этого API https://developers.google.com/gmail/api/quickstart/php, то мы увидим, что она предполагает все же некоторую подготовительную работу прежде, чем можно будет приступить к непосредственной работе с почтой.

Посмотрим на листинг в пункте «Step 2: Set up the sample», там мы видим подключение composer зависимостей, затем функцию getClient(), которая создает подключение к сервису почты, и только после нее идет пример кода работы с почтовыми лейблами. Как минимум эту функцию мы можем вынести за скобки нашего основного веб-приложения в библиотеку, которую сделать доступной для установки на любой проект, где понадобится работа с Gmail. Конечно, в реальном мире разработки создавать библиотеку только ради упрощения одной функции идея не самая лучшая, но мы учимся, к тому же вполне вероятно, что во время работы мы войдем во вкус и придумаем, что еще можно упростить, ускорить и вынести в библиотеку кроме этой функции настройки и подключения.

Создание библиотеки

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

mkdir gmail && cd gmail
composer init

Composer предлагает нам ввести данные о создаваемом пакете:

Welcome to the Composer config generator  

This command will guide you through creating your composer.json config.

Package name (<vendor>/<name>) [zhukmax/gmail]: 
Description []: Package for easy using Gmail Api
Author [ZhukMax <zhukmax@ya.ru>, n to skip]: 
Minimum Stability []: dev
Package Type (e.g. library, project, metapackage, composer-plugin) []: library
License []: Apache-2.0

Define your dependencies.

Would you like to define your dependencies (require) interactively [yes]? no
Would you like to define your dev dependencies (require-dev) interactively [yes]? no

{
    "name": "zhukmax/gmail",
    "description": "Package for easy using Gmail Api",
    "type": "library",
    "license": "Apache-2.0",
    "authors": [
        {
            "name": "ZhukMax",
            "email": "zhukmax@ya.ru"
        }
    ],
    "minimum-stability": "dev",
    "require": {}
}

Do you confirm generation [yes]?
Include the Composer autoloader with: require 'vendor/autoload.php';

Обратим внимание на название пакета (Package name), оно должно быть уникальным для того, чтобы можно было разместиться в packagist. Так же мы видим, что composer в квадратных скобках [ ] предлагает ответ на свой же вопрос, и если нас он устраивает, то просто нажимаем на клавишу Enter, иначе после двоеточия пишем свой вариант. Для лицензии проекта я выбрал Apache второй версии, открытую лицензию, отлично подходящую для opensource публикуемых программных решений, так же есть крайне популярные MIT и GNU GPL (https://ru.wikipedia.org/wiki/GNU_General_Public_License). В поле Description одним предложением описываем свою библиотеку, а в Author можно перечислить авторов, мой Composer уже знает меня, и поэтому подставил по умолчанию. На вопросы о зависимостях (dependencies) я пока что ответил отрицательно, мы поставим нужные зависимости позже, а также добавим кое-что вручную прям в файл composer.json, в котором и хранятся все настройки проекта.

В конце после вопроса «Do you confirm generation [yes]?» мы нажали Enter, и в корне проекта появился файл composer.json. Откроем его и пропишем зависимость от php, я выбрал версию не ниже 7.1, так как седьмая версия выпущена уже более пяти лет. Но если вы предполагаете, что вашей библиотекой будут пользоваться на проектах с php 5, тогда можно проставить «>=5.6.0». Далее запускаем команду установки зависимости от официальной Gmail API библиотеки:

composer require google/apiclient:^2.0

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

{
    "name": "zhukmax/gmail",
    "description": "Package for easy using Gmail Api",
    "type": "library",
    "license": "Apache-2.0",
    "authors": [
        {
            "name": "ZhukMax",
            "email": "zhukmax@ya.ru"
        }
    ],
    "minimum-stability": "dev",
    "require": {
        "php": ">=7.1.0",
        "google/apiclient": "^2.0",
        "ext-json": "*"
    },
    "autoload": {
        "psr-4": {
            "Zhukmax\Gmail\": "src/"
        }
    }
}

Заходим на github под своим аккаунтом (если нужно зарегестрируйтесь) и создаем новый репозиторий с названием gmail, так же стоит указать описание, лицензию и другие настройки.

Новый репозиторий на github

Новый репозиторий на github

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

git init
git remote add origin https://github.com/ZhukMax/gmail.git
git config checkout.defaultRemote origin
git pull origin main

Открываем папку с нашим проектов в любимом редакторе коде, я лично предпочитаю PhpStrorm, но вполне подойдет даже Notepad++. Создаем директорию src, в которой и будет располагаться основной код. Как видно из листинга выше, мы прописали именно эту папку для всех классов в namespace ZhukmaxGmail.

Создаем директорию src

Создаем директорию src

На шаге два из официальной инструкции к работе с Gmail API (https://developers.google.com/gmail/api/quickstart/php#step_2_set_up_the_sample) нам требуется создать файл, в котором размещаем некоторые конфигурационные данные, функцию авторизации и сам код работы с API. Как раз конфигурацию и авторизацию мы и перенесем в наш пакет, чтобы при использовании можно было не заморачиваться с этим кодом и сразу заниматься тем, что необходимо на проекте. Поэтому создаем класс авторизации в src и добавляем в него функцию авторизации:

touch src/Auth.php

Класс авторизации

Класс авторизации

Пока что наш код не будет работать, так как функции авторизации нужны настройки: название приложения, путь к директории для хранения токена, путь к файлу credentials.json, который можно получить в консоли разработчика Google. А еще этот код можно улучшить и упростить работу с ним для конечного разработчика. Для начала создадим публичный конструктор и метод, который будет отдавать готовый к работе сервис, либо выбрасывать исключение (Exception), если произошла какая-то ошибка, а уже созданный сделаем приватным:

<?php

private $name;
private $scope;
private $credentialsPath;
private $tokenPath;

public function __construct(array $params)
{
    if (!$params['credentials']) {
      throw new Exception("Path to credentials is required");
    }
    $this->credentialsPath = $params['credentials'];

    if (!$params['token']) {
      throw new Exception("Path to token is required");
    }
    $this->tokenPath = $params['token'];
    $this->name = $params['name'] ?? 'Gmail API PHP';
    $this->scope = $params['scope'] ?? Google_Service_Gmail::GMAIL_READONLY;
}

public function getService()
{
    $client = self::getClient();
    return new Google_Service_Gmail($client);
}

Добавляем путь к токену

Добавляем путь к токену

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

<?php

private function makeNewToken(Google_Client $client)
{
    // Request authorization from the user.
    $authUrl = $client->createAuthUrl();
    printf("Open the following link in your browser:n%sn", $authUrl);
    print 'Enter verification code: ';
    $authCode = trim(fgets(STDIN));

    // Exchange authorization code for an access token.
    $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
    $client->setAccessToken($accessToken);

    // Check to see if there was an error.
    if (array_key_exists('error', $accessToken)) {
      throw new Exception(join(', ', $accessToken));
    }
}

private function saveToken(Google_Client $client)
{
    if (!file_exists(dirname($this->tokenPath))) {
      mkdir(dirname($this->tokenPath), 0700, true);
    }
    file_put_contents($this->tokenPath, json_encode($client->getAccessToken()));
}

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

Публикация библиотеки

Когда код библиотеки готов сделаем коммит и отправим все правки в репозиторий на github (или другой выбранный вами ранее):

git push origin main

Теперь скопируем ссылку на репозиторий:

На сайте packagist.org после авторизации переходим в раздел Submit, вставляем GitHub URL — ссылку на репозиторий и нажимаем Check и затем Submit

Готово, наша библиотка отправлена в публичное пространство и доступна для установок и использования.

Результат

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

Репозиторий с кодом из статьи лежит по ссылке https://github.com/ZhukMax/gmail

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

Замечу, что в этой статье мы сосредоточимся на написании хорошего пакета — код будет настоящим, и готовым к работе в реальных проектах. API Diffbot довольно простое, и интерфейс Guzzle довольно лёгок, им можно пользоваться даже без необходимости в отдельной библиотеке PHP. Вместо этого я хочу обратить ваше внимание на подходы, которые мы используем для разработки высококачественных PHP пакетов, чтобы их можно было повторно использовать в своих проектах. Diffbot был выбран для написания пакета потому, что я хотел продемонстрировать хорошие подходы на примерах из реальной жизни, вместо того, чтобы писать ещё один тестовый пакет.

Хорошо спроектированный пакет

В последние годы установились хорошие стандарты проектирования PHP пакетов, не в малой степени благодаря Composer, Packagist, Лиге PHP, и с недавнего времени ещё и The Checklist. Сведя всё это воедино, мы можем вывести для себя следующие правила, которым будем следовать:

  1. Включить лицензию
  2. Распространяться с открытым исходным кодом
  3. Не включать вещи, используемые для разработки в дистрибутив
  4. Использовать автозагрузку по стандарту PSR-4
  5. Располагаться в Packagist для установки через Composer
  6. Не завязываться на конкретные фреймворки
  7. Использовать стандарт написания кода PSR-2
  8. Писать исчерпывающие комментарии к коду
  9. Использовать семантическое версионирование
  10. Использовать непрерывную интеграцию и юнит-тесты

Для более детального рассмотрения этих и других правил обратитесь к этому списку.

Начинаем

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

sites:
    - map: test.app
      to: /home/vagrant/Code/diffbot_lib
    - map: test2.app
      to: /home/vagrant/Code/diffbot_test

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

Чтобы дать толчок, будем использовать шаблон пакета от Лиги PHP, который поставляется со встроенными правилами разработки. Я сделал свой собственный форк с доработками в .gitignore и другими мелкими изменениями — можете воспользоваться им, или же шаблоном Лиги, разница не принципиальна.

git clone https://github.com/Swader/php_package_skeleton diffbot_lib

Отредактируем composer.json, в итоге у нас получается нечто такое:

{
    "name": "swader/diffbot_client",
    "description": "A PHP wrapper for using Diffbot's API",
    "keywords": [
        "diffbot", "api", "wrapper", "client"
    ],
    "homepage": "https://github.com/swader/diffbot_client",
    "license": "MIT",
    "authors": [
        {
            "name": "Bruno Skvorc",
            "email": "bruno@skvorc.me",
            "homepage": "http://bitfalls.com",
            "role": "Developer"
        }
    ],
    "require": {
        "php" : ">=5.5.0"
    },
    "require-dev": {
        "phpunit/phpunit" : "4.*"
    },
    "autoload": {
        "psr-4": {
            "Swader\Diffbot\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Swader\Diffbot\Test\": "tests"
        }
    },
    "extra": {
        "branch-alias": {
            "dev-master": "1.0-dev"
        }
    }
}

Мы прописали общие метаданные, определили требования, и настроили автозагрузку по стандарту PSR-4. Это, а также тот факт, что мы воспользовались шаблонным приложением от Лиги PHP, говорит о выполнении пунктов 1-6 из вышеприведённого списка. Пока мы не ушли далеко, можем добавить зависимость от Guzzle, так как мы будем использовать эту библиотеку в качестве HTTP-клиента для совершения запросов к API Diffbot.

"require": {
    "php" : ">=5.5.0",
    "guzzlehttp/guzzle": "~5.0"
},

После выполнения команды composer install, которая загрузит все зависимости, включая PHPUnit, который необходим для тестирования, можем проверить, что всё работает, изменив содержимое src/SkeletonClass.php:

<?php

namespace SwaderDiffbot;

class SkeletonClass
{

    /**
     * Создаём новый экземпляр шаблонного приложения
     */
    public function __construct()
    {
    }

    /**
     * Дружелюбное приветствие
     *
     * @param string $phrase Возвращаемая фраза
     *
     * @return string Возвращает переданную фразу
     */
    public function echoPhrase($phrase)
    {
        return $phrase;
    }
}

и создав файл index.php в корне проекта:

<?php

require_once "vendor/autoload.php";

$class = new SwaderDiffbotSkeletonClass();

echo $class->echoPhrase("Оно работает!");

Посетив страницу test.app:8000 из браузера, вы должны увидеть сообщение “Оно работает!”.

Не беспокойтесь об отсутствии директории public, или чего-то в этом духе — при разработке пакета это не важно. При разработке библиотеки всё внимание должно быть сконцентрировано на пакете, и только на пакете — нет необходимости иметь дело с фреймворками и MVC. Мы будем использовать файл index.php для тестирования некоторых вещей время от времени, но в большинстве случаев для разработки будем пользоваться PHPUnit. Пока добавим файл index.php в .gitignore, чтобы ненароком не отправить его удалённый репозиторий.

PSR-2

Чтобы не отставать от современных стандартов, стоит изначально писать код в стиле PSR-2. Я использую PhpStorm, поэтому для меня это проще простого. Вы даже можете выбрать встроенный стандарт PSR-1/PSR-2 (как-то так), или же можете установить и настроить CodeSniffer, и использовать его как средство проверки кода PhpStorm (вот так). Я выбрал первый вариант, так как удалённый запуск CodeSniffer через PhpStorm пока не поддерживается (а виртуальная машина на Vagrant, как бы то ни было, является удалённой машиной), но если вы хотели бы добавления такой возможности в PhpStorm, пожалуйста, оставьте свой голос здесь.

Вы всё же можете подключить CodeSniffer в ваш проект посредством Composer, и запускать его командой на виртуальной машине, то есть:

Также вы можете установить чистый PHP на вашей локальной машине (в отличие от дополнительной ерунды, которая устанавливается со стандартными XAMPP/WAMP сборками), загрузить CodeSniffer, и использовать его с локальной машины. Основная машина будет использоваться только для проверки кода, тогда как разработка и выполнение логики из пакета будет производиться на виртуальной машине. Это немного неудобно, но облегчает работу в IDE, вроде PhpStorm, по крайней мере, пока не будет решён вопрос удалённого запуска CodeSniffer.

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

Планирование

Имея окружение для запуска, можно начинать разработку. Давайте подумаем обо всём, что нам нужно.

Точка входа

Неважно, какие существуют сценарии использования API Diffbot, пользователь захочет создать экземпляр API-клиента — с Diffbot вы не можете сделать ничего другого, кроме как отправлять запросы на предопределённые API-методы. Каждый запрос также требует указания токена разработчика, который передаётся в запрос параметром, в виде ?token=xxxxxx. Мои предположения следующие: разработчик обычно будет использовать один и тот же токен, так что помимо возможности создания нового экземпляра клиента с передачей токена (скажем, в конструктор), также нужно предусмотреть возможность задания глобального токена. Другими словами, мы хотим, чтобы оба подхода были верными:

$token = xxxx;

// подход 1
$api1 = new Diffbot($token);
$api2 = new Diffbot($token);

// подход 2
Diffbot::setToken($token);
$api1 = new Diffbot();
$api2 = new Diffbot();

Первый подход полезен, когда вы создаёте один экземпляр клиента, или вы используете несколько токенов (возможно, один для Crawlbot, а другой для обычного API). Второй подход полезен когда в приложении вы определили несколько конечных точек API, но не хотите каждый раз передавать токен.

С этими соображениями перейдём к созданию первого класса нашего пакета. Создайте файл src/Diffbot.php.

<?php

namespace SwaderDiffbot;

use SwaderDiffbotExceptionsDiffbotException;

/**
 * Class Diffbot
 *
 * Основной класс работы с API
 *
 * @package SwaderDiffbot
 */
class Diffbot
{
    /** @var string Токен доступа к API */
    private static $token = null;

    /** @var string Токен экземпляра, устанавливается для каждой новой сущности */
    private $instanceToken;

    /**
     * @param string|null $token Токен доступа к API, полученный на
diffbot.com/dev
     * @throws DiffbotException Если не передан токен
     */
    public function __construct($token = null)
    {
        if ($token === null) {
            if (self::$token === null) {
                $msg = 'Токен не передан и не установлен глобально. ';
                $msg .= 'Используйте Diffbot::setToken, или передайте его в конструктор.';
                throw new DiffbotException($msg);
            }
        } else {
            self::validateToken($token);
            $this->instanceToken = $token;
        }
    }

    /**
     * Устанавливает токен для всех новых сущностей
     * @param $token string Токен доступа к API, полученный на
diffbot.com/dev
     * @return void
     */
    public static function setToken($token)
    {
        self::validateToken($token);
        self::$token = $token;
    }

    private static function validateToken($token)
    {
        if (!is_string($token)) {
            throw new InvalidArgumentException('Переданный токен не является строкой.');
        }
        if (strlen($token) < 4) {
            throw new InvalidArgumentException('Токен "' . $token . '" слишком короткий, и не является валидным.');
        }
        return true;
    }
}

Метод также использует класс DiffbotException, так что не откладывая в долгий ящик создадим файл src/exceptions/DiffbotException.php со следующим содержанием:

<?php

namespace SwaderDiffbotExceptions;

/**
 * Class DiffbotException
 * @package SwaderDiffbotExceptions
 */
class DiffbotException extends Exception
{

}

Давайте по-быстрому рассмотрим класс Diffbot.

Статическое свойство token играет роль значения по-умолчанию, которое Diffbot будет использовать, если токен не передан в конструктор при создании нового экземпляра. В этом случае токен копируется в свойство instanceToken, которое относится к конкретному экземпляру.

Конструктор проверяет, если был передан токен. Если не — он пытается использовать предустановленный токен по умолчанию, или выбрасывает исключение DiffbotException, если не установлен ни один из параметров — вот зачем нам нужен был класс исключения. Если с токеном всё в порядке, он устанавливается как токен экземпляра. С другой стороны, если токен был передан в конструктор — он копируется в instanceToken. Обратите внимание, что в обоих случаях токен должен быть отвалидирован статическим методом validateToken. Этот приватный метод на данный момент просто проверяет, что токен — строка больше трёх символов. Если это не так — выбрасывается исключение о неверном аргументе.

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

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

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

Заключение

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

Мы начали с того, что такое Composer и зачем он нужен:

Рекомендуем публикацию по теме

Теперь мы разберем пошаговое создание мини-проекта на языке PHP и добавим его на сервис packagist.

Создание проекта

Для начала необходимо создать публичный GIT репозиторий, в моём случае это будет github.com.

Инициализация composer проекта

После клонируем репозиторий на компьютер и заходим в директорию проекта, где необходимо создать файл composer.json. Можно создать его вручную, но composer имеет команду специально для этого — composer init.

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

По изображению выше видны вопросы, которые задает composer при инициализации проекта.

  • Package name (<vendor>/<name>) — запрашивает название вашего проекта. Также можно использовать предложенный вариант от composer

  • Description — описание проекта

  • Author — данные об авторе, обычно подтягивает данные с репозитория

  • Minimum Stability — определяет поведение по умолчанию для фильтрации пакетов по стабильности. По умолчанию значение stable

  • Package Type — определяет тип пакета (вашего проекта). По умолчанию — library

  • License — Определяет лицензию вашего пакета (подробнее тут)

Более детально про composer.json можно посмотреть по ссылке. После создания файл конфигурации будет выглядеть следующим образом:

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

README файл

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

Packagist

Packagist имеет страницу регистрации, но самый легкий способ — это авторизация через github, как показано на изображении ниже.

Регистрация пакета

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

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

Подключение вашей библиотеки возможно по команде composer require <vendor>/<name>

P.S.: для удобства лучше использовать версии вашего проекта, таким образом пользователи смогу выбирать себе версию вашей библиотеки сами:

git tag 1.0.0

git push origin 1.0.0

Начните изучать PHP! Курсы ведут только практикующие специалисты 👇

Рекомендуем курс по теме

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

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

function merge_sort($array, $tmparray, $right, $left)
{ ..... }
function bubble_sort($array, $n)
{ ..... }
function quik_sort($array, $right, $left)
{ ..... }
  

Библиотека array_sorting.inc служит накопителем для всех функций сортировки. Библиотеку функций можно включить в сценарий при помощи команд PHP include() или require(), в результате чего все функции библиотеки становятся доступными. Синтаксис этих команд выглядит так:

include(путь/имя файла); или include "путь/имя файла";
require(путь/имя файла); или require "путь/имя файла";
 

И в заключении маленький пример использования функций библиотеки array_sorting.inc в сценарии:

// Предполагается, что библиотека array_sorting.inc
// находится в одном каталоге со сценарием.
// Поэтому подключаем библиотеку
include("array_sorting.inc");

// Теперь можно использовать любые функции из array_sorting.inc
$some_array = array(50, 42. 35, 46);

// Также используем функцию bubble_sort()
$sorted_array = bubble_sort($some_array, 1);
  

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

Создание
библиотеки
функций

Как только появляются многократно используемые фрагменты кода, сразу
хочется загнать их куда-нибудь в библиотеку и вызывать по мере
необходимости. В PHP библиотеку можно построить на основе классов.
Поскольку классы будут использоваться, в основном, для работы с базами
данных, создавать их можно на основе соответствующих классов
библиотеки ADODB (есть в составе VCL, смотри полезные
ссылки).
В качестве альтернативного варианта можно создать в Delphi for PHP
новый модуль (в котором
автоматически будет создан класс), набросать на него необходимые
компоненты для работы с БД и написать соответствующие функции. Причем,
поскольку отображать ничего
не потребуется, можно использовать не Unit, а невизуальный
контейнер компонентов DataModule. Разумеется, можно использовать этот
способ организации библиотеки для работы с любыми компонентами, а не
только с компонентами доступа к данным. Преимущества такого способа —
быстрое задание свойств компонентов через инспектор объектов ну и,
конечно, идеологическая выдержанность. Среди недостатков —
необходимость использовать в каждой процедуре создание нового
экземпляра модуля данных и загрузка файла ресурсов.

В качестве примера рассмотрим функцию аутентификации
пользователя. В целях обеспечения безопасности желательно использовать
ее в каждом модуле, передавая данные из окон ввода имени и пароля (на
странице аутентификации) или из переменных сессии (на других
страницах). Загоним ее
в библиотеку. Для этого создадим новый модуль данных с именем unit4
(File — New — DataModule), разместим на нем компоненты Database и Query
и
настроим соединение с базой данных (смотри сюда).
В модуле пишем функцию
veriPass проверки имени пользователя и пароля:

               
//проверка пользователя и пароля
              
function veriPass($name,$pass)
              
{

                
//создаем модуль данных и загружаем файл ресурсов
                
global $application;
                
global $Unit4;
                
$Unit4=new Unit4($application);
                
$Unit4->loadResource(__FILE__);

                
//открываем соединение
                
$this->Database1->Open();

                
//устанавливаем кодировку запросов
                
$sql=»SET NAMES cp1251″;
                
$this->Query1->SQL=$sql;
                
$this->Query1->LimitStart=’-1′;
                
$this->Query1->LimitCount=’-1′;
                
$this->Query1->Open();
                
$this->Query1->Close();

                
//выполняем запрос для проверки наличия пользователя в базе
                
$sql=»select id from table1 where name='» . $name . «‘» . »
and
pass='» . $pass . «‘»;
                
$this->Query1->SQL=$sql;
                
$this->Query1->Open();

                
//должна быть только одна такая запись
                
if ($this->Query1->readRecordCount()==1)
                
{

           
      
$id=$this->Query1->id;
                  
//закрываем соединение
                  
$this->Query1->Close();
                  
$this->Database1->Close();

                  
return $id;

                
}
                
else
                
{

                  
//закрываем соединение
                  
$this->Query1->Close();
                  
$this->Database1->Close();

                  
return 0;

                 }

             
}

Входными параметрами являются имя пользователя и пароль, передаваемые
на проверку. Выходной параметр — ID пользователя, если он найден, или
0, если не найден. В вызывающем модуле в разделе Includes необходимо
поставить ссылку на используемую библиотеку:

       
//Includes
       
require_once(«vcl/vcl.inc.php»);
       
use_unit(«dbtables.inc.php»);
       
use_unit(«forms.inc.php»);
       
use_unit(«extctrls.inc.php»);
       
use_unit(«stdctrls.inc.php»);

       
//наша библиотека
       
require_once(«Unit4.php»);

Вызов функции осушествляется обычным порядком (имя и пароль для
проверки берутся из полей Edit1 и Edit2):

              
function Button3Click($sender, $params)
              
{

                
$b=Unit4::veriPass($this->Edit1->Text,
$this->Edit2->Text);

                
if ($b>0)
                
{
                  
//обработка успешной аутентификации
                
}
                
else
                
{
                  
//обработка неудачной аутентификации
                
}

                

              
}

Полезные
ссылки:
http://rsdn.ru/article/inet/php_5_prof.xml
http://izone.kiev.ua/articles/php/4/320.htm
(абстрактный класс доступа к данным ADODB)
http://php.russofile.ru/ru/translate/unsort/php4_to_php5/ (работа
с классами в PHP5)

© re-stichka.narod.ru
При
публикации данного материала ссылка на источник обязательна.

Hosted by uCoz

PHP offers a lot of various extensions which add additional library functions, classes, constants and other constructs.Common extensions include, for example, php_mysql and php_iconv. Since extensions are implemented in C language, the performance is great. It also allows programmers to use other native libraries that are not available in PHP. However, there is one big disadvantage; writing such extensions is not easy. C code is harder to maintain, it requires learning lower-level language and it is easier to make mistakes that lead to program failures that are hard to handle.

Writing custom extensions is mostly done by companies requiring high performance code. Most of the normal libraries are written in PHP as a bunch of standard PHP scripts containing functions and classes. Users then include these libraries as normal, and the result is very similar. Writing libraries in PHP is much easier than writing extensions in C language, but it has significant performance costs. Also, the author has to expose the source code of their library, which is not always desirable (I know, there is an obfuscation; but also deobfuscation).

Phalanger Approach

With Phalanger (the PHP compiler for .NET), developers can simply compile their PHP libraries as they are into a DLL file. Taking advantage of pure mode, the resulting compiled assembly can be used both from PHP scripts running on Phalanger or from any other .NET language.

Extension Advantages

So there are three approaches of extending PHP scripts with your library functionality. Every approach has some advantages. As you can see, Phalanger offers the best of both worlds. You can:

  • Write a bunch of PHP scripts as a “library”. Any PHP programmer is able to write such library. However, the library has to be processed and loaded every time it is used. Also, you don’t have an access to native libraries written in other languages.
  • Implement PHP extension in C language. This requires better knowledge of PHP internals and C language. The prize for that effort is maximum performance. Also, you can take advantage of other libraries written in C/C++.
  • Implement Phalanger extension in PHP langage. When you take your PHP library and compile it using Phalanger, the result is DLL working as any other extension. You are using simple PHP syntax, but you have access to all the .NET libraries including your own. The extension is fast, compiled, and loaded only once. Moreover, Phalanger compiler optimizes usage of such extensions more than usage of a bunch of PHP scripts.

An Example

The extension has to be compiled in pure mode. It has only one (logical) limitation – you are not allowed to write a global code. You can only declare global functions, global classes or global constants. Also there are no inclusions; all the source files are processed together, as one big script containing everything (the order of scripts is not important since there is no global code).

Imagine you need simple extension with one function. Something you cannot do efficiently in PHP :) , such as calling a .NET method. Sure, you can call it directly from Phalanger-compiled PHP code without using an extension, but this is just for the demonstration:

<?php

function getextension($fname) {
    return SystemIOPath::GetExtension($fname);
}





?></span>

The code can be compiled by the following command or from Visual Studio:

phpc.exe /target:dll /pure+ /out:myex.dll /r:mscorlib ex.php

The command references .NET library “mscorlib” where the System.IO.Path class is defined and compiles the “ex.php” script. Resulting assembly “myex.dll” can be used as any other Phalanger extension by adding the following into the configuration:

<add url='myex.dll' />

That’s all. Functions, classes and constants defined in the extension will be available in the PHP code. The extension can be distributed compiled, so the source code is safe. Since it is loaded as an extension, the performance is not degraded; and it does not matter how big your extension is.

Summary

This article is, of course, just a simple demonstration, but it shows a very powerful approach for extending PHP with extensions when using Phalanger. The approach described in this article has a number of applications — when porting applications from PHP to Phalanger, you can use it to reimplement a C extension that you’re using, so that you don’t have to change a single line of your program. You can also use it to wrap .NET functionality in a PHP-friendly way. Finally, the technique can also be used to re-implement standard PHP extensions that are not yet available in Phalanger (although, all major extensions are already available).

The complete example with sample usages is available at discussion forum. See PHP extension in PHP. Note you can use Phalanger Tools for Visual Studio if you don’t like command line. :-)

This member has not yet provided a Biography. Assume it’s interesting and varied, and probably something to do with programming.

Понравилась статья? Поделить с друзьями:
  • Как написать библиотеку для python на python
  • Как написать библиотеку для arduino
  • Как написать библиотеку stm32
  • Как написать библиографию
  • Как написать библиографическое описание статьи