Skip to content

Commit

Permalink
Merge pull request #114 from vlucas/alias_mapping
Browse files Browse the repository at this point in the history
Fix #97 with support for field/column aliases
  • Loading branch information
vlucas committed Sep 9, 2015
2 parents 75b6d81 + fb2c97b commit 420ac45
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 16 deletions.
30 changes: 28 additions & 2 deletions lib/Entity/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ class Manager
*/
protected $fields = [];

/**
* @var array
*/
protected $fieldAliasMappings = [];

/**
* @var array
*/
Expand Down Expand Up @@ -102,7 +107,7 @@ public function fields()

// Table Options
$entityTableOptions = $entityName::tableOptions();
$this->tableOptions = (array)$entityTableOptions;
$this->tableOptions = (array) $entityTableOptions;

// Custom Mapper
$this->mapper = $entityName::mapper();
Expand All @@ -113,6 +118,7 @@ public function fields()
'default' => null,
'value' => null,
'length' => null,
'column' => null,
'required' => false,
'notnull' => false,
'unsigned' => false,
Expand Down Expand Up @@ -164,6 +170,14 @@ public function fields()
$fieldOpts['notnull'] = true;
}

// Set column name to field name/key as default
if (null === $fieldOpts['column']) {
$fieldOpts['column'] = $fieldName;
} else {
// Store user-specified field alias mapping
$this->fieldAliasMappings[$fieldName] = $fieldOpts['column'];
}

// Old Spot used 'serial' field to describe auto-increment
// fields, so accomodate that here
if (isset($fieldOpts['serial']) && $fieldOpts['serial'] === true) {
Expand Down Expand Up @@ -195,6 +209,16 @@ public function fields()
return $returnFields;
}

/**
* Field alias mappings (used for lookup)
*
* @return array Field alias => actual column name
*/
public function fieldAliasMappings()
{
return $this->fieldAliasMappings;
}

/**
* Groups field keys into names arrays of fields with key name as index
*
Expand All @@ -214,7 +238,9 @@ public function fieldKeys()
'index' => []
];
$usedKeyNames = [];
foreach ($formattedFields as $fieldName => $fieldInfo) {
foreach ($formattedFields as $fieldInfo) {
$fieldName = $fieldInfo['column'];

// Determine key field name (can't use same key name twice, so we have to append a number)
$fieldKeyName = $table . '_' . $fieldName;
while (in_array($fieldKeyName, $usedKeyNames)) {
Expand Down
21 changes: 18 additions & 3 deletions lib/Mapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,9 @@ public function insert($entity, array $options = [])
if ($pkFieldInfo && $pkFieldInfo['autoincrement'] === true) {
if ($this->connectionIs('pgsql')) {
// Allow custom sequence name
$sequenceName = $table . '_' . $pkField . '_seq';
$fieldAliasMappings = $this->entityManager()->fieldAliasMappings();
$sequenceField = isset($fieldAliasMappings[$pkField]) ? $fieldAliasMappings[$pkField] : $pkField;
$sequenceName = $this->resolver()->escapeIdentifier($table . '_' . $sequenceField . '_seq');
if (isset($pkFieldInfo['sequence_name'])) {
$sequenceName = $pkFieldInfo['sequence_name'];
}
Expand Down Expand Up @@ -816,7 +818,7 @@ public function update(EntityInterface $entity, array $options = [])
$data = $this->convertToDatabaseValues($entityName, $data);

if (count($data) > 0) {
$result = $this->connection()->update($this->table(), $data, [$this->primaryKeyField() => $this->primaryKey($entity)]);
$result = $this->resolver()->update($this->table(), $data, [$this->primaryKeyField() => $this->primaryKey($entity)]);

// Run afterSave and afterUpdate
if (
Expand Down Expand Up @@ -894,6 +896,10 @@ public function delete($conditions = [])

/**
* Prepare data to be dumped to the data store
*
* @param string $entityName
* @param array $data Key/value pairs of data to store
* @return array
*/
protected function convertToDatabaseValues($entityName, array $data)
{
Expand All @@ -909,15 +915,24 @@ protected function convertToDatabaseValues($entityName, array $data)
}

