Construindo um pacote de transporte sem instalar o MODX



Escrever seus pacotes para o MODX não é fácil para iniciantes, e um desenvolvedor experiente às vezes se diverte. Mas o iniciante está assustado, e o experiente entende :).

Este post fala sobre como você pode escrever e criar um pacote de componentes para o MODX sem instalar e configurar o próprio MODX. O nível está acima da média, portanto, em alguns casos, você pode ter que mexer com o cérebro, mas vale a pena.

Peço detalhes sob gato.

Uma vez, quando o MODX Revolution apareceu, era na versão beta inicial, os desenvolvedores ainda não sabiam como trabalhar com ele e como escrever plugins para ele. Bem, exceto a equipe que se debruçou sobre o CMS. E a equipe, devo dizer, teve sucesso parcial e forneceu ao próprio sistema a coleta conveniente de pacotes que podem ser instalados no repositório, o que parece lógico. Mas desde então, muitos anos se passaram e os requisitos para pacotes e sua montagem mudaram um pouco.

Copiar e colar é ruim, embora nem sempre


Nos últimos meses, fui assombrado com a idéia de por que, para criar um pacote para o MODX, você deve instalá-lo, criar um banco de dados, criar um administrador, etc. Tantas ações extras. Não, não há nada errado com isso se você configurá-lo uma vez e depois usá-lo. Muitos fazem isso. Mas e quando você deseja confiar a assembléia ao script e tomar um café você mesmo?

Aconteceu que os criadores do MODX estavam acostumados a trabalhar com o próprio MODX e adicionaram classes ao pacote diretamente no kernel. Eles também escreveram os primeiros componentes, os primeiros scripts de compilação, que foram usados ​​como exemplos por outros desenvolvedores que simplesmente copiaram a solução, nem sempre investigando a essência do que estava acontecendo. E eu fiz isso.

Mas a tarefa é automatizar a montagem do pacote, preferencialmente no servidor, sempre com um conjunto mínimo de software necessário, com recursos mínimos e, portanto, com maior velocidade. A tarefa foi definida e, depois de estudar a fonte, Jason freou no bate-papo e encontrou uma solução.

E qual?


A primeira coisa que descobri é que o código responsável pela compilação do pacote está diretamente na biblioteca xPDO, e no MODX existem apenas classes de wrapper que fornecem uma API mais conveniente e são mais fáceis de trabalhar, mas apenas se o MODX estiver instalado. Portanto, provavelmente apenas o xPDO pode ser usado de alguma forma, mas no código, o construtor do objeto xPDO exige que você especifique dados para a conexão com o banco de dados.

public function __construct( $dsn, $username = '', $password = '', $options = [], $driverOptions= null ); 

Após interrogar Jason, ficou claro que, embora os parâmetros precisem ser definidos, a conexão física real com o banco de dados ocorre exatamente no momento em que é necessário. Carga preguiçosa em toda a sua glória. O segundo problema foi resolvido.

A terceira questão foi a conexão do xPDO ao projeto. O Composer imediatamente veio à mente, mas a versão 2.x na qual o MODX atual está sendo executado não suporta o Composer, e a ramificação 3.x usa namespaces e os nomes de classe são escritos de maneira diferente da 2.x, o que leva a conflitos e erros. Em geral, incompatível. Então eu tive que usar ferramentas git e conectar o xPDO como um submódulo.

Como usar submódulos



Primeiro, leia a documentação neles.

Então, se este for um novo projeto, você precisará adicionar um submódulo:

 $ git submodule add https://github.com/username/reponame 

Este comando clonará e instalará um submódulo no seu projeto. Então você precisará adicionar a pasta submodule ao seu repositório com o comando git add. Ele não adicionará a pasta inteira ao submódulo, mas adicionará ao git apenas um link para o último commit do submódulo.

