Stole the TypeGuesser (and its tests) from symfony's propel bridge

This commit is contained in:
Kévin Gomez 2013-11-04 16:27:38 +00:00
parent b2abeb0ead
commit 44ec2b4b29
6 changed files with 586 additions and 0 deletions

182
Form/PropelTypeGuesser.php Normal file
View file

@ -0,0 +1,182 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Form;
use Propel\Runtime\Map\RelationMap;
use Propel\Runtime\Util\PropelColumnTypes;
use Symfony\Component\Form\FormTypeGuesserInterface;
use Symfony\Component\Form\Guess\Guess;
use Symfony\Component\Form\Guess\TypeGuess;
use Symfony\Component\Form\Guess\ValueGuess;
/**
* Propel Type guesser.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PropelTypeGuesser implements FormTypeGuesserInterface
{
private $cache = array();
/**
* {@inheritDoc}
*/
public function guessType($class, $property)
{
if (!$table = $this->getTable($class)) {
return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE);
}
foreach ($table->getRelations() as $relation) {
if ($relation->getType() === RelationMap::MANY_TO_ONE) {
if (strtolower($property) === strtolower($relation->getName())) {
return new TypeGuess('model', array(
'class' => $relation->getForeignTable()->getClassName(),
'multiple' => false,
), Guess::HIGH_CONFIDENCE);
}
} elseif ($relation->getType() === RelationMap::ONE_TO_MANY) {
if (strtolower($property) === strtolower($relation->getPluralName())) {
return new TypeGuess('model', array(
'class' => $relation->getForeignTable()->getClassName(),
'multiple' => true,
), Guess::HIGH_CONFIDENCE);
}
} elseif ($relation->getType() === RelationMap::MANY_TO_MANY) {
if (strtolower($property) == strtolower($relation->getPluralName())) {
return new TypeGuess('model', array(
'class' => $relation->getLocalTable()->getClassName(),
'multiple' => true,
), Guess::HIGH_CONFIDENCE);
}
}
}
if (!$column = $this->getColumn($class, $property)) {
return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE);
}
switch ($column->getType()) {
case PropelColumnTypes::BOOLEAN:
case PropelColumnTypes::BOOLEAN_EMU:
return new TypeGuess('checkbox', array(), Guess::HIGH_CONFIDENCE);
case PropelColumnTypes::TIMESTAMP:
case PropelColumnTypes::BU_TIMESTAMP:
return new TypeGuess('datetime', array(), Guess::HIGH_CONFIDENCE);
case PropelColumnTypes::DATE:
case PropelColumnTypes::BU_DATE:
return new TypeGuess('date', array(), Guess::HIGH_CONFIDENCE);
case PropelColumnTypes::TIME:
return new TypeGuess('time', array(), Guess::HIGH_CONFIDENCE);
case PropelColumnTypes::FLOAT:
case PropelColumnTypes::REAL:
case PropelColumnTypes::DOUBLE:
case PropelColumnTypes::DECIMAL:
return new TypeGuess('number', array(), Guess::MEDIUM_CONFIDENCE);
case PropelColumnTypes::TINYINT:
case PropelColumnTypes::SMALLINT:
case PropelColumnTypes::INTEGER:
case PropelColumnTypes::BIGINT:
case PropelColumnTypes::NUMERIC:
return new TypeGuess('integer', array(), Guess::MEDIUM_CONFIDENCE);
case PropelColumnTypes::ENUM:
case PropelColumnTypes::CHAR:
if ($column->getValueSet()) {
//check if this is mysql enum
$choices = $column->getValueSet();
$labels = array_map('ucfirst', $choices);
return new TypeGuess('choice', array('choices' => array_combine($choices, $labels)), Guess::MEDIUM_CONFIDENCE);
}
case PropelColumnTypes::VARCHAR:
return new TypeGuess('text', array(), Guess::MEDIUM_CONFIDENCE);
case PropelColumnTypes::LONGVARCHAR:
case PropelColumnTypes::BLOB:
case PropelColumnTypes::CLOB:
case PropelColumnTypes::CLOB_EMU:
return new TypeGuess('textarea', array(), Guess::MEDIUM_CONFIDENCE);
default:
return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE);
}
}
/**
* {@inheritDoc}
*/
public function guessRequired($class, $property)
{
if ($column = $this->getColumn($class, $property)) {
return new ValueGuess($column->isNotNull(), Guess::HIGH_CONFIDENCE);
}
}
/**
* {@inheritDoc}
*/
public function guessMaxLength($class, $property)
{
if ($column = $this->getColumn($class, $property)) {
if ($column->isTextType()) {
return new ValueGuess($column->getSize(), Guess::HIGH_CONFIDENCE);
}
switch ($column->getType()) {
case PropelColumnTypes::FLOAT:
case PropelColumnTypes::REAL:
case PropelColumnTypes::DOUBLE:
case PropelColumnTypes::DECIMAL:
return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE);
}
}
}
/**
* {@inheritDoc}
*/
public function guessPattern($class, $property)
{
if ($column = $this->getColumn($class, $property)) {
switch ($column->getType()) {
case PropelColumnTypes::FLOAT:
case PropelColumnTypes::REAL:
case PropelColumnTypes::DOUBLE:
case PropelColumnTypes::DECIMAL:
return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE);
}
}
}
protected function getTable($class)
{
if (isset($this->cache[$class])) {
return $this->cache[$class];
}
if (class_exists($queryClass = $class.'Query')) {
$query = new $queryClass();
return $this->cache[$class] = $query->getTableMap();
}
}
protected function getColumn($class, $property)
{
if (isset($this->cache[$class.'::'.$property])) {
return $this->cache[$class.'::'.$property];
}
$table = $this->getTable($class);
if ($table && $table->hasColumn($property)) {
return $this->cache[$class.'::'.$property] = $table->getColumn($property);
}
}
}

