Skip to content

Commit

Permalink
OneToMany Reader - For Merging 2 readers
Browse files Browse the repository at this point in the history
  • Loading branch information
AydinHassan authored and Aydin committed Jun 2, 2014
1 parent 82697ee commit cd14a56
Show file tree
Hide file tree
Showing 3 changed files with 606 additions and 0 deletions.
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Documentation
- [DbalReader](#dbalreader)
- [DoctrineReader](#doctrinereader)
- [ExcelReader](#excelreader)
- [One To Many Reader](#onetomanyreader)
- [Create a reader](#create-a-reader)
* [Writers](#writers)
- [ArrayWriter](#arraywriter)
Expand Down Expand Up @@ -267,6 +268,82 @@ $file = new \SplFileObject('path/to/ecxel_file.xls');
$reader = new ExcelReader($file);
```

###OneToManyReader

Allows for merging of two data sources (using existing readers), for example you have one CSV with orders and another with order items.

Imagine two CSV's like the following:

```
OrderId,Price
1,30
2,15
```

```
OrderId,Name
1,"Super Cool Item 1"
1,"Super Cool Item 2"
2,"Super Cool Item 3"
```

You want to associate the items to the order. Using the OneToMany reader we can nest these rows in the order using a key
which you specify in the OneToManyReader.

The code would look something like:

```php
$orderFile = new \SplFileObject("orders.csv");
$orderReader = new CsvReader($file, $orderFile);
$orderReader->setHeaderRowNumber(0);

$orderItemFile = new \SplFileObject("order_items.csv");
$orderItemReader = new CsvReader($file, $orderFile);
$orderItemReader->setHeaderRowNumber(0);

$oneToManyReader = new OneToManyReader($orderReader, $orderItemReader, 'items', 'OrderId', 'OrderId');
```

The third parameter is the key which the order item data will be nested under. This will be an array of order items.
The fourth and fifth parameters are "primary" and "foreign" keys of the data. The OneToMany reader will try to match the data using these keys.
Take for example the CSV's given above, you would expect that Order "1" has the first 2 Order Items associated to it due to their Order Id's also
being "1".

Note: You can omit the last parameter, if both files have the same field. Eg if parameter 4 is 'OrderId' and you don't specify
paramater 5, the reader will look for the foreign key using 'OrderId'

The resulting data will look like:

```php
//Row 1
array(
'OrderId' => 1,
'Price' => 30,
'items' => array(
array(
'OrderId' => 1,
'Name' => 'Super Cool Item 1',
),
array(
'OrderId' => 1,
'Name' => 'Super Cool Item 2',
),
),
);

//Row2
array(
'OrderId' => 2,
'Price' => 15,
'items' => array(
array(
'OrderId' => 2,
'Name' => 'Super Cool Item 1',
),
)
);
```

#### Create a reader

You can create your own data reader by implementing the
Expand Down
182 changes: 182 additions & 0 deletions src/Ddeboer/DataImport/Reader/OneToManyReader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?php

namespace Ddeboer\DataImport\Reader;

use Ddeboer\DataImport\Exception\ReaderException;

/**
* Takes multiple readers for processing in the same workflow
*
* @author Adam Paterson <[email protected]>
* @author Aydin Hassan <[email protected]>
*/
class OneToManyReader implements ReaderInterface
{
/**
* @var ReaderInterface
*/
protected $leftReader;

/**
* @var ReaderInterface
*/
protected $rightReader;

/**
* @var string Left Join Field
*/
protected $leftJoinField;

/**
* @var string Right Join Field
*/
protected $rightJoinField;

/**
* @var string Key to nest the rightRows under
*/
protected $nestKey;

/**
* @param ReaderInterface $leftReader
* @param ReaderInterface $rightReader
* @param string $nestKey
* @param string $leftJoinField
* @param string $rightJoinField
*/
public function __construct(
ReaderInterface $leftReader,
ReaderInterface $rightReader,
$nestKey,
$leftJoinField,
$rightJoinField = null
) {
$this->leftJoinField = $leftJoinField;

if (!$rightJoinField) {
$this->rightJoinField = $this->leftJoinField;
} else {
$this->rightJoinField = $rightJoinField;
}

$this->leftReader = $leftReader;
$this->rightReader = $rightReader;
$this->nestKey = $nestKey;
}

/**
* Create an array of children in the leftRow,
* with the data returned from the right reader
* Where the ID fields Match
*
* @return array
* @throws ReaderException
*/
public function current()
{
$leftRow = $this->leftReader->current();

if (array_key_exists($this->nestKey, $leftRow)) {
throw new ReaderException(
sprintf(
'Left Row: "%s" Reader already contains a field named "%s". Please choose a different nest key field',
$this->key(),
$this->nestKey
)
);
}
$leftRow[$this->nestKey] = array();

$leftId = $this->getRowId($leftRow, $this->leftJoinField);
$rightRow = $this->rightReader->current();
$rightId = $this->getRowId($rightRow, $this->rightJoinField);

while ($leftId == $rightId && $this->rightReader->valid()) {

$leftRow[$this->nestKey][] = $rightRow;
$this->rightReader->next();

$rightRow = $this->rightReader->current();

if($this->rightReader->valid()) {
$rightId = $this->getRowId($rightRow, $this->rightJoinField);
}
}

return $leftRow;
}

/**
* @param array $row
* @param string $idField
* @return mixed
* @throws ReaderException
*/
protected function getRowId(array $row, $idField)
{
if (!array_key_exists($idField, $row)) {
throw new ReaderException(
sprintf(
'Row: "%s" has no field named "%s"',
$this->key(),
$idField
)
);
}

return $row[$idField];
}

/**
* @return void Any returned value is ignored.
*/
public function next()
{
$this->leftReader->next();
//right reader is iterated in current() method.
}

/**
* @return mixed scalar on success, or null on failure.
*/
public function key()
{
return $this->leftReader->key();
}

/**
* Checks if current position is valid
* Returns true on success or false on failure.
*/
public function valid()
{
return $this->leftReader->valid() && $this->rightReader->valid();
}

/**
* Rewind the Iterator to the first element
* @return void Any returned value is ignored.
*/
public function rewind()
{
$this->leftReader->rewind();
$this->rightReader->rewind();
}

/**
* @return array
*/
public function getFields()
{
return array_merge($this->leftReader->getFields(), array($this->nestKey));
}

/**
* Count elements of an object
* The return value is cast to an integer.
*/
public function count()
{
return $this->leftReader->count();
}
}
Loading

0 comments on commit cd14a56

Please sign in to comment.