x-systems/phlex-data

Phlex Data - 数据源抽象框架

3.0.x-dev 2024-09-13 11:21 UTC

README

Agile Toolkit 是一个用 PHP 编写的低代码框架。Agile UI 实现了服务器端渲染引擎和超过 50 个 UI 通用组件,用于与您的数据模型交互。

Agile Data 是一个用于定义您的 "业务层" 的框架,该层与您的 "表示层" 和 "持久层" 相分离。与 Agile UI 一起,您可以直接或通过 Agile UI - 通用 API 端点交付用户界面。

  • Agile Data 使用 PHP 定义您的业务对象、它们的属性和操作。
  • Agile Data 与 SQL、NoSQL 或外部 API 源一起工作。
  • Agile Data 通过最少的代码将插件连接到通用 UI 组件(Crud、Card、Table、Form 等)。
  • Agile Data 支持用户操作。UI 层使用 "操作执行器" 读取 ACL 控制的元数据。
  • Agile Data 开发者友好,易于学习
  • Agile Data 性能高,能够通过高级表达式将聚合逻辑抽象化,并将其转移到强大的数据库持久性(如 SQL)。
  • Agile Data 可扩展 - 字段类型、持久性类型、关系和操作类型可以扩展。

Unit Testing CodeCov GitHub release PHPStan enabled

快速链接: 文档. 命名空间. 示例. Phlex UI. 论坛. 聊天. 商业支持. Udemy 课程.

Phlex Data 是否类似于 ORM?

是,也不是。

Agile Data 是数据持久性框架 - 类似于 ORM,它帮助您摆脱原始 SQL。与 ORM 不同,它将对象映射到 "数据集" 而不是 "数据记录"。操作数据集提供了更高层次的抽象。

$vip_clients = (new Client($db))->addCondition('is_vip', true);

// express total for all VIP client invoices. The value of the variable is an object
$total_due = $vip_clients->getTheirEntity('Invoice')->action('fx', ['sum', 'total']);

// Single database query is executed here, but not before!
echo $total_due->getOne();

在其他 ORM 中,类似的实现可能会是 缓慢、笨拙、有限或存在缺陷

Phlex Data 如何与 UI(或 API)集成

Agile Toolkit 是一个低代码框架。一旦您定义了业务对象,就可以将其与 UI 小部件相关联

$app->add(new Crud())->setModel(new Client($db), ['name', 'surname'], ['edit', 'archive']);

或与 API 端点

$api->rest('/clients', new Client($db));

可扩展性和插件

Phlex Data 可扩展,并提供从 审计聚合/报告 到广泛的插件。开发者还可以实现高级数据库概念,例如 "不交错的子类型" - 允许在数据库中有效地持久化面向对象的数据。

无论您的模型如何构建,以及使用什么数据库后端,它都可以轻松与任何第三方插件一起使用,例如 图表

使用Phlex Data的益处

Phlex Data是为中等到大型PHP应用程序和框架设计的,它是数据映射的简洁实现,将

  • 使您的应用程序真正实现数据库无关。SQL?NoSQL?RestAPI?缓存?使用这些中的任何一种加载和存储数据,而无需重构代码。
  • 在服务器上执行更多操作。敏捷数据将查询逻辑转换为服务器特定的语言(例如SQL),然后根据您需要的数据行/列从单个语句中提供给您,无论该语句多么复杂。
  • 数据架构透明。随着数据库结构的改变,您的应用程序代码无需重构。用表达式替换字段,归一化/规范化数据,连接和合并表。只需在单个位置更新应用程序。
  • 扩展。" 审计 "- 以“撤销”支持透明地记录所有编辑、更新和删除操作。 " 报告 "- 添加条件、分组结果、再次分组结果、添加连接和限制以实现出色的报告设计。
  • 即插即用UI。谁今天还想构建管理系统?成十上万的组件: CRUD网格表单,以及像 图表 这样的插件,都可以用3行代码添加到您的PHP应用程序中。
  • Agile Data的RestAPI服务器目前正在开发中。
  • Agile Data及其上述所有扩展都采用MIT许可,可免费使用。