Para que outro desenvolvedor possa clonar o projeto com todas as dependências, é necessário criar uma configuração .gitmodules para submodules. No projeto Slackify, é assim:

 [submodule "_build/xpdo"] path = _build/xpdo url = https://github.com/modxcms/xpdo.git branch = 2.x 

Depois disso, ao clonar, basta especificar o sinalizador recursivo e o git fará o download de todos os repositórios dependentes.
Como resultado, temos o xPDO, o xPDO pode ser usado sem conexão com o banco de dados; se não for necessário, o xPDO pode ser conectado ao código do componente como uma dependência externa (sub-módulo git). Agora a implementação do script de construção.

Vamos entender


Descreverei o script de compilação do Slackify addon postado recentemente por mim. Esse componente é gratuito e está disponível publicamente no GitHub, o que facilitará o auto-estudo.

Connect xPDO


Nós omitimos a tarefa de constantes com o nome do pacote e outras chamadas necessárias e conectamos o xPDO.

 require_once 'xpdo/xpdo/xpdo.class.php'; require_once 'xpdo/xpdo/transport/xpdotransport.class.php'; $xpdo = xPDO::getInstance('db', [ xPDO::OPT_CACHE_PATH => __DIR__ . '/../cache/', xPDO::OPT_HYDRATE_FIELDS => true, xPDO::OPT_HYDRATE_RELATED_OBJECTS => true, xPDO::OPT_HYDRATE_ADHOC_FIELDS => true, xPDO::OPT_CONNECTIONS => [ [ 'dsn' => 'mysql:host=localhost;dbname=xpdotest;charset=utf8', 'username' => 'test', 'password' => 'test', 'options' => [xPDO::OPT_CONN_MUTABLE => true], 'driverOptions' => [], ] ] ]); 

Adicionei o sub-módulo xPDO à pasta _build, necessária apenas no estágio de desenvolvimento e montagem do pacote e que não entrará no arquivo principal do componente. A segunda cópia do xPDO no site com MODX ao vivo não é necessária.

Nas configurações de conexão do xPDO, defino o nome do banco de dados em dsn , mas ele não desempenha nenhum papel. É importante que a pasta de cache dentro do xPDO seja gravável. É isso aí, o xPDO é inicializado.

Fazendo um truque complicado com classes


Ao usar o MODX instalado ao criar o pacote, tudo é simples, pegamos e criamos um objeto da classe que precisamos. O MODX realmente encontra a classe necessária, encontra a implementação necessária para esta classe (a classe com o postfix _mysql), que depende do banco de dados e cria o objeto desejado (por causa desse recurso, você pode receber erros ao criar o pacote que a classe * _mysql não encontrado, isso não é assustador). No entanto, não temos base nem implementação. De alguma forma, precisamos substituir a classe desejada, o que estamos fazendo.

 class modNamespace extends xPDOObject {} class modSystemSetting extends xPDOObject {} 

Criamos uma classe fictícia (stub), necessária para criar o objeto desejado. Isso não teria que ser feito se o xPDO não verificasse especificamente a qual classe o objeto pertence. Mas ele verifica.

Mas há casos especiais em que você precisa fazer um pouco mais do que apenas definir uma classe. Esses são casos de dependências entre classes. Por exemplo, precisamos adicionar um plugin à categoria. No código, apenas $category->addOne($plugin); mas no nosso caso isso não vai funcionar.

Se você já examinou o esquema do banco de dados MODX , provavelmente viu elementos como agregado e composto. Está escrito sobre eles na documentação , mas se de uma maneira simples, eles descrevem o relacionamento entre as classes.

No nosso caso, pode haver vários plugins em uma categoria, pelos quais o elemento agregado é responsável pela classe modCategory . Portanto, como temos uma classe sem uma implementação concreta, precisamos indicar essa conexão manualmente. É mais fácil fazer isso substituindo o método getFKDefinition :

 class modCategory extends xPDOObject { public function getFKDefinition($alias) { $aggregates = [ 'Plugins' => [ 'class' => 'modPlugin', 'local' => 'id', 'foreign' => 'category', 'cardinality' => 'many', 'owner' => 'local', ] ]; return isset($aggregates[$alias]) ? $aggregates[$alias] : []; } } 

