gpoehl / phpreport
PHP 库,用于创建任何处理分组数据的报告或其他应用程序。
Requires
- php: >=7.4
Requires (Dev)
- phpunit/phpunit: ^9
This package is auto-updated.
Last update: 2024-09-26 23:00:01 UTC
README
phpReport 是一个现代 PHP 库,用于处理应用程序处理数据分组时几乎所有常见任务。
报告是这类应用程序中最典型的类型,但 phpReport 的使用并不限于它们。借助 phpReport,即使是挑战最大的程序也可以显著简化。
phpReport 可无缝集成到您的环境中。它旨在补充您已使用的工具或框架。
例如,没有读取数据的特性。这使您能够从任何来源访问各种数据,而没有任何限制。
phpReport 可以遍历您的数据,控制分组变化,计算总和和子总,处理计数器等。
最重要的是与您的应用程序的交互方式。每当发生事件(如分组变化)时,都会调用一个或多个用户定义的操作。一个动作是一个对象,它通常调用您的应用程序中的方法,但也可以做任何其他事情。
这个概念确保永远不会出现任何可能阻止您的开发过程的情况。您始终拥有完全控制权,可以充分利用 PHP 本身所能提供的一切。
文档可在 https://phpreport.readthedocs.io/en/latest/ 查看。
安装
官方安装方法是通过 composer 及其 packagist 包 gpoehl/phpreport。
$ composer require gpoehl/phpreport
配置
phpReport 无需配置即可直接运行,并且可以与任何 PHP 框架一起使用。
尽管如此,phpReport 具有高度的配置性。它允许您更改在事件上要调用的默认操作以及构建方法名称的规则,以反映您的组织中使用的命名约定。请参阅 config.php 文件以获取系统范围的设置。配置也可以通过传递参数给类的构造函数来为每个 phpReport 实例进行更改。
实例化
要创建一个新的 phpReport 对象,请调用
$rep = new Report($this);
第一个参数是指具有在事件操作上要调用的方法的对象。可以使用更多参数来配置当前 phpReport 实例。
声明
只需使用四种声明方法,您就可以设置应用程序的基本结构。
group() 方法声明一个新的数据组并实例化一个分组计数器。然后,phpReport 将比较数据行之间的分组值,执行映射到分组标题和脚部事件的操作,并增加分组计数器。
compute() 方法让 phpReport 知道您想使用聚合函数(如 sum()、min()、max())或您将请求非空和非零计数器。
sheet() 方法让您以类似电子表格的格式计算多个值。表格可以有固定数量的列,这些列由预先声明的列名索引,或者基于数据值的可变数量的列。
使用 join() 方法将多个数据源组合。当它们属于连接的数据时,在 join() 方法之后调用其他声明方法。
所有聚合函数和计数器在所有数据源的每个声明的分组级别上都是可用的。
数据输入
数据输入与您的应用程序完全解耦。使用您自己的数据访问方法、数据模型和组件,以及任何 PHP 框架或 ORM(对象关系映射器)的数据访问功能。
phpReport接受数据行,可以是数组、对象甚至是字符串。这些数据行可以一次性、逐行或任意批次大小传递给phpReport。这提供了最大的灵活性,并在处理大量数据时提供了对内存使用的更多控制。
选择最适合您当前应用程序的访问策略,并且可以在不触及应用程序的情况下按需更改此策略。
在读取和向phpReport馈送数据之间,您可以修改或过滤输入。因此,很容易处理任何数据格式(如csv文件、Excel表和json字符串)。
数据连接
在phpReport中连接数据可以用于不同的目的。
首先,您可以连接任何数据行与任何其他来源。数据源不必相同类型。因此,您可以轻松地将来自数据库的行与来自Excel表的行结合起来。使用与主数据相同的数据输入方法连接数据。
当您的数据行是一个数据模型时,另一种连接数据的方法就派上用场了。这个模型通常具有提供相关数据的方法或属性。通过调用join()方法声明关系,phpReport将遍历这些相关数据。
要遍历多维数组,使用join()方法声明哪个数组元素持有下一个维度。
对于应用程序来说,连接数据在很大程度上是透明的。分组和计算值的行为就像数据已被作为平面记录提供一样。
数据输出
除了原型设计外,phpReport不会为您生成任何输出。动作是定义必须发生什么的地方。因此,您可以写入数据库表、文件(例如Excel文件)、HTML字符串、生成PDF文档或发送电子邮件。当然,您可以将所有这些一起完成。
要创建输出字符串,可以使用输出类。调用操作返回的值将始终写入一个输出对象之一。默认对象只是将返回的值追加到一个变量中。更舒适的方法使用数组,并允许在每个组级别直接写入九个波段中的任何一个。波段通常还包括标题、数据以及底部数据,以及顶部或组末尾的汇总数据标题、数据和底部数据。get()或pop()等方法使得输出处理非常灵活。
使用和原型设计
原型设计是在开发过程中模拟、替换或扩展用户操作的方法。
每个原型操作都会生成一个HTML表格,其中包含有关当前数据行、数据组值和计算值的一些有趣信息。
以下是一个中等复杂应用程序的示例,展示了基本功能的使用。
use gpoehl\phpreport\Report; class FirstExample{ public Report $rep; /** @var Customers[] Array of customer objects. /* public function __construct(array $customers){ $this->rep = (new Report($this)) ->group ('region') ->group('customer', 'customerID') ->join (['getOrdersByDate'] ,null, null, null, date("Y")) ->group ('month', fn($order)=> substr($order->orderDate, 5, 2)) ->group('orderID') ->compute ('discount', false) ->join (['getOrderDetails']) ->compute ('amount') ->setRuntimeOption(RuntimeOption::Prototype); $this->rep->run($data); echo $this->rep->out->get(); } }
上面的示例类首先实例化一个新的phpReport对象,并在一个变量中保存引用。传递的参数是实现动作方法的对象。
接下来声明了两个名为region和customer的组。相关数据将从客户对象的'region'和'customerID'属性中提取。
要连接客户与订单,join方法让我们声明从哪里获取它们。在这个例子中,我们将调用getOrdersByDate方法,并传递一个额外的参数来选择当前年份的订单。
注意,'getOrdersByDate'被包裹在一个数组中,以区分属性和方法名称。
通常,被调用的方法将返回相关行,phpReport将遍历这些行。
group('month')调用显示了如何使用闭包从数据行中获取值。我们还按orderID对订单进行分组。
我们还想根据客户每月的订单计算折扣。假设获取折扣值非常复杂,我们需要或想要自己进行计算。为此,我们可以使用参数为“value source”等于false的compute方法。在这种情况下,只会实例化一个计算器对象,并将其与id 'discount'关联到总收集器。这就是我们需要确保我们的折扣按月、客户、地区累计到总金额的全部内容。
第二个连接指示为每个订单调用order类中的getOrderDetail方法。
使用与id 'amount'关联到总收集器的计算器对象计算'amount'属性值。
setCallOption调用激活了原型函数。
要开始执行,调用run()方法。在这里,我们一次性将所有客户对象传递给报告对象。
最终,我们在echo从动作方法收集的返回值。
值的复杂计算
如上所述,我们想要/需要自己计算月度折扣。在处理完一个月内的所有订单后,调用monthFooter方法。
第一行显示了如何获取每月计算出的'amount'值的总和。第二行执行的计算实际上会更复杂。计算器add方法添加折扣值。累计值在所有分组级别都可用。
通过实现动作方法,相关的原型方法(根据callOption参数)将不再被调用。您可以自己调用原型方法。不需要参数,因为phpReport知道要做什么。
public function monthFooter($month, $order){ $amount = $this->rep->total->amount->sum(); $discount = $amount * (($amount < 1000) ? 0.02 : 0.03); // That's the complicated formula. :)) $this->rep->total->discount->add($discount); $this->rep->prototype(); }
数据输出
上面的程序完全有效。您需要做的只是准备所需的输出。仅实现您真正需要的动作方法。phpReport足够智能,只调用现有方法,除非您真的要求。
现在也是取消注释上面所做的两个原型调用的好时机。
public function customerHeader($customerID, $customer){ return $customer->adress' . '<br>Dear ' . $customer->name; } public function customerFooter($customerID, $customer){ return "You placed {$this->rep->gc->orders->sum()} orders". . " with an total amount of {$this->rep->total->amount->sum()}." ; "Therefore you receive a total discount of {$this->rep->total->discount->sum()}." ; } // Default method called when no data is found for first data dimension (orders) public function noDataDim1($dimID){ return "You haven't placed any order lately. We hope to see you soon again."; } public function totalFooter(){ return "<h1>Summary page</h1> . "<br>Total sales: " . $this->rep->total->sales->sum() . "<br>Total number of customers: " . $this->rep->gc->customer->sum() . "<br>Total number of orders: " . $this->rep->gc->order->sum() . "<br>Total number of rows: " . $this->rep->rc->sum(); }
需求
对于phpReport的唯一要求是PHP 8.0或更高版本。
支持我们
如果这个库对您有价值,请考虑通过捐赠支持phpReport的开发。
单元测试
使用PHPUnit对phpReport进行单元测试。
要执行测试,请在phpReport根目录中从命令行运行vendor\bin\phpunit。