vinogradsoft / scanner
Vinograd是一个强大的库,用于处理层次化数据。它提供了一个直观的API,用于以广度优先和深度优先的顺序遍历树。开发者还可以实现自己的访问者模式来处理特定的节点或叶子。
Requires
- php: >=8.0
Requires (Dev)
- overtrue/phplint: ^2.0
- phpunit/phpunit: ^9.6
This package is auto-updated.
Last update: 2024-09-10 03:21:33 UTC
README
什么是Scanner?
👉 Scanner是构建用于在层次结构中搜索和处理数据系统的骨架。它提供了两种数据分析方法:第一种是广度优先分析,一次查看树的各个层次;第二种是深度优先分析,按顺序处理树的各个层次,从根开始。此工具的主要目的是使开发者能够专注于应用程序的逻辑,而不是如何遍历树。Scanner对于使用层次化数据并寻求自动化处理此类数据的程序员来说非常有用。
功能
- 💪 支持不同用例的不同驱动程序(例如,文件驱动程序用于目录遍历或ArrayDriver用于处理数组)。
- 👍 能够在树结构中搜索和处理某些元素。
- 🚧 在爬取时过滤元素。
- 🤚 根据条件在任何位置停止树遍历。
- ⚗️ 通过其自身的配置和参数,使用灵活性。
安装
使用composer安装
php composer require vinogradsoft/scanner "^2.0"
一般信息
库中的主要对象是 Vinograd\Scanner\Scanner。此对象累积所有遍历设置并开始遍历树。
绕过算法放置在单独的类中,称为策略,可以根据任务进行更改。库中实现了两种此类策略:广度优先(Vinograd\Scanner\BreadthStrategy)和深度优先(Vinograd\Scanner\SingleStrategy)。
使用 Vinograd\Scanner\Visitor 接口来处理和收集数据。库中没有为其提供实现;其实现由使用此库的开发者执行。该接口中有4个有用的方法
scanStarted- 在扫描开始时调用;scanCompleted- 当策略完成其工作时调用;visitLeaf- 当策略访问树的叶子时调用;visitNode- 当策略访问树节点时调用;
深度优先遍历算法是通过 Vinograd\Scanner\SingleStrategy 策略实现的。其算法相当简单。它接收传递给它的节点的子节点并退出。想法是在Visitor中将Scanner放置并运行扫描,每次在visitNode方法中对每个子节点进行扫描。结果是受控的深度优先递归遍历。
在Vinograd\Scanner\BreadthStrategy策略中不需要这样做;它会在到达树的最后一个元素时结束。
在其他方面,\Vinograd\Scanner\Verifier 对象在爬取过程中被使用。它的目的是确保子元素满足要求,并且应该在元素上调用 Visitor 对象的 visitLeaf 和 visitNode 方法。换句话说,你可以提供一些规则并过滤出树中的元素。对于 Vinograd\Scanner\BreadthStrategy 策略,这并不意味着如果一个节点被过滤,策略将不会绕过其子节点。这意味着在失败的元素上不会调用 visitLeaf 和 visitNode 方法。这样,你可以配置绕过以仅对目标节点执行处理。对于 Vinograd\Scanner\SingleStrategy 策略,这意味着子节点将不会被扫描,因为不会调用 visitNode 方法,并且你将无法对其进行扫描。你可以通过在 Verifier 对象中放宽规则并创建一个 Visitor 代理来解决这个问题,该代理对所有节点进行扫描,但不调用代理对象的 visitNode 方法。
驱动程序允许您选择需要遍历的树对象类型。该库实现了一个用于遍历数组的驱动程序。该类名为 Vinograd\Scanner\ArrayDriver。另一个外部实现 files-driver 允许您在文件系统中遍历目录。这两个驱动程序都实现了 Vinograd\Scanner\Driver 接口。
示例
让我们看看一个概念性的用例。
📢 为了使示例更清晰,它不包括代码中通常执行的检查。相反,示例侧重于展示系统的功能。示例包括查看所需了解系统工作原理的类。您可以通过克隆此 仓库 来运行示例。
问题的表述
您需要创建一个控制台命令,根据配置以特定顺序触发一系列命令执行。在配置中,您需要从 tasks 节点开始绕过节点,并忽略 other 节点。
配置如下
<?php return [ 'tasks' => [ 'make breakfast' => [ 'sandwich' => [ 'cut a piece of bread' => [ 'take the knife in your right hand', 'cut a piece of bread on a wooden board' ], 'spread butter on bread', 'put a piece of cheese on top', ], 'coffee' => [ 'take a cup', 'pour coffee into a cup', ], ] ], 'other' => [ 'setting1' => 'value1', 'setting2' => 'value2', 'setting3' => 'value3' ] ];
节点是类型为 array 的数组值。叶是类型为 string 的数组值。树的每个叶都是我们将要执行的命令。
实现
❗️ 为了简化演示代码,我们将运行
echo,也就是说,我们将显示节点名称和叶子的值。
让我们编写一个处理程序,并将其命名为 Handler。该类实现了 Vinograd\Scanner\Visitor 接口;它在其中输出节点名称和树叶子的值到控制台。
代码
<?php declare(strict_types=1); namespace Example; use Vinograd\Scanner\AbstractTraversalStrategy; use Vinograd\Scanner\Visitor; class Handler implements Visitor { public function scanStarted(AbstractTraversalStrategy $scanStrategy, mixed $detect): void { } public function scanCompleted(AbstractTraversalStrategy $scanStrategy, mixed $detect): void { } public function visitLeaf(AbstractTraversalStrategy $scanStrategy, mixed $parentNode, mixed $currentElement, mixed $data = null): void { $leaf = array_shift($currentElement); echo 'Execute: ', $leaf, PHP_EOL; } public function visitNode(AbstractTraversalStrategy $scanStrategy, mixed $parentNode, mixed $currentNode, mixed $data = null): void { $nodeName = array_key_first($currentNode); echo 'Start: ' . $nodeName, PHP_EOL; } }
为了使 Handler 类的代码仅关注显示名称的逻辑,我们将编写 ProxyHandler 来控制树的遍历,并且只为必要的节点调用 Handler 方法。
代码
<?php declare(strict_types=1); namespace Example; use Vinograd\Scanner\AbstractTraversalStrategy; use Vinograd\Scanner\Scanner; use Vinograd\Scanner\Visitor; class ProxyHandler implements Visitor { private Visitor $handler; private Scanner $scanner; public function __construct(Visitor $handler, Scanner $scanner) { $this->handler = $handler; $this->scanner = $scanner; } public function scanStarted(AbstractTraversalStrategy $scanStrategy, mixed $detect): void { $this->handler->scanStarted($scanStrategy, $detect); } public function scanCompleted(AbstractTraversalStrategy $scanStrategy, mixed $detect): void { $this->handler->scanCompleted($scanStrategy, $detect); } public function visitLeaf(AbstractTraversalStrategy $scanStrategy, mixed $parentNode, mixed $currentElement, mixed $data = null): void { $this->handler->visitLeaf($scanStrategy, $parentNode, $currentElement); } public function visitNode(AbstractTraversalStrategy $scanStrategy, mixed $parentNode, mixed $currentNode, mixed $data = null): void { $name = array_key_first($currentNode); if ($name !== 'tasks') { $this->handler->visitNode($scanStrategy, $parentNode, $currentNode); } $this->scanner->traverse($currentNode[$name]); } }
在此代码中,我们最感兴趣的是 visitNode 方法,因为它包含深度优先树遍历的逻辑。由于我们不想打印 tasks 节点的名称,如果节点名称是 tasks,则不会调用我们的处理程序方法。同时,我们在 Scanner 上调用 traverse 方法以进一步进入树。
让我们编写一个过滤器,它将允许我们不在配置中扫描 other 节点。
代码
<?php declare(strict_types=1); namespace Example; class FilterForNodeOther implements \Vinograd\Scanner\Filter { public function filter(mixed $element): bool { $name = array_key_first($element); return $name !== 'other'; } }
在此代码中,我们检查节点名称,如果不是 'other',则返回 true,否则返回 false。我们检查数组中的确切键。$element 参数是一个数组,包含整个节点并始终有一个键,即节点的名称,因此使用 array_key_first 函数是相当合适的。
让我们创建主类 Application,在其中我们将配置 Scanner 并开始遍历配置。
<?php declare(strict_types=1); namespace Example; use Vinograd\Scanner\ArrayDriver; use Vinograd\Scanner\Scanner; use Vinograd\Scanner\SingleStrategy; class Application { public function run(array $config): void { $scanner = new Scanner(); $scanner->setDriver(new ArrayDriver()); $scanner->setVisitor(new ProxyHandler(new Handler(), $scanner)); $scanner->setStrategy(new SingleStrategy()); $scanner->addNodeFilter(new FilterForNodeOther()); $scanner->traverse($config); } }
我们使用任务所需的对象完成 Scanner 对象,并开始遍历树节点。
在这一行,我们创建了一个新的 Scanner 对象实例
$scanner = new Scanner();
接下来,安装用于处理数组的驱动程序
$scanner->setDriver(new ArrayDriver());
下一步是安装 ProxyHandler,在构造函数中传入我们的处理程序和 Scanner。我们通过设置深度优先遍历策略来完成这个阶段。
代码
$scanner->setVisitor(new ProxyHandler(new Handler(), $scanner)); $scanner->setStrategy(new SingleStrategy());
在这些行中
$scanner->addNodeFilter(new FilterForNodeOther()); $scanner->traverse($config);
我们添加了一个过滤器,允许我们不会扫描 other 节点并开始爬取节点。
它在哪里使用?
Scanner 用于
- File-search - 一个库,允许您搜索必要的文件并对它们进行操作;
- Reflection - 一个库,创建指定目录的对象模型,并允许您对其进行操作:复制、修改文件、删除、移动和创建新文件。
⭐️ 如果您觉得此项目有用,请给它加个星!
测试
php composer 测试
贡献
请参阅CONTRIBUTING 获取详细信息。
许可证
MIT 许可证 (MIT)。有关更多信息,请参阅许可证 文件。