
Escribir sus paquetes para MODX no es fácil para un principiante, y un desarrollador experimentado a veces se lo pasa en grande. Pero el principiante tiene miedo, y el experimentado entiende :).
Esta publicación habla sobre cómo puede escribir y construir un paquete de componentes para MODX sin instalar y configurar MODX. El nivel está por encima de la media, por lo que es posible que tengas que estrujarte el cerebro en algunos casos, pero vale la pena.
Pido detalles bajo cat.
Una vez, cuando apareció MODX Revolution, estaba en la versión beta temprana, los desarrolladores aún no sabían cómo trabajar con él y cómo escribir complementos para él. Bueno, excepto por el equipo que estudió el CMS. Y el equipo, debo decir, tuvo éxito en parte y proporcionó al sistema en sí mismo para recopilar convenientemente paquetes que luego se pueden instalar a través del repositorio, lo que parece lógico. Pero desde entonces, han pasado muchos años y los requisitos para los paquetes y su montaje han cambiado un poco.
Copiar y pegar es malo, aunque no siempre
En los últimos meses, me preguntaba por qué, para crear un paquete para MODX, debe instalarlo, crear una base de datos, crear un administrador, etc. Tantas acciones extra. No, no hay nada de malo en eso si lo configura una vez y luego lo usa. Muchos lo hacen Pero, ¿qué pasa cuando quieres confiar la asamblea al guión e ir a tomar un café tú mismo?
Sucedió que los creadores de MODX estaban acostumbrados a trabajar con MODX y agregaron clases al paquete directamente al kernel. También escribieron los primeros componentes, los primeros scripts de compilación, que luego fueron utilizados como ejemplos por otros desarrolladores que simplemente copiaron la solución, no siempre profundizando en la esencia de lo que estaba sucediendo. Y lo hice
Pero la tarea es automatizar el ensamblaje del paquete, preferiblemente en el servidor, siempre con un conjunto mínimo de software requerido, con recursos mínimos y, por lo tanto, con mayor velocidad. La tarea se estableció y después de estudiar la fuente, Jason frenó en el chat y encontró una solución.
Y cual?
Lo primero que descubrí es que el código responsable de compilar el paquete se encuentra directamente en la biblioteca xPDO, y en MODX solo hay clases de contenedor que proporcionan una API más conveniente y son algo más fáciles de trabajar, pero solo si MODX está instalado. Por lo tanto, probablemente solo se pueda usar xPDO de alguna manera, pero en el código el constructor del objeto xPDO requiere especificar datos para la conexión de la base de datos.
public function __construct( $dsn, $username = '', $password = '', $options = [], $driverOptions= null );
Después de interrogar a Jason, quedó claro que, aunque los parámetros deben establecerse, la conexión física real a la base de datos se produce exactamente en el momento en que es necesario. Carga perezosa en todo su esplendor. El segundo problema ha sido resuelto.
El tercer problema fue el problema de conectar xPDO al proyecto. Composer me vino a la mente de inmediato, pero la versión 2.x en la que se ejecuta el MODX actual no es compatible con Composer, y la rama 3.x usa espacios de nombres y los nombres de clase se escriben de manera diferente que en 2.x, lo que genera conflictos y errores. En general, incompatible. Luego tuve que usar herramientas git y conectar xPDO como un submódulo.
Cómo usar submódulos
Primero, lea la documentación sobre ellos.
Luego, si este es un proyecto nuevo, debe agregar un submódulo:
$ git submodule add https://github.com/username/reponame
Este comando clonará e instalará un submódulo en su proyecto. Luego, deberá agregar la carpeta de submódulos a su repositorio con el comando git add. No agregará toda la carpeta con el submódulo, pero agregará a git solo un enlace al último commit desde el submódulo.
Para que otro desarrollador pueda clonar el proyecto con todas las dependencias, debe crear una configuración .gitmodules para submódulos. En el proyecto Slackify, es así:
[submodule "_build/xpdo"] path = _build/xpdo url = https://github.com/modxcms/xpdo.git branch = 2.x
Después de eso, al clonar, solo especifique el indicador recursivo y git descargará todos los repositorios dependientes.
Como resultado, tenemos xPDO, xPDO se puede usar sin conectarse a la base de datos, si no es necesario, xPDO se puede conectar al código del componente como una dependencia externa (submódulo git). Ahora la implementación del script de compilación.
Entendamos
Describiré el
script de compilación del complemento
Slackify publicado recientemente por mí. Este componente es gratuito y está disponible públicamente en GitHub, lo que facilitará el autoestudio.
Conectar xPDO
Omitimos la tarea de constantes con el nombre del paquete y otras llamadas necesarias y conectamos 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' => [], ] ] ]);
Agregué el submódulo xPDO a la carpeta _build, que solo necesitamos en la etapa de desarrollo y ensamblaje del paquete y que no entrará en el archivo principal del componente. La segunda copia de xPDO en el sitio con Live MODX no la necesitamos.
En la configuración de conexión xPDO, configuro el nombre de la base de datos en
dsn
, pero no juega ningún papel. Es importante que la carpeta de caché dentro de xPDO sea grabable. Eso es todo, xPDO se inicializa.
Haciendo un truco complicado con clases
Al usar el MODX instalado al crear el paquete, todo es simple, tomamos y creamos un objeto de la clase que necesitamos. MODX realmente encuentra la clase requerida, encuentra la implementación necesaria para esta clase (la clase con el postfix _mysql), que depende de la base de datos y luego crea el objeto deseado (debido a esta característica, puede obtener errores al construir el paquete que la clase * _mysql no encontrado, esto no da miedo). Sin embargo, no tenemos una base ni una implementación. Necesitamos reemplazar de alguna manera la clase deseada, que estamos haciendo.
class modNamespace extends xPDOObject {} class modSystemSetting extends xPDOObject {}
Creamos una clase ficticia (stub), que es necesaria para crear el objeto deseado. Esto no debería hacerse si xPDO no verifica específicamente a qué clase pertenece el objeto. Pero él lo comprueba.
Pero hay casos especiales en los que necesita hacer un poco más que solo definir una clase. Estos son casos de dependencias entre clases. Por ejemplo, necesitamos agregar un complemento a la categoría. En el código, solo
$category->addOne($plugin);
pero en nuestro caso esto no funcionará.
Si alguna vez examinó
el esquema de base de datos MODX , probablemente vio elementos como agregado y compuesto. Está escrito sobre ellos en la
documentación , pero si de una manera simple, describen la relación entre clases.
En nuestro caso, puede haber varios complementos en una categoría, de los cuales el elemento agregado es responsable de la clase
modCategory
. Por lo tanto, dado que tenemos una clase sin una implementación concreta, debemos indicar esta conexión a mano. Es más fácil hacer esto anulando el 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] : []; } }
En nuestro componente, solo se usan complementos, por lo que agregamos enlaces solo para ellos. Después de eso, el método addMany de la clase modCategory puede agregar fácilmente los complementos necesarios a la categoría y luego al paquete.
Crea un paquete
$package = new xPDOTransport($xpdo, $signature, $directory);
Como puede ver, todo es muy, muy simple. Aquí necesitábamos pasar el parámetro
$xpdo
, que inicializamos al principio. Si no fuera por este momento, no habría problema 2.
$signature
- el nombre del paquete, incluida la versión,
$directory
- el lugar donde el paquete se colocará cuidadosamente. De dónde provienen estas variables, compruébelo usted mismo en la fuente.
Cree un espacio de nombres y agréguelo al paquete
Necesitamos un espacio de nombres para vincular léxicos y configuraciones del sistema. En nuestro caso, solo por esto, otros aún no se consideran.
$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 ]);
La primera parte es clara para cualquiera que haya escrito código para MODX. El segundo, con la adición al paquete, es un poco más complicado. El método
put
toma 2 parámetros: el objeto en sí y una matriz de parámetros que describen este objeto y su posible comportamiento al momento de instalar el paquete. Por ejemplo,
xPDOTransport::UNIQUE_KEY => 'name'
significa que el campo de
name
con el nombre del propio espacio de nombres como valor se usará para el espacio de nombres como una clave única en la base de datos. Puede leer más sobre los parámetros
en la documentación de xPDO , y mejor estudiando el código fuente.
Del mismo modo, puede agregar otros objetos, como la configuración del sistema.
$package->put($setting, [ xPDOTransport::UNIQUE_KEY => 'key', xPDOTransport::PRESERVE_KEYS => true, xPDOTransport::UPDATE_OBJECT => true, 'class' => 'modSystemSetting', 'resolve' => null, 'validate' => null, 'package' => 'modx', ]);
Crea una categoría
Con la adición de una categoría, tuve la mayor mordaza cuando lo descubrí todo. Los elementos puestos en una categoría en el modelo xPDO deben pertenecer a esta categoría, es decir. estar anidado en él, y solo entonces la categoría en sí debe estar anidada en el paquete. Y al mismo tiempo, debe tener en cuenta las relaciones entre las clases, que ya he descrito anteriormente. Tomó bastante tiempo entenderlo, darse cuenta y aplicarlo correctamente.
$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, pero no tan visto. Un parámetro importante es
xPDOTransport::RELATED_OBJECTS => true
, que indica que la categoría tiene elementos anidados que también deben empaquetarse y luego instalarse.
Dado que la mayoría de los módulos contienen varios elementos (fragmentos, fragmentos, complementos), la categoría con elementos es la pieza más importante del paquete de transporte. Por lo tanto, es aquí donde se especifican los validadores y los solucionadores, que se realizan durante la instalación del paquete.
Los validadores se realizan antes de la instalación, los solucionadores, después.
Casi se me olvida, antes de empacar la categoría, necesitamos agregarle nuestros elementos. Así:
$plugins = include $sources['data'] . 'transport.plugins.php'; if (is_array($plugins)) { $category->addMany($plugins, 'Plugins'); }
Agregue otros datos al paquete.
En el paquete, debe agregar otro archivo con una licencia, un archivo con un registro de cambios y un archivo con una descripción del componente. Si es necesario, puede agregar otro script especial a través del atributo
setup-options
, que mostrará la ventana antes de instalar el paquete. Esto es cuando en lugar de "Instalar" el botón "Opciones de instalación". Y a partir de la versión de MODX 2.4 se hizo posible especificar dependencias entre paquetes usando el atributo
requires
, y en él también se puede especificar la versión de PHP y 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']);
Empacamos
if ($package->pack()) { $xpdo->log(xPDO::LOG_LEVEL_INFO, "Package built"); }
Eso es todo, recoja el paquete terminado en
_packages
, bien, o desde donde configuró el ensamblaje.
Cual es el resultado?
El resultado superó mis expectativas, ya que este enfoque, aunque impone algunas limitaciones y en algunos lugares agrega algunos inconvenientes, pero gana en términos de posibilidades de aplicación.
Para construir el paquete, solo ejecute 2 comandos:
git clone --recursive git@github.com:Alroniks/modx-slackify.git cd modx-slackify/_build && php build.transport.php
El primero es la clonación del repositorio y sus submódulos. Un parámetro importante es
--recursive
, gracias a él git descargará e instalará, además del código del componente en sí, todas las dependencias descritas como submódulos.
El segundo es construir el paquete directamente. Después de eso, puede recoger el
package-1.0.0-pl.transport.zip
terminado
_packages
carpeta
_packages
y cargarlo, por ejemplo, en el repositorio.
Las perspectivas son amplias. Por ejemplo, puede configurar un enlace en GitHub que, después de comprometerse con una rama, ejecutará un script en su servidor que recopilará el paquete y lo colocará en todos los sitios que tenga. O suba la nueva versión a algún repositorio, y en ese momento preparará café para usted, como dije al principio. O puede crear y escribir pruebas para el módulo y ejecutar la ejecución de prueba y construir a través de Jenkins o Travis. Sí, un montón de escenarios que se te pueden ocurrir. Con este enfoque, hacer esto ahora es mucho más fácil.
Haga preguntas, intente responder.
PD: No pases,
pon una estrella de Slackify en GitHub , por favor.