Мне нужно написать скрипт, который будет искать через CSV-файл и выполнять на нем определенные функции поиска;
Теперь у меня нет никаких проблем при кодировании этого процесса, но поскольку я теперь перехожу к Object Orientated Programming, я бы хотел использовать классы и экземпляры объектов.
Однако думать о ООП мне все же не приходит, но я не совсем уверен, куда идти. Я не ищу конкретный код, а скорее предложения о том, как я могу создать сценарий.
Мое нынешнее мышление таково;
Как это работает в index.php:
Проблема, которую я вижу в этом подходе, такова;
Должен ли я вместо этого пойти так?
Моя основная проблема заключается в том, что мне кажется, что мне может понадобиться несколько объектов поиска и повторить это в моем классе цикла.
Любая помощь приветствуется. Я очень новичок в ООП, и, хотя я понимаю отдельные части, я еще не могу видеть большую картину. Я могу быть слишком сложным, что я пытаюсь сделать, или может быть гораздо более простой способ, который я пока не вижу.
Я собираюсь проиллюстрировать разумный подход к разработке кода ООП, который удовлетворит ваши заявленные потребности. Хотя я твердо верю, что представленные ниже идеи являются прочными, помните, что:
Решение с высокой степенью проектирования начнется с попытки определить интерфейс для данных. То есть подумайте о том, что будет представлять данные, которые позволяют выполнять все ваши операции с запросами. Вот один из них:
Это определение достаточно для реализации всех трех типов запросов, о которых вы говорите, путем циклизации по строкам и выполнения некоторого типа теста на значениях конкретного столбца.
Следующим шагом будет определение интерфейса, который описывает выше в коде. Не особенно приятным, но все же адекватным подходом было бы:
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 уже предлагает способ чтения CSV-файла способом OO с помощью SplFileObject :
$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-файлы, но поскольку это файл ввода-вывода, это не самый быстрый. Однако SplFileObject реализует интерфейс Iterator, поэтому вы можете перенести этот экземпляр $ file в другие итераторы для изменения итерации. Например, чтобы ограничить файловый ввод / вывод, вы можете перенести его в CachingIterator:
$cachedFile = new CachingIterator($file, CachingIterator::FULL_CACHE);
Чтобы заполнить кеш, вы перебираете $ cachedFile. Это заполнит кеш
foreach ($cachedFile as $row) {
Затем, чтобы перебрать кеш, вы делаете
foreach ($cachedFile->getCache() as $row) {
Очевидно, что компромисс – это увеличение памяти.
Теперь, чтобы выполнить ваши запросы, вы можете перенести этот 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); } }
A FilterIterator будет пропускать все записи из внутреннего Iterator, который не удовлетворяет тесту в методе accept 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);
Вы можете складывать несколько фильтров для дальнейшего ограничения результатов. Если вам не нравится писать класс для каждой фильтрации, посмотрите на CallbackFilterIterator, который позволяет передавать логику accept во время выполнения:
$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 } } );
Фактически вы выбрали плохой пример для обучения ООП. Потому что функциональность, которую вы ищете для «импорта» и «поиска» файла, может быть лучше всего реализована процедурно, а не объектно-ориентированным способом. Помните, что не все в мире является «объектом». Помимо объектов у нас есть «процедуры», «действия» и т. Д. Вы все равно можете реализовать эту функциональность с классами, которые рекомендуются на самом деле. Но просто добавление функциональности в класс не превращает его в настоящий ООП автоматически.
То, что я пытаюсь сделать, состоит в том, что одна из причин, по которой вы, возможно, пытаетесь понять эту функциональность с точки зрения ООП, заключается в том, что она не имеет объектно-ориентированного характера. Если вы знакомы с классом Java Math (у PHP может быть аналогичная вещь), у него есть b набор методов / функций, таких как abs, log и т. Д. Это, хотя и является классом, на самом деле не является классом в объектно-ориентированном смысл. Это всего лишь куча функций.
Какой действительно класс в объектно-ориентированном смысле? Ну, это огромная тема, но по крайней мере один общий критерий состоит в том, что она имеет как состояние (атрибуты / поля), так и поведение (методы), таким образом, что существует внутренняя связь между поведением и состоянием. Если это так, например, вызов метода обращается к состоянию (потому что они связаны так). Вот простой класс ООП:
Class person { // State name; age; income; // Behavior getName(); setName() . . . getMonthlyIncome() { return income / 12; } }
И вот класс, который, несмотря на его внешний вид (как класс), в действительности является процедурным:
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; }