自2016年首次介绍Agile Data以来,我们的早期采用者已经在大型生产PHP项目中使用了它。 现在是您尝试Agile Data的时候了

入门指南

观看 快速入门屏幕录像。还有我们的 官方Udemy课程完整文档 (PDF)。

Phlex Data依赖于Phlex Core,并且可以由Phlex UI极大地补充。

  • Agile Core - 记录各种低级别特征和功能,如容器、钩子或异常(PDF
  • Agile UI - 记录可选UI组件以及如何使用它们构建Web应用程序(PDF

何时使用Agile Data?

我们相信,作为开发者,您应该高效地利用时间。处理大量数据并不高效。Agile Data使UI组件、导出器、导入器或RestAPI服务器以 通用 方式实现。

HTML应用程序和管理系统

大多数ORM(包括您现在可能正在使用的)都存在一个缺陷。作为一个框架,它们没有足够的信息来描述您的数据模型的模型、字段、关系和条件。

integration-orm

结果就是UI层无法简单地发现您的发票如何与客户相关联。这使得您不得不编写大量的胶水代码——执行查询并将数据喂入UI层。

对于大多数ORM来说,您无法设计一个通用的CRUD或表单,使其与任何模型都兼容。结果是在客户端框架面前,服务器端渲染变得越来越不常见。

Agile Data解决了这个平衡问题。对于展示逻辑,您可以使用例如Agile UI这样的工具,它由通用的CRUD、表单实现或其他接受Agile Data模型协议的模块组成。

$presentation->setModel($business_model);

这使得平衡重新调整,并使得实现任何通用的UI组件成为可能,这些组件可以与您的自定义数据模型和自定义持久性(数据库)一起工作。

integration-atk

重要的是要注意,胶水代码也可能与模型交互,为其特定用途准备。

$grid = new \Atk4\Ui\Table();
$data = new Order($db);
$data->addCondition('is_new', true);
$data->addCondition('client_id', $_GET['client_id']);
$grid->setModel($data);

$html = $grid->render();

领域模型报告

面向对象的方法被设计用来隐藏实现复杂性。然而,每次您需要包含聚合或连接的报表数据时,都必须深入了解您的数据库结构,以提取一些奇特的ORM黑客技巧或注入自定义SQL查询。

Agile Data被设计成所有代码都可以仅依赖于模型对象,这包括报告。

以下示例通过仅依赖模型逻辑构建了一个复杂的“工作盈利报告”。

class JobReport extends Job {
  function doInitialize(): void {
    parent::doInitialize();

    // Invoice contains Lines that may relevant to this job
    $invoice = new Invoice($this->persistence);

    // We need to ignore draft invoices
    $invoice->addCondition('status', '!=', 'draft');

    // Each invoice may have multiple lines, which is what we want
    $invoice_lines = $invoice->getTheirEntity('Lines');

    // Build relation between job and invoice line
    $this->hasMany('InvoiceLines', ['model' => $invoice_lines])
      ->addField('invoiced', ['aggregate'=>'sum', 'field'=>'total', 'type'=>'money']);

    // Next we need to see how much is reported through timesheets
    $timesheet = new Timesheet($this->persistence);

    // Timesheet relates to client. Import client.hourly_rate as expression.
    $timesheet->getTheirEntity('client_id')->addField('hourly_rate');

    // Calculate timesheet cost expression
    $timesheet->addExpression('cost', '[hours]*[hourly_rate]');

    // Build relation between Job and Timesheets
    $this->hasMany('Timesheets', ['model' => $timesheet])
      ->addField('reported', ['aggregate'=>'sum', 'field'=>'cost', 'type'=>'money']);

	// Finally lets calculate profit
    $this->addExpression('profit', '[invoiced]-[reported]');

    // Profit margin could be also useful
    $this->addExpression('profit_margin', 'coalesce([profit] / [invoiced], 0)');
  }
}

您的报表模型

  • 将查询逻辑移动到数据库(SQL)
  • 仍然是一个模型,因此与所有UI组件和扩展兼容

为了在HTML表中输出结果

$grid = new \Atk4\Ui\Grid();
$data = new JobReport($db);
$grid->setModel($data);

$html = $grid->render();

或者如果您想使用https://github.com/atk4/charthttps://github.com/atk4/report将其显示为图表

$chart = new \Atk4\Chart\BarChart();
$data = new JobReport($db);

// BarChart wants aggregated data
$data->addExpression('month', 'month([date])');
$aggregate = new \Atk4\Report\GroupModel($data);
$aggregate->groupBy('month', ['profit_margin'=>'sum']);

// Associate presentation with data
$chart->setModel($aggregate, ['month', 'profit_margin']);
$html = $chart->html();

在这两种情况下,您最终只需执行一个SQL查询。

大型应用程序和企业级使用

重构

Agile Data的一个最佳好处是能够以不会影响应用程序整体的方式重构数据库结构。这严重简化了您的Q/A周期并降低了应用程序维护成本。以下是一个示例场景

现有应用程序根据SQL公式计算利润,但由于数据量巨大,计算速度缓慢。解决方案是添加一个“利润”字段,其值将被自动更新。

Agile Data提供了所有工具,让您只需几步就能完成这个操作

  • 通过将“表达式”替换为常规字段来更新您的模型定义。
  • 创建一个“迁移器”脚本,它使用操作来计算表达式。
  • 通过添加模型钩子(afterSave)来更改模型行为,在同一个ACID事务中重新计算“利润”。

这不会破坏您应用程序的其余部分——UI、RestAPI或报告将继续工作,但速度更快。

审计和自定义

我在视频中解释了一些基本的自定义:https://www.youtube.com/watch?v=s0Vh_WWtfEs&index=5&list=PLUUKFD-IBZWaaN_CnQuSP0iwWeHJxPXKS

在文档中还有“高级主题”部分:http://agile-data.readthedocs.io/en/develop/advanced.html

多系统应用程序

大多数SaaS系统都有这样的要求:用户数据可能无法被其他用户访问。然而,数据存储在同一个数据库中,只是通过“system_id”或“user_id”字段区分。

敏捷数据具有使用模式,该模式将自动根据这些条件限制对所有模型的所有访问。这将确保即使开发者在代码中犯错误,当前登录的用户也无法添加任何数据或访问不属于他的任何数据。

从一个数据库迁移到另一个数据库和跨持久化

使用敏捷数据,您可以无缝地将数据从一个持久化迁移到另一个持久化。如果您依赖新持久化不支持的一些功能(例如表达式),您可以使用在您的应用服务器上执行的回调计算来替换它们。

通常情况下,您的应用程序的其他部分不会受到影响,您甚至可以使用多种不同类型的持久化并仍然导航到引用。

支持

由于我们的团队实现了敏捷数据,我们培训了专家,他们可以提供商业咨询、培训和支持。使用我们的联系表单进行咨询:[http://www.agiletoolkit.org/contact](http://www.agiletoolkit.org/contact)

框架集成

敏捷数据(在某些情况下敏捷UI)已被社区与其他流行框架集成

  • Laravel: [https://github.com/atk4/laravel-ad](https://github.com/atk4/laravel-ad)
  • Wordpress: [https://github.com/ibelar/atk-wordpress](https://github.com/ibelar/atk-wordpress)
  • 需要更多集成!

问答

问:我注意到敏捷数据使用子查询而不是 JOIN。我相信 JOIN 更高效。

虽然大多数情况下现代 SQL 子查询的速度与 JOIN 相当,但敏捷数据的 SQL 持久化也实现了 "JOIN" 支持。默认情况下使用子查询更安全,因为它可以隐含相关实体的条件。

然而,您也可以通过 JOIN 导入字段

问:我不喜欢 $book->set('field', 123),我更喜欢属性

敏捷模型不是实体。它们不代表单个记录,而是一组记录。这就是为什么模型有一些重要的属性:$model->getId()$model->persistencemodel->data

更多关于处理单个数据记录的信息。

问:我不喜欢使用类 \Atk4\Data\Model 作为父类

Model 实现了许多基本功能。正如我之前提到的,模型不是实体,因此在迭代结果时不会创建多个模型实例。如果您需要更深入的说明,请阅读我的博客文章:[http://www.agiletoolkit.org/blog/why-should-you-extend-your-entity-class](http://www.agiletoolkit.org/blog/why-should-you-extend-your-entity-class)

问:敏捷数据有一个小的社区

这是您可以改变的。如果您查看敏捷数据的特性并认为它值得更多的关注,请通过传播消息和扩大我们的社区来帮助我们。

敏捷数据是一个相对较新的框架,直到 PHP 社区认识到它需要时间。

问:有断开的链接/文档/页面

我们将所有精力集中在制作高质量的软件并将其免费提供给您。我们将尽力解决任何断开的页面/链接或过时的页面,但我们的资源是有限的。

问:是否有敏捷数据/敏捷UI的培训材料?

我们正在努力。目前,请访问我们的 gitter.im

问:我如何帮助/贡献?

大家好。我们喜欢结识新人,无论他们是否擅长PHP和框架(https://gitter.im/atk4/atk4)。

如果您想帮忙,我们问题系统中有一个特殊的标签Help Wanted

以下部分信息可能已过时,需要清理。

敏捷数据概览

敏捷数据以实用、易于学习的方式实现了多种高级数据库访问模式,例如Active Record、持久化映射、领域模型、事件溯源、动作、钩子、数据集和查询构建,可用于任何具有SQL或NoSQL数据库的框架,并满足所有企业级特定要求。

在查询调用之前,您可以先操作对象。以下代码示例将使用您现有的客户、订单和订单行数据库,并查询VIP客户下所有订单的总金额。查看生成的查询,您将注意到一个实现细节 - 行总额不是物理存储在数据库中,而是表示为价格和数量的乘积

$m = new Client($db);
echo $m->addCondition('vip', true)
  ->getTheirEntity('Order')->getTheirEntity('Line')->action('fx', ['sum', 'total'])->getOne();

生成的查询将始终使用参数变量,如果供应商驱动程序支持(如PDO)

select sum(`price`*`qty`) from `order_line` `O_L` where `order_id` in (
  select `id` from `order` `O` where `client_id` in (
    select `id` from `client` where `vip` = :a
  )
)

// :a is "Y"

敏捷数据不仅适用于SQL数据库。它可用于任何地方,从解码表单提交数据($_POST)甚至与自定义RestAPI交互。对于“审计跟踪”、“访问控制”和“软删除”等功能的零配置实现以及“撤销”、“全局作用域”和“跨持久性”等新功能,使您的敏捷数据代码直接适用于企业级。

上述所有内容都不会增加您的业务逻辑代码的复杂性。您不需要创建XML、YAML文件或注解。也没有强制性的缓存。

我的下一个示例演示了当您存储新的订单数据时,您的代码看起来有多么简单和干净

$m = new Client($db);
$m->loadBy('name', 'Pear Company');
$m->getTheirEntity('Order')
   ->save(['ref'=>'TBL1', 'delivery'=>new DateTime('+1 month')])
   ->getTheirEntity('Lines')->import([
      ['Table', 'category'=>'furniture', 'qty'=>2, 'price'=>10.50],
      ['Chair', 'category'=>'furniture', 'qty'=>10, 'price'=>3.25],
]);

生成的查询(我已经为了可读性移除了反引号和参数变量)使用了简洁的语法,并展示了某些“幕后”逻辑

  • 新订单必须属于公司。公司也不能是软删除的。
  • delivery存储在字段delivery_date中,同时DateTime类型也被映射到SQL友好的日期。
  • order_id会自动与行一起使用。
  • category_id可以直接在INSERT中查找(SQL参考字段的常规功能)。
select id, name from client where name = "Pear Company" and is_deleted = 0;
insert into order (company_id, ref, delivery_date)
  values (293, "TBL1", "2015-18-12");
insert into order_lines (order_id, title, category_id, qty, price) values
  (201, "Table", (select id from category where name = "furniture"), 2, 10.50),
  (201, "Chair", (select id from category where name = "furniture"), 19, 3.25);

如果您喜欢这些示例并想亲自尝试,请继续https://github.com/atk4/data-primer

介绍模型

敏捷数据使用独立于供应商的轻量级Model类来描述您的业务实体

class Client extends \Atk4\Data\Model {
  public $table = 'client';
  function doInitialize(): void {
    parent::doInitialize();

    $this->addFields(['name','address']);

    $this->hasMany('Project', ['model' => [Project::class]]);
  }
}

介绍动作

mapping

与模型(字段、条件、引用)相关的内容都是存在于PHP内存中“领域模型”范畴内的对象。当您调用save()时,框架会生成一个“动作”,该动作将实际更新您的SQL表、调用RestAPI请求或将文件写入磁盘。

每个持久化实现动作的方式不同。SQL可能是功能最全面的一个。

GitHub release

介绍表达式

敏捷工具箱中的智能字段以对象的形式表示。由于继承,字段可以执行多种不同的操作。例如,FieldSqlExpressionField_Expression可以通过自定义SQL或PHP代码定义字段。

GitHub release

介绍引用

外键和关系是RDBMS的精髓。虽然在“持久性”中很有意义,但并非所有数据库都支持关系。

敏捷数据通过引入“引用”来采取不同的方法。它允许您定义与工作于非关系数据库的域模型之间的关系,同时允许您执行各种操作,如导入或聚合字段。(JOIN的使用说明见下文)

GitHub release

模型条件与数据集

条件(或范围)在ORM中是罕见且可选的功能,但在敏捷数据中,它是最重要的功能之一。它允许您创建代表多个数据库记录的对象,而无需实际加载它们。

一旦定义了条件,它就会出现在操作中,并且也会限制您添加不合规的记录。

GitHub release

在域模型内构建报告

在大多数框架中,当涉及到严重的数据聚合时,您必须做出选择——编写低效的域模型代码或编写原始SQL查询。敏捷数据帮助您充分利用数据库的独特功能,同时让您留在域模型内。

我们如何创建一个高效的查询来显示所有项目按客户国家分组后的总预算,同时完全留在域模型中?在敏捷数据中只需一行代码。

GitHub release

您注意到查询自动排除了已取消的项目吗?

模型级别连接

大多数ORM只能定义与单个SQL表一起工作的模型。如果您必须将逻辑实体数据存储到多个表中——那很不幸,您将不得不自己进行一些链接。

敏捷数据允许您在模型中直接定义多个连接。当您调用join()另一个表时,您将能够从连接的表中导入字段。如果您创建一个新记录,数据将自动分布到表中,并且记录将被正确链接。

GitHub release

连接的最好之处在于,您可以将它们添加到现有模型中用于特定查询。一些扩展甚至可以做到这一点。

深度模型遍历

敏捷数据最好的特性之一可能是深度遍历。还记得您的ORM如何尝试实现各种多对多关系吗?在敏捷数据中,这不再是问题。

假设您想查看所有以2个字母命名的国家。有多少来自位于以2个字母命名的国家中的客户的项目的数量?

敏捷数据可以以查询或结果的形式回答。

GitHub release

高级特性和扩展

您迄今为止看到的示例只是您可以使用敏捷数据实现的可能性的一个小片段。您现在有一个新的游乐场,您可以在其强大的概念周围设计您的业务逻辑。

我们在敏捷数据中最重视的美德之一是能够抽象并在我们的坚实基础之上添加高级功能。

可探索性

如果您在任何方法、附加组件或扩展中传递一个$model对象,它们可以发现数据、字段类型以及各种元信息、其他模型的引用、支持的操作等等。

有了这些,创建一个自动包含具有允许值列表的下拉菜单的动态表单UI对象是可能的。

实际上——我们已经开始了敏捷UI项目的工作!

钩子

现在您有领域级别和持久性级别的钩子。在领域级别的钩子(afterLoad、beforeSave)中,您可以在操作前后操作您的字段数据。

另一方面,您可以利用持久性级别的钩子('beforeUpdateQuery','beforeSelectQuery')并与强大的查询构建器交互,以添加一些SQL选项(插入忽略或calc_found_rows),如果您需要的话。

猜猜看——如果您的模型保存到NoSQL数据库,将执行域级别钩子,但不会执行特定于SQL的钩子。

扩展

大多数ORM硬编码了如软删除、审计日志、时间戳等功能。在Agile Data中,基模型的实现非常轻量级,所有必要功能都通过外部对象添加。

我们仍在开发扩展库,但我们计划包括以下内容:

  • 审计日志 - 记录模型中的所有操作(以及以前的字段值),提供可靠的撤销功能。
  • 报告 - 提供UnionModel
  • ACL - 适用于基于登录用户的权限或自定义逻辑来限制对某些记录、字段或模型的访问的灵活系统。
  • 文件存储 - 允许您在模型内部处理文件。实际上,文件存储在S3(或其他)上,但引用和元信息保留在数据库中。
  • 软删除、清除和恢复删除 - 几种策略、自定义字段、权限。

有关扩展的更多详细信息:http://www.agiletoolkit.org/data/extensions

性能

如果您想知道这些高级功能可能如何影响加载数据和保存数据的性能,还有另一个令人愉快的惊喜。加载数据、保存数据、迭代和删除记录不会创建新的内存对象。

foreach($client->getTheirEntity('Project') as $project) {
    echo $project->get('name')."\n"
}

// $project refers to same object at all times, but $project's active data
// is re-populated on each iteration.

不会预先获取任何不必要的数据。只查询请求的列。行以流的形式传输,我们永远不会尝试将大量的ID挤入变量或查询中。

Agile Data即使数据库中有大量记录,也能快速高效地工作。

安全

当ORM承诺“安全性”时,它们并没有真正扩展到您希望执行子查询的情况。然后,您必须处理原始查询组件并将它们自己粘合在一起。

Agile Data提供了对表达式的通用支持,每个表达式都支持转义参数。我的下一个示例将添加通过国家长度过滤国家的范围。自动参数将确保任何恶意内容都将被正确转义。

$country->addCondition($country->expr('length([name]) = []', [$_GET['len']])); // 2

生成的查询是

where length(`name`) = :a  [:a=2]

当您尝试添加一个新国家时,将引发另一个伟大的安全功能

$country->insert('Latvia');

此代码将失败,因为我们之前设定的条件“拉脱维亚”不满足。这使得其他各种用途变得安全。

$client->load(3);
$client->getTheirEntity('Order')->insert($_POST);

无论$_POST中包含什么,新记录都将有client_id = 3

最后,以下也是可能的

$client->addCondition('is_vip');
$client->getTheirEntity('Order')->insert($_POST);

无论POST数据的内容如何,订单只能为VIP客户创建。即使您执行多行操作,如action('update')action('delete'),它也只会应用于匹配所有条件的记录。

这些安全措施是为了保护您免受人为错误的影响。我们认为输入清理仍然非常重要,您应该这样做。

安装到现有项目中

首先通过composer安装Agile Data

composer require atk4/data
composer g require psy/psysh:@stable  # optional, but handy for debugging!

定义您的第一个模型类

namespace my;
class User extends \Atk4\Data\Model
{
    public $table = 'user';
    function doInitialize(): void
    {
        parent::doInitialize();

        $this->addFields(['email','name','password']);
        // use your table fields here
    }
}

然后创建console.php

<?php
include'vendor/autoload.php';
$db = \Atk4\Data\Persistence::connect(PDO_DSN, USER, PASS);
eval(\Psy\sh());

最后,运行console.php

$ php console.php

现在您可以探索了。尝试键入

> $m = new \my\User($db);
> $m->loadBy('email', 'example@example.com')
> $m->get()
> $m->export(['email','name'])
> $m->action('count')
> $m->action('count')->getOne()

Agile Core和DSQL

Agile Data依赖于DSQL - Query Builder进行SQL持久性和多记录操作(通过操作)。通过各种接口和PHP模式通过Agile Core实现。有关更多信息,请使用以下链接

敏捷数据的UI

在拥有数百个不同PHP Crud实现的宇宙中,我们认为您可能喜欢有一个专门为敏捷数据设计的开源的Grid/Crud/Forms/其他UI库。

请考虑我们的其他MIT许可项目——敏捷UI,以构建类似的东西。

image

社区和支持

Gitter Stack Overlfow Community Discord User forum