概念与培训 / ente
ILIAS的实体组件框架。
Requires (Dev)
- phpunit/phpunit: ^5.6
README
ente.php
ILIAS的实体组件框架。
联系: Richard Klees
模型
实体组件模型或系统是一种架构模式,主要用于游戏开发,但该模式解决的问题也适用于其他情况,例如在ILIAS中开发相互关联的功能。
该模式解决的问题可以描述如下:有一个包含一些(或许多)实体的系统。实体可以理解为具有身份并在时间和空间上表现出连续性的对象。在系统中,有许多子系统作用于这些实体。
示例:在即时战略游戏中,实体可以是游戏军队的单位以及景观中的对象,也可能是其他事物。作用于这些对象的系统可能包括物理系统、路径查找系统、声音系统等。
示例:在ILIAS中,实体基本上可以与所有
ilObject的后代相对应,尽管可能还有更多可以称为实体的东西,例如测试中的问题。作用于这些实体的系统可能包括RBAC系统、学习进度系统、树/仓库等。
使用继承实现此类系统可能导致一些问题,这些问题在ILIAS中也可能观察到。
- 用于构建实体的类需要大量代码才能为所有感兴趣的子系统提供服务。
- 在实体类的不同类别之间共享功能很困难。
- 定义实体类很麻烦,这些类中的对象可能或可能不参与该系统,这取决于具体情况。
- 对于非内存管理语言(例如C),该实现策略可能对CPU缓存中的缓存也很不利。
实体组件模型通过采用“优先考虑组合而非继承”的原则来解决这些问题。实体本身仅被理解为提供所需的身份和连续性,即每个实体基本上是一个id。为满足不同子系统的需求,可以将组件附加到实体上,使其参与该系统。
示例:在即时战略游戏中,一个实体可以扩展一个“物理”组件,该组件知道实体的位置和其他物理指标,使其出现在游戏场景中。
示例:ILIAS中学习进度的实现实际上遵循该模型。不需要为所有具有学习进度的
ilObjects实现某个接口,而是需要构建一个单独的对象来处理原始ilObject的学习进度。
这种解决描述中问题的模式与简单的基于继承的方法相比具有一些优点
- 与包含所有子系统代码的一个大类相比,实体被分割成不同的类,每个类只服务于所有系统的一个子集。
- 在实体间共享功能有一种明显的方法:只需使用相同的组件或组件实现。
- 如果一个实体不应该由子系统处理,只需不要将该组件添加到实体中。这可以轻松地在运行时更改,而不需要更改代码。
- C程序和其他类似程序可以更容易地处理内存局部性。在PHP场景中,关于数据库的类似好处也可能适用。
使用实体组件模型肯定也有缺点。为了论述,它们没有被写在这里。找到它们留给读者作为练习。
实现
这个实现旨在在ILIAS的上下文中工作,其中插件应该能够为ilObjects定义组件。因此,这个库知道四种类型的对象,相应的接口可以在库的基本目录中找到。
Entity提供识别对象的基本手段。也就是说,实体需要能够提供一些可比较的ID,具有相同ID的对象确实是同一个对象。这是从模式中知道的东西。Component是模式中提供特定子系统信息和实现的东西。由于库不了解这些子系统,基本接口只提供了一个方法来了解组件所属的实体。这将是这个库的主要扩展点之一。- 这个库应该能够在
ilObjects、插件和ILIAS树上下文中工作。这个框架将用于的主要插件是用来增强ILIAS课程的不同功能。然后,插件需要能够从不同方向扩展课程,因此需要一个关于实体组件源的概念。因此,Provider可以提供固定实体不同类型的组件。 - 所有这些东西都需要绑定在一起,这就是
Repository的作用。它是查询Providers和Components的来源。
在src/Simple中可以找到一个简单的实现(没有ILIAS)。它定义了两个组件AttachInt和AttachString,它们都有一种*Memory实现。
在src/ILIAS中可以找到一个基于ILIAS的实现。它包含两个更值得注意的对象,它们在上述简单模型和实际的ILIAS世界之间起到连接作用。
UnboundProvider考虑到ILIAS仓库对象实际上可以在树中有不同的位置。提供组件的对象可以提供树的不同分支。为了仍然允许一些通用的存储和加载提供者的机制,仓库对象需要一种方式来谈论它们提供组件的可能性,而不知道它们绑定到哪个实体。ProviderDB是存储和加载提供者的通用机制。提供组件的对象必须通过该对象创建UnboundProviders。然后,ILIAS模型的实现使用树将UnboundProviders转换为树中对象的实际提供者。
两个示例展示了这两个设施的使用。
示例
example文件夹包含两个插件,一个提供组件,一个使用处理提供的组件。请注意,这两个插件不知道彼此。它们的共同点是这个库和它们都使用的AttachString组件。这两个插件都试图展示这个框架,但不是通用插件开发的良好展示!
AttachString组件是一个非常简单的组件,允许将字符串附加到实体。我们很可能不会在现实世界的问题中使用这样的简单组件。
要检查插件的功能,请将它们复制到ilias安装中RepositoryPlugins目录下,使用composer install安装并激活它们在ILIAS中。您应该会提供两种新的repo对象类型:组件处理示例和组件提供示例。在课程中为每个插件创建一个课程和一个对象。
首先打开Provider对象。您应该看到一个简单的表单,您可以在其中添加多个字符串,这些字符串将通过AttachString组件提供。现在就做吧(别忘了保存表单)。然后打开Handler对象。您应该看到您添加到提供者的所有字符串的简单列表。您还可以将更多提供者添加到课程中。处理器将收集来自所有提供者的字符串。当然,您也可以添加更多处理器,但它们与您的第一个处理器不会有区别。
现在来看看提供者对象是如何实现的。插件需要实现的是UnboundProvider的实例。这可以在example/ComponentProviderExample/classes/UnboundProvider.php中找到。这个类需要告诉它将通过componentTypes提供哪些组件,并需要提供一种方式,通过buildComponentsOf创建这些组件的实例。
UnboundProvider有一个owner-ilObject,用于从ILIAS数据库中获取提供的字符串。组件接口AttachString用于在componentTypes中的声明,但buildComponentOf使用内存中的实现AttachStringMemory来创建实际的组件。
提供者对象需要定义何时创建UnboundProvider,对象开始为其他对象提供组件。这是在example/ComponentProviderExample/classes/class.ilObjComponentProviderExample.php中创建对象时完成的。特例ilProviderObjectHelper有助于轻松实现这一点。对象需要通过getDIC提供DIC,然后可以在需要时调用createUnboundProvider。为了做到这一点,它需要告诉它希望为路径上的哪些对象提供组件(示例中的crs),UnboundProvider的名称以及相应的实现可以在哪里找到。对象还需要注意在对象删除后销毁它创建的UnboundProvider,这通过在对象删除时调用deleteUnboundProviders来完成。
处理器对象甚至更简单。在example/ComponentHandlerExample/classes/class.ilObjComponentHandlerExample.php中可以看到处理器相应的实现。它使用特例ilHandlerObjectHelper,并且也需要通过getDIC提供DIC。它还需要提供它打算为哪些对象处理组件的ref_id。在示例中我们只使用父对象。然后它可以使用getComponentsOfType获取为其对象提供的所有组件并对它们进行进一步处理。