我需要编写一个脚本,将通过一个CSV文件进行搜索,并在其上执行某些搜索功能;
- 发现一列重复条目
- 找到匹配,以禁止条目的另一列列表
- 找到关于指定的柱通过正则表达式匹配的条目
现在,我在所有编码这个程序上没有问题,但我现在移动到面向对象编程,我想用对象的类和实例来代替。
然而,在面向对象的思维不自然地来找我,所以我不是很确定哪个方向走。 我不是在寻找特定的代码,但我怎么能设计脚本,而建议。
我目前的想法是这样的;
- 创建一个文件类。 这将处理数据的导入/导出
- 创建一个搜索类别。 子类的文件。 这将包含多种搜索方法
如何将在index.php中发挥作用:
- 您可以通过CSV的阵列中的index.php文件对象
- 创建一个循环通过所述数组的值来迭代
- 调用从搜索对象循环的方法和回声出来
我用这个方法看问题是这样的;
- 我希望在我的数组不同的元素角度去看待特定的“列”。 我可以把我的环路的功能,通过这个作为一个参数,但这种失败的OOP的角度来看,我觉得
- 我的搜索方法会以不同的方式工作。 为了寻找重复的条目是相当有嵌套循环直线前进,但我并不需要一个嵌套循环做一个简单的单词或正则表达式searchs。
我应该去,而不是这样?
- 创建一个文件类。 这将处理数据的导入/导出
- 创建一个环型类文件的一个孩子。 这将包含通过数组迭代方法涉及
- 创建一个搜索类别。 子类循环。 这将包含多种搜索方法
我的这个主要问题是,看来我可能需要多个搜索对象和我的环型的类别内通过这个迭代。
任何帮助将非常感激。 我很新的OOP,而我理解的各个部分,我还没有看到更大的画面。 我可能会过于复杂这是我想要做的,或者有可能是我不能看到又一个更简单的方法。
我要去说明一个合理的方法来设计,供应您所陈述的需求OOP代码。 虽然我坚信想法提出以下是健全的,请注意:
- 设计可以提高 - 这里的目的是要显示的方式,而不是最终产品
- 实施仅作为一个例子 -如果它(勉强)的作品,它的不够好
如何去这样做
高度工程化的解决方案将试图通过定义接口的数据开始。 也就是说,想想会是什么,可以让你执行所有的查询操作的数据的表示。 这里有一个,将工作:
- 数据集是行的有限集合。 每行可以访问赋予其从零开始的索引。
- 行是值的有限集合。 每个值是一个字符串,并且可以访问给定的其基于零的索引(即列索引)。 数据集中的所有行具有相同数量的值。
这个定义就足以实现所有三种类型你提到通过遍历行和特定列的值进行某种类型的测试查询。
下一步的行动是定义描述代码以上的接口。 不是特别好,但还是足够的做法是:
interface IDataSet {
public function getRowCount();
public function getValueAt($row, $column);
}
现在,这部分已经完成了,你可以去定义一个实现了这个接口,并可以在你的情况下使用的具体类:
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;
}
}
下一步是去写一些代码,输入数据转换成某种IDataSet
:
function CSVToDataSet($file) {
return new InMemoryDataSet(array_map('str_getcsv', file($file)));
}
现在你可以平凡创建IDataSet
从一个CSV文件,你知道,你可以对其执行您的查询,因为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; });
}
}
其中键是值在CSV数据和值与其中出现的每个值的行的基于零的索引阵列本代码将返回的数组。 由于只返回重复的值,每一个阵列将具有至少两个元素。
所以在这一点上,你准备好了:
$dataSet = CSVToDataSet("data.csv");
$query = new DataQuery($dataSet);
$dupes = $query->getRowsWithDuplicates(0);
你获得通过这样做
一个支持在未来被修改,而无需修改在你的应用程序清洁,维护的代码。
如果你想添加更多的查询操作,将它们添加到DataQuery
,你可以立即使用它们所有的具体类型的数据集。 该数据集和任何其他外部代码将不再需要任何修改。
如果要更改数据的内部表示,修改InMemoryDataSet
相应或创建一个实现另一个类IDataSet
并使用一个,而不是从CSVToDataSet
。 查询类和任何其他外部代码将不再需要任何修改。
如果您需要更改数据集的定义 (也许是为了允许有效地执行更多类型的查询),那么你必须修改IDataSet
,这也带来了所有的具体数据集类进入画面,可能DataQuery
为好。 虽然这不会是世界末日,这正是你希望避免的那种东西。
而这也正是为什么我建议从这个开始的原因:如果你拿出的数据集定义好了,一切将只是水到渠成。
PHP已经提供了一种方法来读取与SplFileObject面向对象的方式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
}
由于SplFileObject流过CSV数据,内存消耗是相当低的,你可以高效地处理大量的CSV文件,但因为它是文件I / O,它是不是最快的。 然而,SplFileObject实现Iterator接口,这样你就可以换行$文件实例到其他迭代器来修改迭代。 例如,为了限制文件I / O,你可以把它包装成一个CachingIterator:
$cachedFile = new CachingIterator($file, CachingIterator::FULL_CACHE);
为了填充缓存,你遍历$ cachedFile。 这将填充缓存
foreach ($cachedFile as $row) {
遍历缓存,然后,你做
foreach ($cachedFile->getCache() as $row) {
权衡明显增加内存。
现在,做你的查询,你可以遍历CSV数据时换行CachingIterator或SplFileObject成FilterIterator这将限制输出
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);
}
}
一个FilterIterator会忽略从内迭代器不满足测试在FilterIterator的接受方法的所有条目。 上面,我们从反对禁止条目的阵列CSV文件检查当前行,如果匹配,该数据不包括在迭代。 您可以使用这样的:
$filteredCachedFile = new BannedEntriesFilter(
new ArrayIterator($cachedFile->getCache())
)
由于缓存的结果总是一个数组,我们需要的是阵列包装成一个ArrayIterator之前,我们可以把它包装成我们FilterIterator。 注意,要使用高速缓存,您还需要至少一次迭代CachingIterator。 我们只是假设你已经做到了以上。 下一步是配置禁止条目
$filteredCachedFile->setBannedEntries(
array(
// banned entries for column 0
array('foo', 'bar'),
// banned entries for column 1
array( …
)
);
我想这是相当简单的。 你有在CSV数据保持在禁止条目每列一个条目多维数组。 然后,您只需遍历实例,它会给你只行没有禁止的条目
foreach ($filteredCachedFile as $row) {
// do something with filtered rows
}
或者,如果你只是想要得到的结果到一个数组:
$results = iterator_to_array($filteredCachedFile);
您可以堆叠多个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
}
}
);
你实际上已经选择了学习OOP了一个坏榜样。 因为,你正在寻找的“进口”和“搜索”文件的功能,可以在最佳方式过程中实现的,而不是面向对象的方式。 请记住,在世界上并非一切都是“对象”。 除了对象,我们有“程序”,“动作”等,您仍然可以实现与类这个功能,这是推荐的方式,其实。 但是,只是把一个功能的一类不会自动把它变成真正的OOP。
那我想指出的一点是,那你可能会挣扎在OOP方面来理解这一功能的原因之一是,它不是真正的面向对象的性质。 如果你熟悉Java Math类(PHP可能有类似的东西),它的方法/功能,如ABS,日志等本B一堆,虽然是一类,是不是真的在面向对象的一类感。 它只是一堆的功能。
什么是真正的一类在面向对象的意义是什么? 嗯,这是一个很大的话题,但至少有一个一般标准是,它有两种状态(属性/字段)和行为(方法),以这样的方式存在的行为和状态之间的内在结合。 如果是这样,例如,在方法的调用访问状态(因为它们是如此绑在一起)。 下面是一个简单的面向对象的类:
Class person {
// State
name;
age;
income;
// Behavior
getName();
setName()
.
.
.
getMonthlyIncome() {
return income / 12;
}
}
这里是一个类,尽管其在现实中的外观(作为一个阶级)是procedureal:
class Math {
multiply(double x, double y) {
return x * y;
}
divide(double x, double y) {
return x / y;
}
exponentiate(double x, double y) {
return x^y;
}