Intereting Posts
Модули тестирования Symfony2 с ограничениями mysqli bind_param не устанавливает ошибку $ stmt-> или $ stmt-> errno Унифицированные логины Joomla для форума и пользовательский php-сайт Как сделать выравнивание на консоли в php PHP не может читать файлы, содержащие PHP-код, в виде текстовых файлов Может ли php обнаруживать 4-байтовые кодированные символы utf8? Изменение ассоциативного массива в индексированный массив / получение Zend_Table_Row_Abstract как неассоциативного В cakephp, как я могу найти с условиями в соответствующей области? отправка html-формы через jquery ajax () и serialize () Добавление разрешения на запись в PHP на IIS 7 Передавать значение в массиве опций в встроенную форму Zend Framework 2 – Удаленный элемент формы приводит к отказу проверки Как использовать транзакцию в php / mysql Есть ли способ в MySQL перевернуть логическое поле с одним запросом? Поиск дубликатов записей с помощью PDO

PHP: поиск в файле CSV OOP

Мне нужно написать скрипт, который будет искать через CSV-файл и выполнять на нем определенные функции поиска;

  1. найти повторяющиеся записи в столбце
  2. найти совпадения со списком запрещенных записей в другом столбце
  3. найти записи через регулярное выражение в столбце, указанном

Теперь у меня нет никаких проблем при кодировании этого процесса, но поскольку я теперь перехожу к Object Orientated Programming, я бы хотел использовать классы и экземпляры объектов.

Однако думать о ООП мне все же не приходит, но я не совсем уверен, куда идти. Я не ищу конкретный код, а скорее предложения о том, как я могу создать сценарий.

Мое нынешнее мышление таково;

  1. Создайте класс файла. Это будет обрабатывать импорт / экспорт данных
  2. Создайте класс поиска. Детский класс файла. Это будет содержать различные методы поиска

Как это работает в index.php:

  1. получить массив из csv в файловом объекте в index.php
  2. создать цикл для итерации значений массива
  3. вызывать методы в цикле из объекта поиска и выводить их из системы

Проблема, которую я вижу в этом подходе, такова;

  • Я хочу указать на разные элементы в моем массиве, чтобы посмотреть на определенные «столбцы». Я мог бы просто поместить мой цикл в функцию и передать это как параметр, но этот вид побеждает точку ООП, я чувствую
  • Мои методы поиска будут работать по-разному. Поиск дубликатов записей довольно прямолинейно с вложенными циклами, но мне не нужен вложенный цикл для простого поиска слов или регулярных выражений.

Должен ли я вместо этого пойти так?

  1. Создайте класс файла. Это будет обрабатывать импорт / экспорт данных
  2. Создайте класс цикла. Ребенок класса файла. Это будет содержать методы, которые обрабатывают итерацию через массив
  3. Создайте класс поиска. Детский класс цикла. Это будет содержать различные методы поиска

Моя основная проблема заключается в том, что мне кажется, что мне может понадобиться несколько объектов поиска и повторить это в моем классе цикла.

Любая помощь приветствуется. Я очень новичок в ООП, и, хотя я понимаю отдельные части, я еще не могу видеть большую картину. Я могу быть слишком сложным, что я пытаюсь сделать, или может быть гораздо более простой способ, который я пока не вижу.

Я собираюсь проиллюстрировать разумный подход к разработке кода ООП, который удовлетворит ваши заявленные потребности. Хотя я твердо верю, что представленные ниже идеи являются прочными, помните, что:

  • дизайн может быть улучшен – цель здесь – показать подход, а не конечный продукт
  • реализация подразумевается только в качестве примера – если она (едва) работает, она достаточно хороша

Как это сделать

Решение с высокой степенью проектирования начнется с попытки определить интерфейс для данных. То есть подумайте о том, что будет представлять данные, которые позволяют выполнять все ваши операции с запросами. Вот один из них:

  • Набор данных представляет собой конечный набор строк . К каждой строке можно получить доступ с учетом индекса, основанного на нулевом значении.
  • Строка представляет собой конечный набор значений . Каждое значение является строкой и может быть доступно с учетом индекса с нулевым индексом (например, индекса столбца). Все строки в наборе данных имеют точно такое же количество значений.

Это определение достаточно для реализации всех трех типов запросов, о которых вы говорите, путем циклизации по строкам и выполнения некоторого типа теста на значениях конкретного столбца.

Следующим шагом будет определение интерфейса, который описывает выше в коде. Не особенно приятным, но все же адекватным подходом было бы:

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; }