No nosso componente, apenas plugins são usados, portanto, adicionamos links apenas para eles. Depois disso, o método addMany da classe modCategory pode adicionar facilmente os plugins necessários à categoria e, em seguida, ao pacote.

Crie um pacote


 $package = new xPDOTransport($xpdo, $signature, $directory); 

Como você pode ver, tudo é muito, muito simples. Aqui precisamos passar o parâmetro $xpdo , que inicializamos no início. Se não fosse por esse momento, não haveria problema 2. $signature - o nome do pacote, incluindo a versão, $directory - o local onde o pacote será cuidadosamente colocado. De onde vêm essas variáveis, veja você mesmo na fonte.

Crie um espaço para nome e adicione-o ao pacote


Precisamos de um espaço para nome para vincular léxicos e configurações do sistema a ele. No nosso caso, apenas por isso, outros ainda não são considerados.

 $namespace = new modNamespace($xpdo); $namespace->fromArray([ 'id' => PKG_NAME_LOWER, 'name' => PKG_NAME_LOWER, 'path' => '{core_path}components/' . PKG_NAME_LOWER . '/', ]); $package->put($namespace, [ xPDOTransport::UNIQUE_KEY => 'name', xPDOTransport::PRESERVE_KEYS => true, xPDOTransport::UPDATE_OBJECT => true, xPDOTransport::RESOLVE_FILES => true, xPDOTransport::RESOLVE_PHP => true, xPDOTransport::NATIVE_KEY => PKG_NAME_LOWER, 'namespace' => PKG_NAME_LOWER, 'package' => 'modx', 'resolve' => null, 'validate' => null ]); 

A primeira parte é clara para quem já escreveu um código para o MODX. O segundo, com a adição do pacote, é um pouco mais complicado. O método put usa 2 parâmetros: o próprio objeto e uma matriz de parâmetros que descrevem esse objeto e seu possível comportamento no momento da instalação do pacote. Por exemplo, xPDOTransport::UNIQUE_KEY => 'name' significa que o campo de name com o nome do próprio espaço para nome como valor será usado para o espaço como uma chave exclusiva no banco de dados. Você pode ler mais sobre os parâmetros na documentação do xPDO e, melhor, estudando o código-fonte.

Da mesma maneira, você pode adicionar outros objetos, como configurações do sistema.

 $package->put($setting, [ xPDOTransport::UNIQUE_KEY => 'key', xPDOTransport::PRESERVE_KEYS => true, xPDOTransport::UPDATE_OBJECT => true, 'class' => 'modSystemSetting', 'resolve' => null, 'validate' => null, 'package' => 'modx', ]); 

Crie uma categoria


Com a adição de uma categoria, tive a maior piada quando descobri tudo. Os elementos colocados em uma categoria no modelo xPDO devem pertencer a essa categoria, ou seja, ser aninhado nele e somente então a própria categoria deve ser aninhada no pacote. E, ao mesmo tempo, você precisa levar em consideração os relacionamentos entre as classes, que eu já descrevi acima. Demorou muito tempo para entender, perceber e aplicá-lo corretamente.

 $package->put($category, [ xPDOTransport::UNIQUE_KEY => 'category', xPDOTransport::PRESERVE_KEYS => false, xPDOTransport::UPDATE_OBJECT => true, xPDOTransport::ABORT_INSTALL_ON_VEHICLE_FAIL => true, xPDOTransport::RELATED_OBJECTS => true, xPDOTransport::RELATED_OBJECT_ATTRIBUTES => [ 'Plugins' => [ xPDOTransport::UNIQUE_KEY => 'name', xPDOTransport::PRESERVE_KEYS => false, xPDOTransport::UPDATE_OBJECT => false, xPDOTransport::RELATED_OBJECTS => true ], 'PluginEvents' => [ xPDOTransport::UNIQUE_KEY => ['pluginid', 'event'], xPDOTransport::PRESERVE_KEYS => true, xPDOTransport::UPDATE_OBJECT => false, xPDOTransport::RELATED_OBJECTS => true ] ], xPDOTransport::NATIVE_KEY => true, 'package' => 'modx', 'validate' => $validators, 'resolve' => $resolvers ]); 

