PHP:通过CSV文件搜索OOP方式

Mar*_*utt 5 php csv oop loops object

我需要编写一个脚本来搜索CSV文件,并在其上执行某些搜索功能;

  1. 在列中查找重复条目
  2. 查找匹配到另一列中禁止条目的列表
  3. 通过在指定列上进行正则表达式匹配来查找条目

现在,我在程序上编码时没有任何问题,但是当我现在转向面向对象编程时,我想使用对象的类和实例.

但是,在OOP中思考并不是我自然而然的,所以我不完全确定要走哪条路.我不是在寻找具体的代码,而是寻找如何设计脚本的建议.

我目前的想法是这样的;

  1. 创建一个文件类.这将处理数据的导入/导出
  2. 创建一个搜索类.子类文件.这将包含各种搜索方法

它如何在index.php中起作用:

  1. 从index.php文件对象的csv中获取一个数组
  2. 创建一个循环来迭代数组的值
  3. 从搜索对象中调用循环中的方法并将其回显

我用这种方法看到的问题是这个;

  • 我想指出我的数组中的不同元素来查看特定的"列".我可以把我的循环放在一个函数中并将其作为一个参数传递给我,但是这种方法会让OOP失败,我觉得
  • 我的搜索方法将以不同的方式工作.使用嵌套循环搜索重复条目非常简单,但我不需要嵌套循环来执行简单的单词或正则表达式搜索.

我应该这样做吗?

  1. 创建一个文件类.这将处理数据的导入/导出
  2. 创建循环类文件类的子类.这将包含处理迭代数组的方法
  3. 创建一个搜索类.一个子类循环.这将包含各种搜索方法

我的主要问题是看起来我可能需要多个搜索对象并在我的循环类中迭代这个.

任何帮助将非常感激.我对OOP很新,虽然我了解各个部分,但我还没有看到更大的图景.我可能会过度复杂化我正在尝试做的事情,或者可能有一种更简单的方式我还看不到.

Gor*_*don 12

PHP已经提供了一种使用SplFileObject以OO方式读取CSV文件的方法:

$file = new SplFileObject("data.csv");

// tell object that it is reading a CSV file
$file->setFlags(SplFileObject::READ_CSV);
$file->setCsvControl(',', '"', '\\');

// iterate over the data
foreach ($file as $row) {
    list ($fruit, $quantity) = $row;
    // Do something with values
}
Run Code Online (Sandbox Code Playgroud)

由于SplFileObject流式传输CSV数据,因此内存消耗非常低,您可以有效地处理大型CSV文件,但由于它是文件i/o,因此不是最快的.但是,SplFileObject实现了Iterator接口,因此您可以将该$ file实例包装到其他迭代器中以修改迭代.例如,要限制文件i/o,可以将其包装到CachingIterator中:

$cachedFile = new CachingIterator($file, CachingIterator::FULL_CACHE);
Run Code Online (Sandbox Code Playgroud)

要填充缓存,请迭代$ cachedFile.这将填充缓存

