Статьи

Правильно начать новый пакет PHP

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

Обратите внимание, что этот урок будет сфокусирован на написании хорошего пакета — код, который мы напишем, будет реальным и готовым к работе, но вам не следует слишком фокусироваться на самом Diffbot. API-интерфейс Diffbot достаточно прост, а интерфейс Guzzle достаточно плавный, чтобы его можно было использовать напрямую, без необходимости использования библиотеки PHP. Скорее обратите внимание на подходы, которые мы используем для разработки высококачественного пакета PHP, чтобы вы могли повторно использовать их в своем собственном проекте. Diffbot был выбран в качестве предмета пакета, потому что я хотел бы продемонстрировать лучшие практики на примере реального мира, а не еще один пакет «Acme».

Хороший дизайн упаковки

В последние годы появились хорошие стандарты для дизайна пакетов PHP, в немалой степени благодаря Composer , Packagist , The League и, совсем недавно, The Checklist . Поместив все это в практический список, мы можем следовать здесь, но избегая какой-либо тесной связи с The League (поскольку наш пакет там не будет представлен — он специально создан для стороннего поставщика API и, как таковой, очень ограничен в контексте), Правила, которым мы будем следовать:

  1. включать лицензию
  2. быть открытым исходным кодом (ну, да!)
  3. исключить материал для разработки из dist
  4. использовать автозагрузку PSR-4
  5. размещаться на Packagist для установки Composer
  6. быть рамочным агностиком
  7. использовать стандарт кодирования PSR-2
  8. иметь глубокие комментарии кода
  9. использовать семантическое управление версиями
  10. использовать CI и юнит-тесты

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

Начиная

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

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

ОК, после входа в ВМ, давайте начнем взламывать.

Чтобы начать работу, мы будем использовать League Skeleton , который представляет собой пакет шаблонов со встроенными правилами Лиги, позволяющими начать игру. Я сделал свой собственный форк с лучшим .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": , "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. Это, наряду с тем, что мы используем Скелет Лиги, заботится о пунктах 1-6 в нашем списке дел сверху. Пока мы здесь, мы также можем добавить Guzzle к нашим требованиям, так как это клиентская библиотека HTTP, которую мы будем использовать для выполнения всех вызовов точек API Diffbot.

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

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

 <?php namespace Swader\Diffbot; class SkeletonClass { /** * Create a new Skeleton Instance */ public function __construct() { } /** * Friendly welcome * * @param string $phrase Phrase to return * * @return string Returns the phrase passed in */ public function echoPhrase($phrase) { return $phrase; } } 

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

 <?php require_once "vendor/autoload.php"; $class = new \Swader\Diffbot\SkeletonClass(); echo $class->echoPhrase("It's working"); 

При посещении test.app:8000 в браузере теперь должно test.app:8000 сообщение «Это работает».

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

PSR-2

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

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

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

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

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

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

Входная точка

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

 $token = xxxx; // approach 1 $api1 = new Diffbot($token); $api2 = new Diffbot($token); // approach 2 Diffbot::setToken($token); $api1 = new Diffbot(); $api2 = new Diffbot(); 

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

Имея это в виду, давайте сделаем первый класс нашего пакета. Создайте файл src/Diffbot.php .

 <?php namespace Swader\Diffbot; use Swader\Diffbot\Exceptions\DiffbotException; /** * Class Diffbot * * The main class for API consumption * * @package Swader\Diffbot */ class Diffbot { /** @var string The API access token */ private static $token = null; /** @var string The instance token, settable once per new instance */ private $instanceToken; /** * @param string|null $token The API access token, as obtained on diffbot.com/dev * @throws DiffbotException When no token is provided */ public function __construct($token = null) { if ($token === null) { if (self::$token === null) { $msg = 'No token provided, and none is globally set. '; $msg .= 'Use Diffbot::setToken, or instantiate the Diffbot class with a $token parameter.'; throw new DiffbotException($msg); } } else { self::validateToken($token); $this->instanceToken = $token; } } /** * Sets the token for all future new instances * @param $token string The API access token, as obtained on 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('Token is not a string.'); } if (strlen($token) < 4) { throw new \InvalidArgumentException('Token "' . $token . '" is too short, and thus invalid.'); } return true; } } 

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

 <?php namespace Swader\Diffbot\Exceptions; /** * Class DiffbotException * @package Swader\Diffbot\Exceptions */ class DiffbotException extends \Exception { } 

Давайте быстро объясним класс Diffbot.

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

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

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

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

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

Вывод

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