Parece monstruoso, mas não tão visto. Um parâmetro importante é xPDOTransport::RELATED_OBJECTS => true , que indica que a categoria possui elementos aninhados que também precisam ser empacotados e depois instalados.

Como a maioria dos módulos contém vários elementos (chunks, snippets, plugins), a categoria com elementos é a parte mais importante do pacote de transporte. Portanto, é aqui que são especificados validadores e resolvedores, que são executados durante a instalação do pacote.
Os validadores são executados antes da instalação, os resolvedores - depois.

Eu quase esqueci, antes de embalar a categoria, precisamos adicionar nossos elementos a ela. Assim:

 $plugins = include $sources['data'] . 'transport.plugins.php'; if (is_array($plugins)) { $category->addMany($plugins, 'Plugins'); } 

Adicione outros dados ao pacote.


No pacote, você precisa adicionar outro arquivo com uma licença, um arquivo com um log de alterações e um arquivo com uma descrição do componente. Se necessário, você pode adicionar outro script especial através do atributo setup-options , que mostrará a janela antes de instalar o pacote. É quando, em vez de "Instalar", o botão "Opções de instalação". E a partir da versão do MODX 2.4, tornou-se possível especificar dependências entre pacotes usando o atributo require, e nele você também pode especificar a versão do PHP e MODX.

 $package->setAttribute('changelog', file_get_contents($sources['docs'] . 'changelog.txt')); $package->setAttribute('license', file_get_contents($sources['docs'] . 'license.txt')); $package->setAttribute('readme', file_get_contents($sources['docs'] . 'readme.txt')); $package->setAttribute('requires', ['php' => '>=5.4']); $package->setAttribute('setup-options', ['source' => $sources['build'] . 'setup.options.php']); 

Nós embalamos


 if ($package->pack()) { $xpdo->log(xPDO::LOG_LEVEL_INFO, "Package built"); } 

É isso, pegue o pacote pronto em _packages , bem, ou de onde você configurou a montagem.

Qual é o resultado?


O resultado superou minhas expectativas, pois essa abordagem, apesar de impor algumas limitações e, em alguns lugares, acrescenta algum inconveniente, mas vence em termos de possibilidades de aplicação.

Para compilar o pacote, basta executar 2 comandos:

 git clone --recursive git@github.com:Alroniks/modx-slackify.git cd modx-slackify/_build && php build.transport.php 

O primeiro é a clonagem do repositório e de seus submódulos. Um parâmetro importante é --recursive , pois o git fará o download e instalará, além do próprio código do componente, todas as dependências descritas como sub-módulos.

O segundo é construir o pacote diretamente. Depois disso, você pode pegar o _packages pasta _packages e carregá-lo, por exemplo, no repositório.

As perspectivas são amplas. Por exemplo, você pode configurar um gancho no GitHub, que, após se comprometer com uma ramificação, executará um script no servidor que coletará o pacote e o colocará em todos os sites que você possui. Ou faça o upload da nova versão para algum repositório e, nesse momento, você fará café para si, como eu disse no começo. Ou você pode criar e escrever testes para o módulo, executar o teste e criar através do Jenkins ou Travis. Sim, você pode criar vários cenários. Com essa abordagem, agora é muito mais fácil fazer isso.

Faça perguntas, tente responder.

PS Não passe, coloque uma estrela do Slackify no GitHub , por favor.

Source: https://habr.com/ru/post/pt439420/


All Articles