Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Custom Data Types (Data Domain) #1320

Merged
merged 18 commits into from
Apr 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
53c19ed
Adapted 'Console/Command/ListTest' unit test to PHPUnit 6 namespaced …
twoixter Mar 3, 2018
e513916
Added 'getDataDomain' to Config object
twoixter Mar 3, 2018
6b54aaf
Make sure Migration\Manager\Environment inherits 'data_domain' option…
twoixter Mar 3, 2018
569cf86
Added methods to AbstractAdapter to allow for data domain types.
twoixter Mar 5, 2018
0248c2b
Make Table::addColumn and Table::changeColumn data domain aware.
twoixter Mar 6, 2018
2e2714d
Added feature to specify domain type as Phinx constant (E.G: PHINX_TY…
twoixter Mar 6, 2018
a288459
Added documentation for the data model configuration.
twoixter Mar 6, 2018
5cc6cef
Fixed issues with stickler-ci and travis-ci
twoixter Mar 6, 2018
ed3369c
Fixed travis-ci (return type of ConfigInterface::getDataDomain)
twoixter Mar 6, 2018
a16909c
Made ConfigInterface::getDataDomain always return an array.
twoixter Mar 6, 2018
489e75e
Added additional typehinting and refactor of if-else on AbstractAdapt…
twoixter Mar 13, 2018
0ac5ff2
Merge remote-tracking branch 'upstream/master'
twoixter Mar 23, 2020
3d6f96e
Fixing style errors.
stickler-ci Mar 23, 2020
ec3831c
Removed test that was using deprecated 'INT_SMALL' constant from Post…
twoixter Mar 23, 2020
d1d7144
Resolved conflicts from upstream.
twoixter Mar 23, 2020
87c1b89
Merge branch '0.next' into master
twoixter Mar 24, 2020
d348b28
Fixing style errors.
stickler-ci Mar 24, 2020
3796795
Updated changes from merged 'master' into '0.next'.
twoixter Apr 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 72 additions & 8 deletions docs/en/migrations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -953,16 +953,80 @@ INT_BIG BIGINT

.. code-block:: php

use Phinx\Db\Adapter\MysqlAdapter;
<?php

use Phinx\Db\Adapter\MysqlAdapter;

//...

$table = $this->table('cart_items');
$table->addColumn('user_id', 'integer')
->addColumn('product_id', 'integer', ['limit' => MysqlAdapter::INT_BIG])
->addColumn('subtype_id', 'integer', ['limit' => MysqlAdapter::INT_SMALL])
->addColumn('quantity', 'integer', ['limit' => MysqlAdapter::INT_TINY])
->create();

User Defined Types (Custom Data Domain)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Building upon the base types and column options you can define your custom
user defined types. Custom user defined types are configured in the
``data_domain`` root config option.

.. code-block:: yaml

data_domain:
phone_number:
type: string
length: 20
address_line:
type: string
length: 150

Each user defined type can hold any valid type and column option, they are just
used as "macros" and replaced at the time of migration.

//...
.. code-block:: php

<?php

//...

$table = $this->table('user_data');
$table->addColumn('user_phone_number', 'phone_number')
->addColumn('user_address_line_1', 'address_line')
->addColumn('user_address_line_2', 'address_line', ['null' => true])
->create();

Specifying a data domain at the beginning of your project is crucial to have a
homogeneous data model. It avoids mistakes like having many ``contact_name``
columns with different lengths, mismatched integer types (long vs. bigint, etc).

.. note::

$table = $this->table('cart_items');
$table->addColumn('user_id', 'integer')
->addColumn('product_id', 'integer', ['limit' => MysqlAdapter::INT_BIG])
->addColumn('subtype_id', 'integer', ['limit' => MysqlAdapter::INT_SMALL])
->addColumn('quantity', 'integer', ['limit' => MysqlAdapter::INT_TINY])
->create();
For ``integer``, ``text`` and ``blob`` columns you can use the special
constants from MySQL and Postgress adapter classes.

You can even customize some internal types to add your own default options,
but some column options can't be overriden in the data model (some options
are fixed like ``limit`` for the ``uuid`` special data type).

.. code-block:: yaml

# Some examples of custom data types
data_domain:
file:
type: blob
limit: BLOB_LONG # For MySQL DB. Uses MysqlAdapter::BLOB_LONG
boolean:
type: boolean # Customization of the boolean to be unsigned
signed: false
image_type:
type: enum # Enums can use YAML lists or a comma separated string
values:
- gif
- jpg
- png

Null Option and SQLite
~~~~~~~~~~~~~~~~~~~~~~
Expand Down
12 changes: 12 additions & 0 deletions src/Phinx/Config/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,18 @@ public function getTemplateClass()
return $this->values['templates']['class'];
}

/**
* {@inheritdoc}
*/
public function getDataDomain()
{
if (!isset($this->values['data_domain'])) {
return [];
}

return $this->values['data_domain'];
}

/**
* Get the version order.
*
Expand Down
7 changes: 7 additions & 0 deletions src/Phinx/Config/ConfigInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ public function getTemplateFile();
*/
public function getTemplateClass();

/**
* Get the data domain array.
*
* @return array[]
*/
public function getDataDomain();

/**
* Get the version order.
*
Expand Down
113 changes: 110 additions & 3 deletions src/Phinx/Db/Adapter/AbstractAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ abstract class AbstractAdapter implements AdapterInterface
protected $schemaTableName = 'phinxlog';

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

/**
* Class Constructor.
*
* @param array $options Options
* @param \Symfony\Component\Console\Input\InputInterface|null $input Input Interface
* @param \Symfony\Component\Console\Output\OutputInterface|null $output Output Interface
Expand All @@ -73,6 +80,10 @@ public function setOptions(array $options)
$this->setSchemaTableName($options['default_migration_table']);
}

if (isset($options['data_domain'])) {
$this->setDataDomain($options['data_domain']);
}

return $this;
}

Expand Down Expand Up @@ -146,7 +157,7 @@ public function getOutput()
}

/**
* {@inheritDoc}
* @inheritDoc
*
* @return array
*/
Expand Down Expand Up @@ -182,15 +193,111 @@ public function setSchemaTableName($schemaTableName)
}

