Current File : /home/pacjaorg/wpt.pacja.org/cop/libraries/fof40/Utils/ModelTypeHints.php |
<?php
/**
* @package FOF
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace FOF40\Utils;
defined('_JEXEC') || die;
use FOF40\Model\DataModel;
/**
* Generate phpDoc type hints for the magic properties and methods of your DataModels.
*
* Usage:
* $typeHinter = new ModelTypeHints($instanceOfYourFOFDataModel);
* var_dump($typeHinter->getHints());
*
* This will dump the type hints you should add to the DocBlock of your DataModel class to allow IDEs such as phpStorm
* to provide smart type hinting for magic property and method access.
*
* @package FOF40\Utils
*/
class ModelTypeHints
{
/**
* The model for which to create type hints
*
* @var DataModel
*/
protected $model;
/**
* Name of the class. If empty will be inferred from the current object
*
* @var string
*/
protected $className;
/**
* Public constructor
*
* @param \FOF40\Model\DataModel $model The model to create hints for
*/
public function __construct(DataModel $model)
{
$this->model = $model;
$this->className = get_class($model);
}
/**
* Translates the database field type into a PHP base type
*
* @param string $type The type of the field
*
* @return string The PHP base type
*/
public static function getFieldType(string $type): string
{
// Remove parentheses, indicating field options / size (they don't matter in type detection)
if (!empty($type))
{
[$type,] = explode('(', $type);
}
$detectedType = null;
switch (trim($type))
{
case 'varchar':
case 'text':
case 'smalltext':
case 'longtext':
case 'char':
case 'mediumtext':
case 'character varying':
case 'nvarchar':
case 'nchar':
$detectedType = 'string';
break;
case 'date':
case 'datetime':
case 'time':
case 'year':
case 'timestamp':
case 'timestamp without time zone':
case 'timestamp with time zone':
$detectedType = 'string';
break;
case 'tinyint':
case 'smallint':
$detectedType = 'bool';
break;
case 'float':
case 'currency':
case 'single':
case 'double':
$detectedType = 'float';
break;
}
// Sometimes we have character types followed by a space and some cruft. Let's handle them.
if (is_null($detectedType) && !empty($type))
{
[$type,] = explode(' ', $type);
switch (trim($type))
{
case 'varchar':
case 'text':
case 'smalltext':
case 'longtext':
case 'char':
case 'mediumtext':
case 'nvarchar':
case 'nchar':
$detectedType = 'string';
break;
case 'date':
case 'datetime':
case 'time':
case 'year':
case 'timestamp':
case 'enum':
$detectedType = 'string';
break;
case 'tinyint':
case 'smallint':
$detectedType = 'bool';
break;
case 'float':
case 'currency':
case 'single':
case 'double':
$detectedType = 'float';
break;
default:
$detectedType = 'int';
break;
}
}
// If all else fails assume it's an int and hope for the best
if (empty($detectedType))
{
$detectedType = 'int';
}
return $detectedType;
}
/**
* @param string $className
*/
public function setClassName(string $className): void
{
$this->className = $className;
}
/**
* Return the raw hints array
*
* @return array
*
* @throws \FOF40\Model\DataModel\Relation\Exception\RelationNotFound
*/
public function getRawHints(): array
{
$model = $this->model;
$hints = [
'property' => [],
'method' => [],
'property-read' => [],
];
$hasFilters = $model->getBehavioursDispatcher()->hasObserverClass('FOF40\Model\DataModel\Behaviour\Filters');
$magicFields = [
'enabled', 'ordering', 'created_on', 'created_by', 'modified_on', 'modified_by', 'locked_on', 'locked_by',
];
foreach ($model->getTableFields() as $fieldName => $fieldMeta)
{
$fieldType = static::getFieldType($fieldMeta->Type);
if (!in_array($fieldName, $magicFields))
{
$hints['property'][] = [$fieldType, '$' . $fieldName];
}
if ($hasFilters)
{
$hints['method'][] = [
'$this',
$fieldName . '()',
$fieldName . '(' . $fieldType . ' $v)',
];
}
}
$relations = $model->getRelations()->getRelationNames();
$modelType = get_class($model);
$modelTypeParts = explode('\\', $modelType);
array_pop($modelTypeParts);
$modelType = implode('\\', $modelTypeParts) . '\\';
if ($relations !== [])
{
foreach ($relations as $relationName)
{
$relationObject = $model->getRelations()->getRelation($relationName)->getForeignModel();
$relationType = get_class($relationObject);
$relationType = str_replace($modelType, '', $relationType);
$hints['property-read'][] = [
$relationType,
'$' . $relationName,
];
}
}
return $hints;
}
/**
* Returns the docblock with the magic field hints for the model class
*
* @return string
*/
public function getHints(): string
{
$modelName = $this->className;
$text = "/**\n * Model $modelName\n *\n";
$hints = $this->getRawHints();
if (!empty($hints['property']))
{
$text .= " * Fields:\n *\n";
$colWidth = 0;
foreach ($hints['property'] as $hintLine)
{
$colWidth = max($colWidth, strlen($hintLine[0]));
}
$colWidth += 2;
foreach ($hints['property'] as $hintLine)
{
$text .= " * @property " . str_pad($hintLine[0], $colWidth, ' ') . $hintLine[1] . "\n";
}
$text .= " *\n";
}
if (!empty($hints['method']))
{
$text .= " * Filters:\n *\n";
$colWidth = 0;
$col2Width = 0;
foreach ($hints['method'] as $hintLine)
{
$colWidth = max($colWidth, strlen($hintLine[0]));
$col2Width = max($col2Width, strlen($hintLine[1]));
}
$colWidth += 2;
$col2Width += 2;
foreach ($hints['method'] as $hintLine)
{
$text .= " * @method " . str_pad($hintLine[0], $colWidth, ' ')
. str_pad($hintLine[1], $col2Width, ' ')
. $hintLine[2] . "\n";
}
$text .= " *\n";
}
if (!empty($hints['property-read']))
{
$text .= " * Relations:\n *\n";
$colWidth = 0;
foreach ($hints['property-read'] as $hintLine)
{
$colWidth = max($colWidth, strlen($hintLine[0]));
}
$colWidth += 2;
foreach ($hints['property-read'] as $hintLine)
{
$text .= " * @property " . str_pad($hintLine[0], $colWidth, ' ') . $hintLine[1] . "\n";
}
$text .= " *\n";
}
return $text . "**/\n";
}
}