
对于初学者来说,为MODX编写软件包并不容易,而且经验丰富的开发人员有时会度过一段美好的时光。 但是初学者很害怕,有经验的人知道:)。
本文讨论如何在不安装和配置MODX的情况下为MODX编写和构建组件包。 该水平高于平均水平,因此在某些情况下您可能需要绞尽脑汁,但这是值得的。
我要求猫下提供详细信息。
曾经,当MODX Revolution刚出现时,它是早期的beta版本,开发人员还不知道如何使用它以及如何为它编写插件。 好吧,除了研究CMS的团队。 我必须说,该团队取得了部分成功,并为系统提供了方便地收集可以通过仓库安装的软件包的能力,这似乎是合乎逻辑的。 但是自那以后,已经过去了很多年,对包装及其组装的要求也有所变化。
复制粘贴是邪恶的,尽管并非总是如此
在过去的几个月中,我一直在为为什么要构建用于MODX的软件包而必须安装,创建数据库,创建管理员等问题困扰着我。 这么多额外的动作。 不,如果您设置一次然后使用它,这没有任何问题。 许多这样做。 但是,当您想将程序集委托给脚本并自己去喝咖啡时呢?
碰巧的是,MODX的创建者习惯于使用MODX本身,并直接将类添加到包中。 他们还编写了第一个组件,第一个构建脚本,然后被其他开发人员用作示例,他们只是复制解决方案,而并不总是深入研究正在发生的事情的本质。 而我做到了。
但是任务是始终使用最少的所需软件集,最少的资源并因此以更高的速度来自动化包装的组装,最好在服务器上。 任务已经确定,在研究了源代码之后,Jason在聊天中刹车找到了解决方案。
哪一个?
我发现的第一件事是,负责直接构建软件包的代码位于xPDO库中,而在MODX中,只有包装器类提供了更方便的API,并且在使用时更容易一些,但前提是安装了MODX。 因此,可能只能以某种方式使用xPDO,但是在代码中,xPDO对象的构造函数要求您为数据库连接指定数据。
public function __construct( $dsn, $username = '', $password = '', $options = [], $driverOptions= null );
在询问了Jason之后,很明显,尽管需要设置参数,但与数据库的实际物理连接恰好在必要时发生。 懒惰的负载充满了荣耀。 第二个问题已经解决。
第三个问题是将xPDO连接到项目的问题。 立即想到了Composer,但是运行当前MODX的2.x版本不支持Composer,并且3.x分支使用名称空间,并且类名的写法与2.x中的写法不同,从而导致冲突和错误。 通常,不兼容。 然后,我不得不使用git工具并将xPDO作为子模块进行连接。
如何使用子模块
首先,阅读有关它们的文档 。
然后,如果这是一个新项目,则需要添加一个子模块:
$ git submodule add https://github.com/username/reponame
此命令将在您的项目中克隆并安装一个子模块。 然后,您需要使用git add命令将子模块文件夹添加到您的存储库中。 它不会将整个文件夹与子模块一起添加,但只会将到子模块上次提交的链接添加到git中。
为了让其他开发人员可以克隆具有所有依赖项的项目,需要为子模块创建一个.gitmodules配置。 在Slackify项目中,它是这样的:
[submodule "_build/xpdo"] path = _build/xpdo url = https://github.com/modxcms/xpdo.git branch = 2.x
之后,在克隆时,只需指定递归标志,git就会下载所有相关的存储库。
结果,我们有了xPDO,可以在不连接数据库的情况下使用xPDO,如果没有必要,可以将xPDO作为外部依赖项(git子模块)连接到组件代码。 现在执行构建脚本。
让我们来了解
我将描述我最近发布
的 Slackify插件的
构建脚本 。 该组件是免费的,并且可以在GitHub上公开获得,这将有助于自学。
连接xPDO
我们忽略了带有包名称和其他必要调用的常量任务,并连接了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' => [], ] ] ]);
我将xPDO子模块添加到_build文件夹中,该子模块仅在软件包的开发和组装阶段才需要,而不会进入组件的主归档中。 我们不需要带有实时MODX的站点上的xPDO的第二个副本。
在xPDO连接设置中,我在
dsn
设置了数据库名称,但是它没有任何作用。 xPDO内的缓存文件夹可写很重要。 就是这样,xPDO已初始化。
用类进行棘手的黑客攻击
在创建软件包时使用已安装的MODX时,一切都很简单,我们采用并创建所需类的对象。 MODX实际上会找到所需的类,并为该类(带有_mysql后缀的类)找到所需的实现,该实现取决于数据库,然后创建所需的对象(由于此功能,在构建该类的软件包时可能会出错*找不到_mysql,这并不可怕)。 但是,我们既没有基础也没有实现。 我们需要以某种方式替换我们正在执行的所需类。
class modNamespace extends xPDOObject {} class modSystemSetting extends xPDOObject {}
我们创建一个虚拟类(存根),这是创建所需对象所必需的。 如果xPDO没有专门检查对象属于哪个类,则不必这样做。 但是他检查。
但是在某些特殊情况下,您需要做的不仅仅是定义类。 这些是类之间依赖关系的情况。 例如,我们需要将插件添加到类别中。 在代码中,只需
$category->addOne($plugin);
但是对于我们来说,这是行不通的。
如果您曾经研究
过MODX数据库架构 ,则可能会看到诸如聚合和复合之类的元素。 它是在
文档中介绍它们的,但是如果以一种简单的方式,它们将描述类之间的关系。
在我们的例子中,一个类别中可以有多个插件,由其聚合元素负责
modCategory
类。 因此,由于我们有一个没有具体实现的类,因此我们需要手动指示此连接。 通过重写
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] : []; } }
在我们的组件中,仅使用插件,因此我们仅为其添加链接。 之后,modCategory类的addMany方法可以轻松地将必要的插件添加到类别中,然后再添加到包中。
创建一个包
$package = new xPDOTransport($xpdo, $signature, $directory);
如您所见,一切都非常非常简单。 在这里,我们需要传递参数
$xpdo
,我们在开始时就对其进行了初始化。 如果暂时不存在,那将不会有问题2。
$signature
程序包的名称,包括版本,
$directory
小心放置程序包的位置。 这些变量来自哪里,请在源代码中亲自查看。
创建一个名称空间并将其添加到包中
我们需要一个名称空间,以便将词典和系统设置绑定到该名称空间。 就我们而言,仅此而已,尚未考虑其他因素。
$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 ]);
对于曾经为MODX编写代码的任何人,第一部分都很清楚。 第二个,加上软件包,要复杂一些。
put
方法具有两个参数:对象本身和描述该对象及其在安装软件包时可能发生的行为的参数数组。 例如,
xPDOTransport::UNIQUE_KEY => 'name'
意味着将
name
空间本身的名称作为值的
name
字段将用作名称空间,作为数据库中的唯一键。 您可以
在xPDO文档中阅读有关参数
的更多信息 ,最好通过研究源代码来阅读。
同样,您可以添加其他对象,例如系统设置。
$package->put($setting, [ xPDOTransport::UNIQUE_KEY => 'key', xPDOTransport::PRESERVE_KEYS => true, xPDOTransport::UPDATE_OBJECT => true, 'class' => 'modSystemSetting', 'resolve' => null, 'validate' => null, 'package' => 'modx', ]);
创建一个类别
当添加一个类别时,当我弄清楚所有内容时,我感到最大的烦恼。 在xPDO模型中放入类别的元素必须都属于该类别,即 嵌套在其中,只有类别本身才必须嵌套在包中。 同时,您需要考虑类之间的关系,我已经在上面进行了描述。 了解,认识和正确应用它花了很长时间。
$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 ]);
它看起来很可怕,但是却看不到。 一个重要的参数是
xPDOTransport::RELATED_OBJECTS => true
,它指示类别具有嵌套的元素,这些元素也需要打包然后安装。
由于大多数模块包含各种元素(块,摘要,插件),因此带有元素的类别是运输包中最重要的部分。 因此,在这里指定了验证器和解析器,它们在软件包安装期间执行。
验证程序在安装之前执行,而解析程序在执行之后。
我几乎忘记了,在打包类别之前,我们需要向其中添加元素。 像这样:
$plugins = include $sources['data'] . 'transport.plugins.php'; if (is_array($plugins)) { $category->addMany($plugins, 'Plugins'); }
将其他数据添加到包中。
在软件包中,您需要添加另一个带有许可证的文件,一个带有更改日志的文件以及一个带有组件说明的文件。 如有必要,可以通过
setup-options
属性添加另一个特殊脚本,该脚本将在安装软件包之前显示该窗口。 这是“安装选项”按钮代替“安装”的时候。 从MODX 2.4版本开始,可以使用
requires
属性指定包之间的依赖关系,并且还可以在其中指定PHP和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']);
我们打包
if ($package->pack()) { $xpdo->log(xPDO::LOG_LEVEL_INFO, "Package built"); }
就这样,从
_packages
拾取完成的包,或者从配置程序集的位置。
结果如何?
结果超出了我的预期,因为这种方法虽然施加了一些限制,并且在某些地方增加了一些不便,但是在应用程序方面还是有优势的。
要构建程序包,只需执行2条命令:
git clone --recursive git@github.com:Alroniks/modx-slackify.git cd modx-slackify/_build && php build.transport.php
首先是存储库及其子模块的克隆。 一个重要的参数是
--recursive
,由于它,git不仅会下载和安装组件代码本身,还将下载和安装所有描述为子模块的依赖项。
第二个是直接构建软件包。 之后,您可以从
_packages
文件夹中提取完成的
package-1.0.0-pl.transport.zip
并将其加载到例如存储库中。
前景广阔。 例如,您可以在GitHub中配置一个挂钩,提交到分支后,该挂钩将在服务器上运行脚本,该脚本将收集软件包并将其放入您拥有的所有站点中。 或将新版本上传到某个存储库,到那时,您将像我一开始所说的那样自己煮咖啡。 或者,您可以提出并为模块编写测试,然后通过Jenkins或Travis进行测试运行并进行构建。 是的,您可以提出许多方案。 使用这种方法,现在可以轻松得多。
提出问题,尝试回答。
PS不要路过,请
在GitHub上放一个Slackify星 。