/**
* @inheritDoc
* Gets the data domain.
*
* @return array
*/
public function getDataDomain()
{
return $this->dataDomain;
}

/**
dereuromark marked this conversation as resolved.
Show resolved Hide resolved
* Sets the data domain.
*
* @param array $dataDomain Array for the data domain
* @return $this
*/
public function setDataDomain(array $dataDomain)
{
$this->dataDomain = [];

// Iterate over data domain field definitions and perform initial and
// simple normalization. We make sure the definition as a base 'type'
// and it is compatible with the base Phinx types.
foreach ($dataDomain as $type => $options) {
if (!isset($options['type'])) {
throw new \InvalidArgumentException(sprintf(
'You must specify a type for data domain type "%s".',
$type
));
}

// Replace type if it's the name of a Phinx constant
if (defined('static::' . $options['type'])) {
$options['type'] = constant('static::' . $options['type']);
}

if (!in_array($options['type'], $this->getColumnTypes())) {
throw new \InvalidArgumentException(sprintf(
'An invalid column type "%s" was specified for data domain type "%s".',
$options['type'],
$type
));
}

$internal_type = $options['type'];
unset($options['type']);

// Do a simple replacement for the 'length' / 'limit' option and
// detect hinting values for 'limit'.
if (isset($options['length'])) {
$options['limit'] = $options['length'];
unset($options['length']);
}

if (isset($options['limit']) && !is_numeric($options['limit'])) {
if (!defined('static::' . $options['limit'])) {
throw new \InvalidArgumentException(sprintf(
'An invalid limit value "%s" was specified for data domain type "%s".',
$options['limit'],
$type
));
}

$options['limit'] = constant('static::' . $options['limit']);
}

// Save the data domain types in a more suitable format
$this->dataDomain[$type] = [
'type' => $internal_type,
'options' => $options,
];
}

return $this;
}

/**
* @inheritdoc
*/
public function getColumnForType($columnName, $type, array $options)
{
$column = new Column();
$column->setName($columnName);

if (array_key_exists($type, $this->getDataDomain())) {
$column->setType($this->dataDomain[$type]['type']);
$column->setOptions($this->dataDomain[$type]['options']);
} else {
$column->setType($type);
}

$column->setOptions($options);

return $column;
}

/**
* @inheritdoc
*/
public function hasSchemaTable()
{
return $this->hasTable($this->getSchemaTableName());
}

/**
* {@inheritDoc}
* @inheritDoc
*
* @throws \InvalidArgumentException
*
Expand Down
10 changes: 10 additions & 0 deletions src/Phinx/Db/Adapter/AdapterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ public function setOutput(OutputInterface $output);
*/
public function getOutput();

/**
* Returns a new Phinx\Db\Table\Column using the existent data domain.
*
* @param string $columnName The desired column name
* @param string $type The type for the column. Can be a data domain type.
* @param array $options Options array
* @return \Phinx\Db\Table\Column
*/
public function getColumnForType($columnName, $type, array $options);

/**
* Records a migration being run.
*
Expand Down
8 changes: 8 additions & 0 deletions src/Phinx/Db/Adapter/AdapterWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ public function getOutput()
*
* @return void
*/
public function getColumnForType($columnName, $type, array $options)
{
return $this->adapter->getColumnForType($columnName, $type, $options);
}

/**
* {@inheritdoc}
*/
public function connect()
{
$this->getAdapter()->connect();
Expand Down
1 change: 1 addition & 0 deletions src/Phinx/Migration/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@ public function getEnvironment($name)
// create an environment instance and cache it
$envOptions = $this->getConfig()->getEnvironment($name);
$envOptions['version_order'] = $this->getConfig()->getVersionOrder();
$envOptions['data_domain'] = $this->getConfig()->getDataDomain();

$environment = new Environment($name, $envOptions);
$this->environments[$name] = $environment;
Expand Down
15 changes: 11 additions & 4 deletions tests/Phinx/Config/AbstractConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,19 @@ public function getConfigArray()
'testing' => [
'adapter' => 'sqllite',
'wrapper' => 'testwrapper',
'path' => '%%PHINX_CONFIG_PATH%%/testdb/test.db'
'path' => '%%PHINX_CONFIG_PATH%%/testdb/test.db',
],
'production' => [
'adapter' => 'mysql'
]
]
'adapter' => 'mysql',
],
],
'data_domain' => [
'phone_number' => [
'type' => 'string',
'null' => true,
'length' => 15,
],
],
];
}

Expand Down
19 changes: 19 additions & 0 deletions tests/Phinx/Config/ConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,25 @@ public function testHasEnvironmentMethod()
$this->assertFalse($config->hasEnvironment('fakeenvironment'));
}

/**
* @covers \Phinx\Config\Config::getDataDomain
*/
public function testGetDataDomainMethod()
{
$config = new Config($this->getConfigArray());
$this->assertInternalType('array', $config->getDataDomain());
}

/**
* @covers \Phinx\Config\Config::getDataDomain
*/
public function testReturnsEmptyArrayWithEmptyDataDomain()
{
$config = new Config([]);
$this->assertInternalType('array', $config->getDataDomain());
$this->assertCount(0, $config->getDataDomain());
}

/**
* @covers \Phinx\Config\Config::getDefaultEnvironment
*/
Expand Down
Loading