View file

@ -12,6 +12,7 @@
<parameter key="propel.data_collector.class">Propel\PropelBundle\DataCollector\PropelDataCollector</parameter>
<parameter key="propel.logger.class">Propel\PropelBundle\Logger\PropelLogger</parameter>
<parameter key="propel.twig.extension.syntax.class">Propel\PropelBundle\Twig\Extension\SyntaxExtension</parameter>
<parameter key="form.type_guesser.propel.class">Propel\PropelBundle\Form\PropelTypeGuesser</parameter>
</parameters>
<services>
@ -32,5 +33,9 @@
<service id="propel.twig.extension.syntax" class="%propel.twig.extension.syntax.class%">
<tag name="twig.extension" />
</service>
<service id="form.type_guesser.propel" class="%form.type_guesser.propel.class%">
<tag name="form.type_guesser" />
</service>
</services>
</container>

61
Tests/Fixtures/Column.php Normal file
View file

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Fixtures;
use Propel\Runtime\Util\PropelColumnTypes;
class Column
{
private $name;
private $type;
public function __construct($name, $type)
{
$this->name = $name;
$this->type = $type;
}
public function getType()
{
return $this->type;
}
public function isTextType()
{
if (!$this->type) {
return false;
}
switch ($this->type) {
case PropelColumnTypes::CHAR:
case PropelColumnTypes::VARCHAR:
case PropelColumnTypes::LONGVARCHAR:
case PropelColumnTypes::BLOB:
case PropelColumnTypes::CLOB:
case PropelColumnTypes::CLOB_EMU:
return true;
}
return false;
}
public function getSize()
{
return $this->isTextType() ? 255 : 0;
}
public function isNotNull()
{
return ('id' === $this->name);
}
}

109
Tests/Fixtures/Item.php Normal file
View file