foreach ($cachedFile as $row) {
Run Code Online (Sandbox Code Playgroud)

然后迭代缓存,你做

foreach ($cachedFile->getCache() as $row) {
Run Code Online (Sandbox Code Playgroud)

权衡显然是增加了内存.

现在,要进行查询,可以将CachingIterator或SplFileObject包装到FilterIterator中,这会在迭代csv数据时限制输出

class BannedEntriesFilter extends FilterIterator
{
    private $bannedEntries = array();

    public function setBannedEntries(array $bannedEntries)
    {
        $this->bannedEntries = $bannedEntries;
    }

    public function accept()
    {
        foreach ($this->current() as $key => $val) {
            return !$this->isBannedEntryInColumn($val, $key);
        }
    }

    public function $isBannedEntryInColumn($entry, $column)
    {
        return isset($this->bannedEntries[$column])
            && in_array($this->bannedEntries[$column], $entry);
    }
}
Run Code Online (Sandbox Code Playgroud)

FilterIterator将省略内部迭代器中的所有条目,这些条目不满足FilterIterator的accept方法中的测试.在上面,我们根据禁止条目数组检查csv文件中的当前行,如果匹配,则数据不包含在迭代中.你这样使用它:

$filteredCachedFile = new BannedEntriesFilter(
    new ArrayIterator($cachedFile->getCache())
)
Run Code Online (Sandbox Code Playgroud)

由于缓存的结果总是一个Array,我们需要先将该Array包装到ArrayIterator中,然后再将它包装到FilterIterator中.请注意,要使用缓存,您还需要至少迭代一次CachingIterator.我们假设您已经完成了上述操作.下一步是配置禁止的条目

$filteredCachedFile->setBannedEntries(
    array(
        // banned entries for column 0
        array('foo', 'bar'),
        // banned entries for column 1
        array( …
    )
);
Run Code Online (Sandbox Code Playgroud)

我想这很简单.您有一个多维数组,其中包含禁止条目的CSV数据中每列的一个条目.然后,您只需遍历实例,它将只为您提供没有禁止条目的行

foreach ($filteredCachedFile as $row) {
    // do something with filtered rows
}
Run Code Online (Sandbox Code Playgroud)

或者,如果您只想将结果放入数组中:

$results = iterator_to_array($filteredCachedFile);
Run Code Online (Sandbox Code Playgroud)

您可以堆叠多个FilterIterators以进一步限制结果.如果您不想为每个过滤编写一个类,请查看CallbackFilterIterator,它允许在运行时传递接受逻辑:

$filteredCachedFile = new CallbackFilterIterator(
    new ArrayIterator($cachedFile->getCache()),
    function(array $row) {
        static $bannedEntries = array(
            array('foo', 'bar'),
            …
        );
        foreach ($row as $key => $val) {
            // logic from above returning boolean if match is found
        }
    }
);
Run Code Online (Sandbox Code Playgroud)


Jon*_*Jon 4

我将说明一种合理的方法来设计 OOP 代码来满足您所声明的需求。虽然我坚信下面提出的想法是合理的,但请注意:

  • 设计可以改进——这里的目的是展示方法,而不是最终产品
  • 该实现仅作为示例- 如果它(勉强)起作用,那就足够了

如何去做这件事

高度工程化的解决方案首先要尝试定义数据接口。也就是说,考虑允许您执行所有查询操作的数据表示形式。这是一个可行的方法:

  • 数据集的有限集合。给定从零开始的索引,可以访问每一行。
  • 行是的有限集合每个值都是一个字符串,并且可以在给定其从零开始的索引(即列索引)的情况下进行访问。数据集中的所有行都具有完全相同数量的值。

此定义足以通过循环行并对特定列的值执行某种类型的测试来实现您提到的所有三种类型的查询。

下一步是定义一个用代码描述上述内容的接口。一个不是特别好但仍然足够的方法是:

interface IDataSet {
    public function getRowCount();
    public function getValueAt($row, $column);
}
Run Code Online (Sandbox Code Playgroud)

现在这部分已经完成,您可以去定义一个实现此接口并可以在您的情况下使用的具体类:

class InMemoryDataSet implements IDataSet {
    private $_data = array();

    public function __construct(array $data) {
        $this->_data = $data;
    }

    public function getRowCount() {
        return count($this->_data);
    }

    public function getValueAt($row, $column) {
        if ($row >= $this->getRowCount()) {
            throw new OutOfRangeException();
        }

        return isset($this->_data[$row][$column])
            ? $this->_data[$row][$column]
            : null;
    }
}
Run Code Online (Sandbox Code Playgroud)

下一步是编写一些代码,将输入数据转换为某种IDataSet

function CSVToDataSet($file) {
    return new InMemoryDataSet(array_map('str_getcsv', file($file)));
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以轻松地从 CSV 文件创建一个IDataSetCSV 文件,并且您知道可以对其执行查询,因为它IDataSet是专门为此目的而设计的。你快到了。

唯一缺少的是创建一个可重用的类,可以在IDataSet. 这是其中之一:

class DataQuery {
    private $_dataSet;

    public function __construct(IDataSet $dataSet) {
        $this->_dataSet = $dataSet;
    }

    public static function getRowsWithDuplicates($columnIndex) {
        $values = array();
        for ($i = 0; $i < $this->_dataSet->getRowCount(); ++$i) {
            $values[$this->_dataSet->->getValueAt($i, $columnIndex)][] = $i;
        }

        return array_filter($values, function($row) { return count($row) > 1; });
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码将返回一个数组,其中键是 CSV 数据中的值,值是带有每个值出现的行的从零开始的索引的数组。由于仅返回重复值,因此每个数组至少有两个元素。

所以此时您已准备好:

$dataSet = CSVToDataSet("data.csv");
$query = new DataQuery($dataSet);
$dupes = $query->getRowsWithDuplicates(0);
Run Code Online (Sandbox Code Playgroud)

这样做你会得到什么

干净、可维护的代码支持将来进行修改,而无需在整个应用程序中进行编辑。

如果您想添加更多查询操作,请将它们添加到DataQuery,您可以立即在所有具体类型的数据集上使用它们。数据集和任何其他外部代码不需要任何修改。

如果您想更改数据的内部表示,请进行InMemoryDataSet相应修改或创建另一个类来实现IDataSet并使用该类,而不是使用CSVToDataSet. 查询类和任何其他外部代码不需要任何修改。

如果您需要更改数据集的定义(也许是为了有效地执行更多类型的查询),那么您必须修改IDataSet,这也可能将所有具体的数据集类带入图片中DataQuery。虽然这不会是世界末日,但这正是您想要避免的事情。

这正是我建议从这里开始的原因:如果你为数据集提出一个好的定义,其他一切都会水到渠成。