/**
* Retrieve data from the data store
* Retrieve data from the data store and map it to PHP values
*
* @param string $entityName
* @param array $data Key/value pairs of data to store
* @return array
*/
protected function convertToPHPValues($entityName, array $data)
{
$phpData = [];
$fields = $entityName::fields();
$fieldAliasMappings = $this->entityManager()->fieldAliasMappings();
$platform = $this->connection()->getDatabasePlatform();
$entityData = array_intersect_key($data, $fields);
foreach ($data as $field => $value) {
if ($fieldAlias = array_search($field, $fieldAliasMappings)) {
$field = $fieldAlias;
}

// Field is in the Entity definitions
if (isset($entityData[$field])) {
$typeHandler = Type::getType($fields[$field]['type']);
Expand Down
8 changes: 8 additions & 0 deletions lib/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ private function parseWhereToSQLFragments(array $where, $useAlias = true)

$sqlFragments = [];
foreach ($where as $column => $value) {

$whereClause = "";
// Column name with comparison operator
$colData = explode(' ', $column);
Expand Down Expand Up @@ -717,6 +718,13 @@ public function escapeIdentifier($identifier)
*/
public function fieldWithAlias($field, $escaped = true)
{
$fieldInfo = $this->_mapper->entityManager()->fields();

// Determine real field name (column alias support)
if (isset($fieldInfo[$field])) {
$field = $fieldInfo[$field]['column'];
}

$field = $this->_tableName . '.' . $field;

return $escaped ? $this->escapeIdentifier($field) : $field;
Expand Down
35 changes: 24 additions & 11 deletions lib/Query/Resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,25 @@ public function migrateCreateSchema()
$schema = new \Doctrine\DBAL\Schema\Schema();
$table = $schema->createTable($this->escapeIdentifier($table));

foreach ($fields as $field => $fieldInfo) {
$fieldType = $fieldInfo['type'];
unset($fieldInfo['type']);
$table->addColumn($field, $fieldType, $fieldInfo);
foreach ($fields as $field) {
$fieldType = $field['type'];
unset($field['type']);
$table->addColumn($this->escapeIdentifier($field['column']), $fieldType, $field);
}

// PRIMARY
if ($fieldIndexes['primary']) {
$table->setPrimaryKey($fieldIndexes['primary']);
$resolver = $this;
$primaryKeys = array_map(function($value) use($resolver) { return $resolver->escapeIdentifier($value); }, $fieldIndexes['primary']);
$table->setPrimaryKey($primaryKeys);
}
// UNIQUE
foreach ($fieldIndexes['unique'] as $keyName => $keyFields) {
$table->addUniqueIndex($keyFields, $keyName);
$table->addUniqueIndex($keyFields, $this->escapeIdentifier($keyName));
}
// INDEX
foreach ($fieldIndexes['index'] as $keyName => $keyFields) {
$table->addIndex($keyFields, $keyName);
$table->addIndex($keyFields, $this->escapeIdentifier($keyName));
}

return $schema;
Expand Down Expand Up @@ -154,9 +156,7 @@ public function read(Query $query)
public function create($table, array $data)
{
$connection = $this->mapper->connection();
$result = $connection->insert($this->escapeIdentifier($table), $data);

return $result;
return $connection->insert($this->escapeIdentifier($table), $this->dataWithFieldAliasMappings($data));
}

/**
Expand All @@ -171,8 +171,21 @@ public function create($table, array $data)
public function update($table, array $data, array $where)
{
$connection = $this->mapper->connection();
return $connection->update($this->escapeIdentifier($table), $this->dataWithFieldAliasMappings($data), $this->dataWithFieldAliasMappings($where));
}

return $connection->update($this->escapeIdentifier($table), $data, $where);
/**
* Taken given field name/value inputs and map them to their aliased names
*/
public function dataWithFieldAliasMappings(array $data)
{
$fields = $this->mapper->entityManager()->fields();
$fieldMappings = [];
foreach($data as $field => $value) {
$fieldAlias = $this->escapeIdentifier(isset($fields[$field]) ? $fields[$field]['column'] : $field);
$fieldMappings[$fieldAlias] = $value;
}
return $fieldMappings;
}

/**
Expand Down
57 changes: 57 additions & 0 deletions tests/Entity/Legacy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
namespace SpotTest\Entity;

use Spot\Entity;
use Spot\EntityInterface;
use Spot\MapperInterface;
use Spot\EventEmitter;

/**
* Legacy - A legacy database table with custom column mappings
*
* @package Spot
*/
class Legacy extends Entity
{
protected static $table = 'test_legacy';

public static function fields()
{
return [
'id' => ['type' => 'integer', 'autoincrement' => true, 'primary' => true, 'column' => self::getIdFieldColumnName()],
'name' => ['type' => 'string', 'required' => true, 'column' => self::getNameFieldColumnName()],
'number' => ['type' => 'integer', 'required' => true, 'column' => self::getNumberFieldColumnName()],
'date_created' => ['type' => 'datetime', 'value' => new \DateTime(), 'column' => self::getDateCreatedColumnName()],
];
}

public static function relations(MapperInterface $mapper, EntityInterface $entity)
{
return [
'polymorphic_comments' => $mapper->hasMany($entity, 'SpotTest\Entity\PolymorphicComment', 'item_id')->where(['item_type' => 'legacy'])
];
}

/**
* Helpers for field/column names - methods with public access so we can avoid duplication in tests
*/
public static function getIdFieldColumnName()
{
return 'obnoxiouslyObtuse_IdentityColumn';
}

public static function getNameFieldColumnName()
{
return 'string_54_LegacyDB_x8';
}

public static function getNumberFieldColumnName()
{
return 'xbf86_haikusInTheDark';
}

public static function getDateCreatedColumnName()
{
return 'dtCreatedAt';
}
}
105 changes: 105 additions & 0 deletions tests/FieldAlias.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php
namespace SpotTest;
use SpotTest\Entity\Legacy;

/**
* @package Spot
*/
class FieldAlias extends \PHPUnit_Framework_TestCase
{
public static $legacyTable;

public static function setupBeforeClass()
{
self::$legacyTable = new \SpotTest\Entity\Legacy();
foreach (['Legacy', 'PolymorphicComment'] as $entity) {
test_spot_mapper('SpotTest\Entity\\' . $entity)->migrate();
}
}

public static function tearDownAfterClass()
{
foreach (['Legacy', 'PolymorphicComment'] as $entity) {
test_spot_mapper('\SpotTest\Entity\\' . $entity)->dropTable();
}
}

public function testLegacySelectFieldsAreAliases()
{
$mapper = test_spot_mapper('SpotTest\Entity\Legacy');
$query = $mapper->select()->noQuote()->where(['number' => 2, 'name' => 'legacy_crud']);
$this->assertEquals("SELECT * FROM test_legacy test_legacy WHERE test_legacy." . self::$legacyTable->getNumberFieldColumnName() ." = ? AND test_legacy." . self::$legacyTable->getNameFieldColumnName() . " = ?", $query->toSql());
}

// Ordering
public function testLegacyOrderBy()
{
$mapper = test_spot_mapper('SpotTest\Entity\Legacy');
$query = $mapper->select()->noQuote()->where(['number' => 2])->order(['date_created' => 'ASC']);
$this->assertContains("ORDER BY test_legacy." . self::$legacyTable->getDateCreatedColumnName() . " ASC", $query->toSql());
}

// Grouping
public function testLegacyGroupBy()
{
$mapper = test_spot_mapper('SpotTest\Entity\Legacy');
$query = $mapper->select()->noQuote()->where(['name' => 'test_group'])->group(['id']);
$this->assertEquals("SELECT * FROM test_legacy test_legacy WHERE test_legacy." . self::$legacyTable->getNameFieldColumnName() . " = ? GROUP BY test_legacy." . self::$legacyTable->getIdFieldColumnName(), $query->toSql());
}

// Insert
public function testLegacyInsert()
{
$legacy = new Legacy();
$legacy->name = 'Something Here';
$legacy->number = 5;

$mapper = test_spot_mapper('SpotTest\Entity\Legacy');
$mapper->save($legacy);
return $legacy;
}

/**
* @depends testLegacyInsert
*/
public function testLegacyUpdate(Legacy $legacy)
{
$legacy->name = 'Something ELSE Here';
$legacy->number = 6;

$mapper = test_spot_mapper('SpotTest\Entity\Legacy');
$mapper->save($legacy);
}

/**
* @depends testLegacyInsert
*/
public function testLegacyEntityFieldMapping(Legacy $legacy)
{
$mapper = test_spot_mapper('SpotTest\Entity\Legacy');
$savedLegacyItem = $mapper->first();

$this->assertEquals($legacy->name, $savedLegacyItem->name);
$this->assertEquals($legacy->number, $savedLegacyItem->number);
}

/**
* @depends testLegacyInsert
*/
public function testLegacyRelations(Legacy $legacy)
{
// New Comment
$commentMapper = test_spot_mapper('SpotTest\Entity\PolymorphicComment');
$comment = new \SpotTest\Entity\PolymorphicComment([
'item_id' => $legacy->id,
'item_type' => 'legacy',
'name' => 'Testy McTesterpants',
'email' => '[email protected]',
'body' => '<p>Comment Text</p>'
]);
$commentMapper->save($comment);

$this->assertInstanceOf('Spot\Relation\HasMany', $legacy->polymorphic_comments);
$this->assertEquals(count($legacy->polymorphic_comments), 1);
}
}

0 comments on commit 420ac45

Please sign in to comment.