@ -0,0 +1,109 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Fixtures;
use Propel\Runtime\ActiveRecord\ActiveRecordInterface;
use Propel\Runtime\Connection\ConnectionInterface;
class Item implements ActiveRecordInterface
{
private $id;
private $value;
private $groupName;
private $price;
public function __construct($id = null, $value = null, $groupName = null, $price = null)
{
$this->id = $id;
$this->value = $value;
$this->groupName = $groupName;
$this->price = $price;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getValue()
{
return $this->value;
}
public function getGroupName()
{
return $this->groupName;
}
public function getPrice()
{
return $this->price;
}
public function getPrimaryKey()
{
return $this->getId();
}
public function setPrimaryKey($primaryKey)
{
$this->setId($primaryKey);
}
public function isModified()
{
return false;
}
public function isColumnModified($col)
{
return false;
}
public function isNew()
{
return false;
}
public function setNew($b)
{
}
public function resetModified()
{
}
public function isDeleted()
{
return false;
}
public function setDeleted($b)
{
}
public function delete(ConnectionInterface $con = null)
{
}
public function save(ConnectionInterface $con = null)
{
}
}

View file

@ -0,0 +1,100 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Fixtures;
use Propel\Runtime\Map\ColumnMap;
use Propel\Runtime\Map\RelationMap;
use Propel\Runtime\Map\TableMap;
use Propel\Runtime\Util\PropelColumnTypes;
class ItemQuery
{
private $map = array(
'id' => PropelColumnTypes::INTEGER,
'value' => PropelColumnTypes::VARCHAR,
'price' => PropelColumnTypes::FLOAT,
'is_active' => PropelColumnTypes::BOOLEAN,
'enabled' => PropelColumnTypes::BOOLEAN_EMU,
'updated_at' => PropelColumnTypes::TIMESTAMP,
'updated_at' => PropelColumnTypes::TIMESTAMP,
'updated_at' => PropelColumnTypes::TIMESTAMP,
'updated_at' => PropelColumnTypes::TIMESTAMP,
);
public function getTableMap()
{
// Allows to define methods in this class
// to avoid a lot of mock classes
return $this;
}
public function getPrimaryKeys()
{
$cm = new ColumnMap('id', new TableMap());
$cm->setType('INTEGER');
return array('id' => $cm);
}
/**
* Method from the TableMap API
*/
public function hasColumn($column)
{
return in_array($column, array_keys($this->map));
}
/**
* Method from the TableMap API
*/
public function getColumn($column)
{
if ($this->hasColumn($column)) {
return new Column($column, $this->map[$column]);
}
return null;
}
/**
* Method from the TableMap API
*/
public function getRelations()
{
// table maps
$authorTable = new TableMap();
$authorTable->setClassName('\Foo\Author');
$resellerTable = new TableMap();
$resellerTable->setClassName('\Foo\Reseller');
// relations
$mainAuthorRelation = new RelationMap('MainAuthor');
$mainAuthorRelation->setType(RelationMap::MANY_TO_ONE);
$mainAuthorRelation->setForeignTable($authorTable);
$authorRelation = new RelationMap('Author');
$authorRelation->setType(RelationMap::ONE_TO_MANY);
$authorRelation->setForeignTable($authorTable);
$resellerRelation = new RelationMap('Reseller');
$resellerRelation->setType(RelationMap::MANY_TO_MANY);
$resellerRelation->setLocalTable($resellerTable);
return array(
$mainAuthorRelation,
$authorRelation,
$resellerRelation
);
}
}

View file

@ -0,0 +1,129 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Propel\PropelBundle\Tests\Form;
use Propel\PropelBundle\Form\PropelTypeGuesser;
use Propel\PropelBundle\Tests\TestCase;
use Symfony\Component\Form\Guess\Guess;
class PropelTypeGuesserTest extends TestCase
{
const CLASS_NAME = 'Propel\PropelBundle\Tests\Fixtures\Item';
const UNKNOWN_CLASS_NAME = 'Propel\PropelBundle\Tests\Fixtures\UnknownItem';
private $guesser;
public function setUp()
{
$this->guesser = new PropelTypeGuesser();
}
public function testGuessMaxLengthWithText()
{
$value = $this->guesser->guessMaxLength(self::CLASS_NAME, 'value');
$this->assertNotNull($value);
$this->assertEquals(255, $value->getValue());
}
public function testGuessMaxLengthWithFloat()
{
$value = $this->guesser->guessMaxLength(self::CLASS_NAME, 'price');
$this->assertNotNull($value);
$this->assertNull($value->getValue());
}
public function testGuessMinLengthWithText()
{
$value = $this->guesser->guessPattern(self::CLASS_NAME, 'value');
$this->assertNull($value);
}
public function testGuessMinLengthWithFloat()
{
$value = $this->guesser->guessPattern(self::CLASS_NAME, 'price');
$this->assertNotNull($value);
$this->assertNull($value->getValue());
}
public function testGuessRequired()
{
$value = $this->guesser->guessRequired(self::CLASS_NAME, 'id');
$this->assertNotNull($value);
$this->assertTrue($value->getValue());
}
public function testGuessRequiredWithNullableColumn()
{
$value = $this->guesser->guessRequired(self::CLASS_NAME, 'value');
$this->assertNotNull($value);
$this->assertFalse($value->getValue());
}
public function testGuessTypeWithoutTable()
{
$value = $this->guesser->guessType(self::UNKNOWN_CLASS_NAME, 'property');
$this->assertNotNull($value);
$this->assertEquals('text', $value->getType());
$this->assertEquals(Guess::LOW_CONFIDENCE, $value->getConfidence());
}
public function testGuessTypeWithoutColumn()
{
$value = $this->guesser->guessType(self::CLASS_NAME, 'property');
$this->assertNotNull($value);
$this->assertEquals('text', $value->getType());
$this->assertEquals(Guess::LOW_CONFIDENCE, $value->getConfidence());
}
/**
* @dataProvider dataProviderForGuessType
*/
public function testGuessType($property, $type, $confidence, $multiple = null)
{
$value = $this->guesser->guessType(self::CLASS_NAME, $property);
$this->assertNotNull($value);
$this->assertEquals($type, $value->getType());
$this->assertEquals($confidence, $value->getConfidence());
if ($type === 'model') {
$options = $value->getOptions();
$this->assertSame($multiple, $options['multiple']);
}
}
public static function dataProviderForGuessType()
{
return array(
array('is_active', 'checkbox', Guess::HIGH_CONFIDENCE),
array('enabled', 'checkbox', Guess::HIGH_CONFIDENCE),
array('id', 'integer', Guess::MEDIUM_CONFIDENCE),
array('value', 'text', Guess::MEDIUM_CONFIDENCE),
array('price', 'number', Guess::MEDIUM_CONFIDENCE),
array('updated_at', 'datetime', Guess::HIGH_CONFIDENCE),
array('Authors', 'model', Guess::HIGH_CONFIDENCE, true),
array('Resellers', 'model', Guess::HIGH_CONFIDENCE, true),
array('MainAuthor', 'model', Guess::HIGH_CONFIDENCE, false),
);
}
}