I'm writing a package that is designed to provide a convenient way to inspect the schema for a database. The goal is that I can use it for generator code to suit the schema. The complete code can be found at https://github.com/courtney-miles/schnoop.
For example, I have classes for IntType
, VarCharType
, BlobType
, etc. And I need to be able to construct these types from the results of the query, SHOW FULL COLUMNS FROM tbl;
These data type objects conform to a common interface, DataTypeInterface
.
<?php
interface DataTypeInterface
{
/**
* Get the name of the type.
* @return string
*/
public function getName();
/**
* Indicates if a default value can be used with this type in a column.
* @return bool
*/
public function allowDefault();
/**
* Cast a value from MySQL to a suitable PHP type.
* @param mixed $value
* @return mixed
*/
public function cast($value);
}
Additional interfaces are combined to reflect the unique properties of the data types. For example, this is the interface for used by all integer types.
<?php
interface IntTypeInterface extends DataTypeInterface
{
public function getDisplayWidth();
public function isSigned();
public function getMinRange();
public function getMaxRange();
}
(To be more correct, the IntTypeInterface
actually extends other interfaces that specify these properties because there is an overlap with Floating Point Types. If you are interested in precisely all the interfaces involved, refer to https://github.com/courtney-miles/schnoop/blob/master/src/Schema/MySQL/DataType/IntTypeInterface.php)
So this is my current factory method which is obviously quite ugly.
<?php
class Factory
{
// ...
/**
* @param string $dataTypeString Data type string as reported by MySQL when executing SHOW FULL COLUMNS FROM table;
* @param null $collation
*/
public function createDataType($dataTypeString, $collation = null)
{
$dataType = null;
if (preg_match('/^(tiny|small|medium|big)?int(eger)?\((\d+)\)( unsigned)?/i', $dataTypeString, $matches)) {
$displayWidth = $matches[3];
$signed = empty($matches[4]);
switch (strtolower($matches[1])) {
case 'tiny':
$dataType = new TinyIntType($displayWidth, $signed);
break;
case 'small':
$dataType = new SmallIntType($displayWidth, $signed);
break;
case 'medium':
$dataType = new MediumIntType($displayWidth, $signed);
break;
case 'big':
$dataType = new BigIntType($displayWidth, $signed);
break;
default:
$dataType = new IntType($displayWidth, $signed);
break;
}
} elseif (preg_match('/^(decimal|float|double)\((\d+),(\d+)\)( unsigned)?/i', $dataTypeString, $matches)) {
$precision = $matches[2];
$scale = $matches[3];
$signed = empty($matches[4]);
switch (strtolower($matches[1])) {
case 'decimal':
$dataType = new DecimalType($precision, $scale, $signed);
break;
case 'double':
$dataType = new DoubleType($precision, $scale, $signed);
break;
case 'float':
$dataType = new FloatType($precision, $scale, $signed);
}
} elseif (preg_match('/^(var)?binary\((\d+)\)$/i', $dataTypeString, $matches)) {
$isVarBinary = !empty($matches[1]);
$length = $matches[2];
if (strtolower($isVarBinary)) {
$dataType = new VarBinaryType($length);
} else {
$dataType = new BinaryType($length);
}
} elseif (preg_match('/^bit\((\d+)\)$/i', $dataTypeString, $matches)) {
$dataType = new BitType($matches[1]);
} else {
$characterSet = !empty($collation) ? $this->getCharacterSet($collation) : null;
if (preg_match('/^(var)?char\((\d+)\)$/i', $dataTypeString, $matches)) {
$isVarChar = !empty($matches[1]);
$length = $matches[2];
if ($isVarChar) {
$dataType = new VarCharType($length, $characterSet, $collation);
} else {
$dataType = new CharType($length, $characterSet, $collation);
}
} elseif (preg_match("/(set|enum)(\('.+'\))/i", $dataTypeString, $matches)) {
$options = $this->parseOptions($matches[2]);
switch (strtolower($matches[1])) {
case 'set':
$dataType = new SetType($options, $characterSet, $collation);
break;
case 'enum':
$dataType = new EnumType($options, $characterSet, $collation);
break;
}
} else {
switch (strtolower($dataTypeString)) {
case 'text':
$dataType = new TextType($characterSet, $collation);
break;
case 'tinytext':
$dataType = new TinyTextType($characterSet, $collation);
break;
case 'mediumtext':
$dataType = new MediumTextType($characterSet, $collation);
break;
case 'longtext':
$dataType = new LongTextType($characterSet, $collation);
break;
case 'blob':
$dataType = new BlobType();
break;
case 'tinyblob':
$dataType = new TinyBlobType();
break;
case 'mediumblob':
$dataType = new MediumBlobType();
break;
case 'longblob':
$dataType = new LongBlobType();
break;
}
}
}
return $dataType;
}
}
I am unsure of the most ideal pattern to clean this up. My current thoughts are:
- Use something like the Chain Of Responsibility pattern, where I have a factory per data type, and each will decide if it can handle the data type string, and if it can't it will be passed onto the next factory.
- Have a factory per data type, and a super factory that would examine the data type string to determine the correct data type factory to handle the string.
All examples of Factory patterns that I see are where the different objects all have the exact same constructor arguments. So I'm not sure of the the most sensible pattern to solve when the different objects have different constructor arguments.
Note, I originally posted this at on Stackoverflow at https://stackoverflow.com/questions/38339366/how-to-employ-the-factory-pattern-where-the-constructor-arguments-vary
cast()
method. In my opinion it's inconsistent (SRP violation), but at the same time this is the only method that would make having subtypes reasonable (it could make a true object - not data structure). I mean you don't need special type if it's only responsibility is to tell its properties to the user. What is your intention behind this method? Who would be the client to such object? \$\endgroup\$