Статьи

Вклад в открытый исходный код: Gatekeeper Case Study

GateKeeper — довольно удобная библиотека регистрации, аутентификации и авторизации пользователей, которая использует собственную базу данных для хранения и запроса пользовательских записей. Это означает, что он полностью отделен от вашего основного приложения (так что вы можете, по сути, использовать Postgre или Mongo для своей бизнес-логики, используя совершенно другой движок, такой как MySQL, для базовых пользовательских записей) и легко настраивать и расширять.

Логотип с открытым исходным кодом

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

Этот пост не о гейткипере как таковом. Речь идет о содействии открытому исходному коду и правильном подходе к этому.

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

Шаг 1: Спросите владельца

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

Шаг 2: Вилка, клон, тест

Примечание: если вы хотите следовать, пожалуйста, клонируйте старую версию Gatekeeper, у которой пока нет этой функции. Это следует сделать.

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

Кнопка вилки на Github

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

 git clone https://github.com/swader/gatekeeper cd gatekeeper composer install vendor/bin/phpunit 

Все тесты должны быть зелеными:

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

 git checkout - b "feature-count" 

Шаг 3: План действий

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

Таким образом, нам нужно будет изменить:

  • Gatekeeper/DataSource — абстрактный класс DataSource
  • DataSource/MySQL — источник данных MySQL, содержащий фактические методы, которые мы используем
  • DataSource/Stub — для обновления заглушки, с которой пишутся другие источники данных, чтобы другие участники знали, что им тоже нужен метод count

Нам также необходимо создать новый обработчик Count , поскольку Gatekeeper использует магические статические вызовы для создания, поиска, обновления и удаления объектов, перенаправляя их соответствующему обработчику в зависимости от имени вызываемого метода. Например, посмотрите магический метод __callStatic в Gatekeeper/Gatekeeper.php и узнайте, как он откладывает вызовы методов до таких обработчиков, как Handler/Create.php или Handler/FindBy.php и т. Д.

Шаг 4: Просто сделай это ™

Делегирование статического вызова

Чтобы подготовить основу для нашего пользовательского обработчика Count , мы делегируем ему статический вызов и передаем аргумент и источник данных. Все это делается простым добавлением еще одного блока elseif в Gatekeeper::__callStatic :

  } elseif ($action == 'count') { $action = new \Psecio\Gatekeeper\Handler\Count($name, $args, self::$datasource); } 

Поскольку мы добавили новое действие, нам также нужно изменить статическое свойство $actions :

  /** * Allowed actions * @var array */ private static $actions = array( 'find', 'delete', 'create', 'save', 'clone', 'count' ); 

Граф обработчик

Затем мы создаем обработчик в файле Psecio/Gatekeeper/Handler/Count.php :

 <?php namespace Psecio\Gatekeeper\Handler; class Count extends \Psecio\Gatekeeper\Handler { /** * Execute the object/record count handling * * @throws \Psecio\Gatekeeper\Exception\ModelNotFoundException If model type is not found * @return int Count of entities */ public function execute() { $args = $this->getArguments(); $name = $this->getName(); $model = '\\Psecio\\Gatekeeper\\' . str_replace('count', '', $name) . 'Model'; if (class_exists($model) === true) { $instance = new $model($this->getDb()); $count = (!$args) ? $this->getDb()->count($instance) : $this->getDb()->count($instance, $args[0]); return (int)$count['count']; } else { throw new \Psecio\Gatekeeper\Exception\ModelNotFoundException( 'Model type ' . $model . ' could not be found' ); } } } 

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

Источник данных и заглушка

Далее, давайте избавимся от простых.

В Psecio/Gatekeeper/DataSource/Stub.php мы добавляем новый пустой метод:

  /** * Return the number of entities in DB per condition or in general * * @param \Modler\Model $model Model instance * @param array $where * @return bool Success/fail of action * @internal param array $where "Where" data to locate record */ public function count(\Modler\Model $model, array $where = array()){} 

Затем мы добавляем аналогичную подпись к аннотации:

  /** * Return the number of entities in DB per condition or in general * * @param \Modler\Model $model Model instance * @param array $where * @return bool Success/fail of action * @internal param array $where "Where" data to locate record */ public abstract function count(\Modler\Model $model, array $where = array()); 

После всего этого пришло время написать реальную логику, которая учитывает счет.

MySQL

