Skip to content

Instantly share code, notes, and snippets.

@hakre
Created November 27, 2012 09:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hakre/4153380 to your computer and use it in GitHub Desktop.
Save hakre/4153380 to your computer and use it in GitHub Desktop.
Decorate Iterators - CSV Data Example
<?php
/**
* @link http://stackoverflow.com/q/10181054/367456
*/
/**
* Extending from a TraversableDecorator allows to decorate (change) the Iterator
* being Traversed without changing the object.
*/
abstract class TraversableDecorator implements Iterator
{
protected $subject;
public function __construct($subject) {
if (is_array($subject)) {
$subject = new ArrayIterator($subject);
} elseif (($subject instanceof Traversable) and !($subject instanceof iterator)) {
$subject = new IteratorIterator($subject);
}
$this->subject = $subject;
}
public function current() {
return $this->subject->current();
}
public function next() {
$this->subject->next();
}
public function key() {
return $this->subject->key();
}
public function valid() {
return $this->subject->valid();
}
public function rewind() {
$this->subject->rewind();
}
function __call($name, $arguments) {
return call_user_func_array([$this->subject, $name], $arguments);
}
function __get($name) {
return $this->subject->$name;
}
function __set($name, $value) {
$this->subject->$name = $value;
}
function __isset($name) {
return isset($this->subject->$name);
}
}
/**
* Decoration for the TraversableDecorator
*/
abstract class TraversableDecoration extends IteratorIterator
{
public function __construct() {
}
public function setIterator(Traversable $iterator) {
parent::__construct($iterator);
}
}
/**
* Decoration for skipping lines that are like the one given
*/
class SkipLines extends TraversableDecoration
{
private $like;
public function __construct(array $like) {
$this->like = $like;
}
public function next() {
parent::next();
while(parent::valid()) {
if (parent::current() !== $this->like) break;
parent::next();
}
}
}
/**
* Decoration for skipping empty lines
*/
class SkipEmptyLines extends SkipLines
{
public function __construct() {
parent::__construct([null]);
}
}
/**
* Decoration for setting field names
*/
class FieldNames extends TraversableDecoration
{
private $fieldNames;
public function __construct(array $fieldNames = null) {
$this->fieldNames = $fieldNames;
}
public function current() {
if ($this->fieldNames) {
return array_combine($this->fieldNames, parent::current());
}
return parent::current();
}
public function setFieldNames(array $fieldNames) {
$this->fieldNames = $fieldNames;
}
public function getFieldNames() {
return $this->fieldNames;
}
}
/**
* Decoration for having field names in the first row
*/
class FieldNamesInFirstRow extends FieldNames
{
public function rewind() {
parent::rewind();
$fieldNames = parent::current();
parent::setFieldNames($fieldNames);
parent::next();
}
}
/**
* CsvFile
*/
class CsvFile extends TraversableDecorator
{
/**
* @var Traversable
*/
private $file;
public function __construct($file = null) {
$this->file = $this->filterInput($file);
parent::__construct($this->file);
$this->decorateWith(new SkipEmptyLines());
}
public function getFile() {
return $this->file;
}
public function decorateWith(TraversableDecoration $iterator) {
$iterator->setIterator($this->subject);
$this->subject = $iterator;
}
public function removeDecoration() {
parent::__construct($this->file);
}
/**
* @param mixed $file
* @return Traversable
* @throws InvalidArgumentException
*/
private function filterInput($file) {
if (null === $file) {
$file = new SplTempFileObject();
}
if (is_string($file)) {
try {
$file = new SplFileObject($file);
} catch (InvalidArgumentException $e) {
throw $e;
}
}
if ($file instanceof SplFileObject) {
$file->setFlags(SplFileObject::READ_CSV);
}
return $file;
}
}
/**
* small helper function to display a csv file
* @param CsvFile $csv
*/
$echoCsvFile = function(CsvFile $csv) {
$counter = new IteratorIterator($csv);
printf("CSV File with %d Line(s):\n", iterator_count($counter));
foreach ($csv as $i => $line) {
echo "$i:|";
foreach($line as $key => $value) {
($value === NULL) && $value = 'NULL';
echo "$key: $value|";
}
echo "\n";
}
};
// Create a Temporary in-memory CSV file with two-line sample data
// and an empty line at the end
// Note: PHP eats non-standard \n for standard \r\n
$csv = new CsvFile();
/* @var $file SplTempFileObject */
$file = $csv->getFile();
$file->fwrite("A1,B1,C1\nA2,B2,C2\n\n");
$echoCsvFile($csv);
// as the example shows empty lines are dropped automatically as those
// are normally inside files in common scenarios, but this can be
// removed:
$csv->removeDecoration();
$echoCsvFile($csv);
// and added again:
$csv->decorateWith(new SkipEmptyLines());
$echoCsvFile($csv);
// for the moment take the CSV file as is and ignore empty lines
// this works because CsvFile can not only operate on an SplFileObject
// but anything that is Traversable
$csv = new CsvFile($csv);
// but tell that the first line contains the field names
$csv->decorateWith(new FieldNamesInFirstRow());
$echoCsvFile($csv);
// Let's change the field names, because in their current form it's not
// that nice
$csv->decorateWith(new FieldNames(['A', 'B', 'C']));
$echoCsvFile($csv);
// A full blown example
$csvData = <<<CSV
"Make","Model","Note"
"Chevy","1500","loaded"
"Chevy","2500",""
"Chevy","","loaded"
CSV;
$path = 'data://text/plain,' . $csvData;
$csv = new CsvFile($path);
$csv->decorateWith(new FieldNamesInFirstRow());
$echoCsvFile($csv);
// $csv->decorateWith(new SkipLines(["Chevy", "2500", ""]));
// $csv->decorateWith();
// $csv->decorateWith(new FieldNames(['make', 'model', 'note']));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment