<?php /** * @package FOF * @copyright 2010-2017 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU GPL version 2 or later */ namespace FOF30\Utils; use FOF30\Model\DataModel; /** * Generate phpDoc type hints for the magic fields of your DataModels * * @package FOF30\Utils */ class ModelTypeHints { /** * The model for which to create type hints * * @var DataModel */ protected $model = null; /** * Name of the class. If empty will be inferred from the current object * * @var string */ protected $className = null; /** * @param string $className */ public function setClassName($className) { $this->className = $className; } /** * Public constructor * * @param \FOF30\Model\DataModel $model The model to create hints for */ public function __construct(DataModel $model) { $this->model = $model; $this->className = get_class($model); } /** * Return the raw hints array * * @return array * * @throws \FOF30\Model\DataModel\Relation\Exception\RelationNotFound */ public function getRawHints() { $model = $this->model; $hints = array( 'property' => array(), 'method' => array(), 'property-read' => array() ); $hasFilters = $model->getBehavioursDispatcher()->hasObserverClass('FOF30\Model\DataModel\Behaviour\Filters'); $magicFields = array( 'enabled', 'ordering', 'created_on', 'created_by', 'modified_on', 'modified_by', 'locked_on', 'locked_by', ); foreach ($model->getTableFields() as $fieldName => $fieldMeta) { $fieldType = $this->getFieldType($fieldMeta->Type); if (!in_array($fieldName, $magicFields)) { $hints['property'][] = array($fieldType, '$' . $fieldName); } if ($hasFilters) { $hints['method'][] = array( '$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'][] = array( $relationType, '$' . $relationName ); } } return $hints; } /** * Returns the docblock with the magic field hints for the model class * * @return string */ public function getHints() { $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"; } $text .= "**/\n"; return $text; } /** * 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($type) { // Remove parentheses, indicating field options / size (they don't matter in type detection) if (!empty($type)) { list($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)) { list ($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; } }