Настало время изменить DataSource/MySQL.php . Мы добавим метод count прямо под методом find :

  /** * Find count of entities by where conditions. * All where conditions applied with AND * * @param \Modler\Model $model Model instance * @param array $where Data to use in "where" statement * @return array Fetched data */ public function count(\Modler\Model $model, array $where = array()) { $properties = $model->getProperties(); list($columns, $bind) = $this->setup($where); $update = array(); foreach ($bind as $column => $name) { // See if we keep to transfer it over to a column name if (array_key_exists($column, $properties)) { $column = $properties[$column]['column']; } $update[] = $column.' = '.$name; } $sql = 'select count(*) as `count` from '.$model->getTableName(); if (!empty($update)) { $sql .= ' where '.implode(' and ', $update); } $result = $this->fetch($sql, $where, true); return $result; } 

Собирая логику из аналогичного метода find описанного выше, наш метод count делает следующее:

  1. Получить свойства, как определено в рассматриваемой модели (например, см. UserModel::$properties )
  2. Разделите значения как переданные через $where на столбцы и их значения
  3. Создайте часть запроса WHERE properties и выяснив, имеют ли какие-либо из них имена, отличные от запрошенных (например, у запрашиваемого FirstName есть аналог базы данных first_name )
  4. Построить весь запрос
  5. Выполните с принудительным single режимом на true (см. Метод fetch ), потому что мы ожидаем получить только одно значение — целое число, указывающее счет.
  6. Верните счет.

Шаг 5: Тестирование

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

Реализация экспериментальной версии

Давайте сделаем ручной тест. Во-первых, мы передадим нашу работу в онлайн-версию.

 git add -A git commit -m "Adding count feature" git push origin feature-count 

Изменения теперь будут в нашей форке, онлайн. Затем давайте создадим новый проект в другой папке со следующим файлом composer.json :

 { "require": { "psecio/gatekeeper": "dev-master" }, "repositories": [ { "type": "vcs", "url": "https://github.com/swader/gatekeeper" } ] } 

Используя функцию репозиториев Composer, мы можем убедиться, что Composer извлекает нашу копию Gatekeeper вместо оригинала, все еще думая, что он имеет оригинал — это позволяет нам тестировать наши изменения, как в реальном проекте с использованием Gatekeeper, — возможно, более реалистичное тестирование сценарий, чем юнит-тесты будет в этой точке Сохраните и выйдите из этого файла, а затем запустите:

 composer require symfony/var-dumper --dev 

Это позволит установить выше определенный пользовательский пакет и VarDumper Symfony для упрощения отладки. Вас могут попросить токен Github во время установки — если это так, просто следуйте инструкциям.

И вот, если мы сейчас заглянем в основной класс Gatekeeper , то увидим, что все наши обновления count уже есть. Далее давайте теперь следуем типичной процедуре установки Gatekeeper, выполнив vendor/bin/setup.sh и следуя инструкциям. Если вы используете Homestead Improved , просто введите значения по умолчанию ( localhost , homestead , homestead , secret ).

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

Теперь давайте создадим файл index.php который мы будем использовать для тестирования:

 <?php require_once 'vendor/autoload.php'; use Psecio\Gatekeeper\Gatekeeper; Gatekeeper::init('./'); $groups = [ 'admin' => 'Administrators', 'users' => 'Regular users' ]; foreach ($groups as $name => $description) { if (!Gatekeeper::findGroupByName($name)) { Gatekeeper::createGroup([ 'name' => $name, 'description' => $description ]); } } 

Мы активируем автозагрузчик, инициализируем Gatekeeper (он использует файл .env из корня нашей папки для учетных данных) и устанавливаем две группы по умолчанию.

Sequel Pro Скриншот двух групп

Тогда давайте продолжим тестирование и рассчитываем на группы:

 dump(Gatekeeper::countGroup()); dump(Gatekeeper::countGroup(['id' => 1])); 

Конечно же, это работает.

Точное количество групп

Давайте проверим пользователей сейчас.

 Gatekeeper::countUser(); 

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

 Gatekeeper::createUser([ 'username' => 'bruno-admin', 'first_name' => 'Bruno', 'last_name' => 'Skvorc', 'email' => 'bruno.skvorc@sitepoint.com', 'password' => '12345', 'groups' => (Gatekeeper::countUser()) ? ['users'] : ['admin'] ]); Gatekeeper::createUser([ 'username' => 'reg-user', 'first_name' => 'Reggie', 'last_name' => 'User', 'email' => 'reg@example.com', 'password' => '12345', 'groups' => (Gatekeeper::countUser()) ? ['users'] : ['admin'] ]); dump(Gatekeeper::findUserByUsername('bruno-admin')->groups[0]->name); dump(Gatekeeper::findUserByUsername('reg-user')->groups[0]->name); 

Несомненно, вывод точен — первая группа, напечатанная на экране, является admin , а вторая — users .

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

Отправка PR

Сначала мы идем на нашу собственную развилку проекта. Затем мы нажимаем кнопку «Новый запрос на извлечение». На следующем экране все должно быть зеленым — пользовательский интерфейс должен сказать «Возможность слияния»:

Возможность слияния

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

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

Вот и все — нажатием кнопки «Создать запрос на извлечение» все заканчивается, и все, что мы можем сейчас сделать, — это дождаться обратной связи от владельца проекта.

Вывод

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

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

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

Хотите поделиться своими историями? Дайте нам знать — мы хотели бы изучить их!