<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 0.1.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Bake\Shell\Task;
use Cake\Console\Shell;
use Cake\Core\Configure;
use Cake\Database\Schema\TableSchema;
use Cake\Datasource\ConnectionManager;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Utility\Inflector;
use Cake\Validation\Validation;
/**
* Task class for generating model files.
*
* @property \Bake\Shell\Task\FixtureTask $Fixture
* @property \Bake\Shell\Task\BakeTemplateTask $BakeTemplate
* @property \Bake\Shell\Task\TestTask $Test
*/
class ModelTask extends BakeTask
{
/**
* path to Model directory
*
* @var string
*/
public $pathFragment = 'Model/';
/**
* tasks
*
* @var array
*/
public $tasks = [
'Bake.Fixture',
'Bake.BakeTemplate',
'Bake.Test',
];
/**
* Tables to skip when running all()
*
* @var array
*/
public $skipTables = ['i18n', 'cake_sessions', 'phinxlog', 'users_phinxlog'];
/**
* Holds tables found on connection.
*
* @var array
*/
protected $_tables = [];
/**
* Holds the model names
*
* @var array
*/
protected $_modelNames = [];
/**
* Holds validation method map.
*
* @var array
*/
protected $_validations = [];
/**
* Execution method always used for tasks
*
* @param string|null $name The name of the table to bake.
* @return void
*/
public function main($name = null)
{
parent::main();
$name = $this->_getName($name);
if (empty($name)) {
$this->out('Choose a model to bake from the following:');
foreach ($this->listUnskipped() as $table) {
$this->out('- ' . $this->_camelize($table));
}
return;
}
$this->bake($this->_camelize($name));
}
/**
* Generate code for the given model name.
*
* @param string $name The model name to generate.
* @return void
*/
public function bake($name)
{
$table = $this->getTable($name);
$tableObject = $this->getTableObject($name, $table);
$data = $this->getTableContext($tableObject, $table, $name);
$this->bakeTable($tableObject, $data);
$this->bakeEntity($tableObject, $data);
$this->bakeFixture($tableObject->getAlias(), $tableObject->getTable());
$this->bakeTest($tableObject->getAlias());
}
/**
* Get table context for baking a given table.
*
* @param \Cake\ORM\Table $tableObject The model name to generate.
* @param string $table The table name for the model being baked.
* @param string $name The model name to generate.
* @return array
*/
public function getTableContext($tableObject, $table, $name)
{
$associations = $this->getAssociations($tableObject);
$this->applyAssociations($tableObject, $associations);
$associationInfo = $this->getAssociationInfo($tableObject);
$primaryKey = $this->getPrimaryKey($tableObject);
$displayField = $this->getDisplayField($tableObject);
$propertySchema = $this->getEntityPropertySchema($tableObject);
$fields = $this->getFields($tableObject);
$validation = $this->getValidation($tableObject, $associations);
$rulesChecker = $this->getRules($tableObject, $associations);
$behaviors = $this->getBehaviors($tableObject);
$connection = $this->connection;
$hidden = $this->getHiddenFields($tableObject);
return compact(
'associations',
'associationInfo',
'primaryKey',
'displayField',
'table',
'propertySchema',
'fields',
'validation',
'rulesChecker',
'behaviors',
'connection',
'hidden'
);
}
/**
* Bake all models at once.
*
* @return void
*/
public function all()
{
$tables = $this->listUnskipped();
foreach ($tables as $table) {
TableRegistry::getTableLocator()->clear();
$this->main($table);
}
}
/**
* Get a model object for a class name.
*
* @param string $className Name of class you want model to be.
* @param string $table Table name
* @return \Cake\ORM\Table Table instance
*/
public function getTableObject($className, $table)
{
$plugin = $this->param('plugin');
if (!empty($plugin)) {
$className = $plugin . '.' . $className;
}
if (TableRegistry::getTableLocator()->exists($className)) {
return TableRegistry::getTableLocator()->get($className);
}
return TableRegistry::getTableLocator()->get($className, [
'name' => $className,
'table' => $this->tablePrefix . $table,
'connection' => ConnectionManager::get($this->connection),
]);
}
/**
* Get the array of associations to generate.
*
* @param \Cake\ORM\Table $table The table to get associations for.
* @return array
*/
public function getAssociations(Table $table)
{
if (!empty($this->params['no-associations'])) {
return [];
}
$this->out('One moment while associations are detected.');
$this->listAll();
$associations = [
'belongsTo' => [],
'hasMany' => [],
'belongsToMany' => [],
];
$primary = $table->getPrimaryKey();
$associations = $this->findBelongsTo($table, $associations);
if (is_array($primary) && count($primary) > 1) {
$this->err(
'<warning>Bake cannot generate associations for composite primary keys at this time</warning>.'
);
return $associations;
}
$associations = $this->findHasMany($table, $associations);
$associations = $this->findBelongsToMany($table, $associations);
return $associations;
}
/**
* Sync the in memory table object.
*
* Composer's class cache prevents us from loading the
* newly generated class. Applying associations if we have a
* generic table object means fields will be detected correctly.
*
* @param \Cake\ORM\Table $model The table to apply associations to.
* @param array $associations The associations to append.
* @return void
*/
public function applyAssociations($model, $associations)
{
if (get_class($model) !== 'Cake\ORM\Table') {
return;
}
foreach ($associations as $type => $assocs) {
foreach ($assocs as $assoc) {
$alias = $assoc['alias'];
unset($assoc['alias']);
$model->{$type}($alias, $assoc);
}
}
}
/**
* Collects meta information for associations.
*
* The information returned is in the format of map, where the key is the
* association alias:
*
* ```
* [
* 'associationAlias' => [
* 'targetFqn' => '...'
* ],
* // ...
* ]
* ```
*
* @param \Cake\ORM\Table $table The table from which to collect association information.
* @return array A map of association information.
*/
public function getAssociationInfo(Table $table)
{
$info = [];
$appNamespace = Configure::read('App.namespace');
foreach ($table->associations() as $association) {
/* @var $association \Cake\ORM\Association */
$tableClass = get_class($association->getTarget());
if ($tableClass === 'Cake\ORM\Table') {
$namespace = $appNamespace;
$className = $association->getClassName();
if (strlen($className)) {
list($plugin, $className) = pluginSplit($className);
if ($plugin !== null) {
$namespace = $plugin;
}
} else {
$className = $association->getTarget()->getAlias();
}
$namespace = str_replace('/', '\\', trim($namespace, '\\'));
$tableClass = $namespace . '\Model\Table\\' . $className . 'Table';
}
$info[$association->getName()] = [
'targetFqn' => '\\' . $tableClass,
];
}
return $info;
}
/**
* Find belongsTo relations and add them to the associations list.
*
* @param \Cake\ORM\Table $model Database\Table instance of table being generated.
* @param array $associations Array of in progress associations
* @return array Associations with belongsTo added in.
*/
public function findBelongsTo($model, array $associations)
{
$schema = $model->getSchema();
foreach ($schema->columns() as $fieldName) {
if (!preg_match('/^.+_id$/', $fieldName) || ([$fieldName] === $schema->primaryKey())) {
continue;
}
if ($fieldName === 'parent_id') {
$className = ($this->plugin) ? $this->plugin . '.' . $model->getAlias() : $model->getAlias();
$assoc = [
'alias' => 'Parent' . $model->getAlias(),
'className' => $className,
'foreignKey' => $fieldName,
];
} else {
$tmpModelName = $this->_modelNameFromKey($fieldName);
if (!in_array(Inflector::tableize($tmpModelName), $this->_tables)) {
$found = $this->findTableReferencedBy($schema, $fieldName);
if ($found) {
$tmpModelName = Inflector::camelize($found);
}
}
$assoc = [
'alias' => $tmpModelName,
'foreignKey' => $fieldName,
];
if ($schema->getColumn($fieldName)['null'] === false) {
$assoc['joinType'] = 'INNER';
}
}
if ($this->plugin && empty($assoc['className'])) {
$assoc['className'] = $this->plugin . '.' . $assoc['alias'];
}
$associations['belongsTo'][] = $assoc;
}
return $associations;
}
/**
* find the table, if any, actually referenced by the passed key field.
* Search tables in db for keyField; if found search key constraints
* for the table to which it refers.
*
* @param \Cake\Database\Schema\TableSchema $schema The table schema to find a constraint for.
* @param string $keyField The field to check for a constraint.
* @return string|null Either the referenced table or null if the field has no constraints.
*/
public function findTableReferencedBy($schema, $keyField)
{
if (!$schema->getColumn($keyField)) {
return null;
}
foreach ($schema->constraints() as $constraint) {
$constraintInfo = $schema->getConstraint($constraint);
if (!in_array($keyField, $constraintInfo['columns'])) {
continue;
}
if (!isset($constraintInfo['references'])) {
continue;
}
$length = mb_strlen($this->tablePrefix);
if ($length > 0 && mb_substr($constraintInfo['references'][0], 0, $length) === $this->tablePrefix) {
return mb_substr($constraintInfo['references'][0], $length);
}
return $constraintInfo['references'][0];
}
return null;
}
/**
* Find the hasMany relations and add them to associations list
*
* @param \Cake\ORM\Table $model Model instance being generated
* @param array $associations Array of in progress associations
* @return array Associations with hasMany added in.
*/
public function findHasMany($model, array $associations)
{
$schema = $model->getSchema();
$primaryKey = $schema->primaryKey();
$tableName = $schema->name();
$foreignKey = $this->_modelKey($tableName);
$tables = $this->listAll();
foreach ($tables as $otherTableName) {
$otherModel = $this->getTableObject($this->_camelize($otherTableName), $otherTableName);
$otherSchema = $otherModel->getSchema();
$pregTableName = preg_quote($tableName, '/');
$pregPattern = "/^{$pregTableName}_|_{$pregTableName}$/";
if (preg_match($pregPattern, $otherTableName) === 1) {
$possibleHABTMTargetTable = preg_replace($pregPattern, '', $otherTableName);
if (in_array($possibleHABTMTargetTable, $tables)) {
continue;
}
}
foreach ($otherSchema->columns() as $fieldName) {
$assoc = false;
if (!in_array($fieldName, $primaryKey) && $fieldName === $foreignKey) {
$assoc = [
'alias' => $otherModel->getAlias(),
'foreignKey' => $fieldName,
];
} elseif ($otherTableName === $tableName && $fieldName === 'parent_id') {
$className = ($this->plugin) ? $this->plugin . '.' . $model->getAlias() : $model->getAlias();
$assoc = [
'alias' => 'Child' . $model->getAlias(),
'className' => $className,
'foreignKey' => $fieldName,
];
}
if ($assoc && $this->plugin && empty($assoc['className'])) {
$assoc['className'] = $this->plugin . '.' . $assoc['alias'];
}
if ($assoc) {
$associations['hasMany'][] = $assoc;
}
}
}
return $associations;
}
/**
* Find the BelongsToMany relations and add them to associations list
*
* @param \Cake\ORM\Table $model Model instance being generated
* @param array $associations Array of in-progress associations
* @return array Associations with belongsToMany added in.
*/
public function findBelongsToMany($model, array $associations)
{
$schema = $model->getSchema();
$tableName = $schema->name();
$foreignKey = $this->_modelKey($tableName);
$tables = $this->listAll();
foreach ($tables as $otherTableName) {
$assocTable = null;
$offset = strpos($otherTableName, $tableName . '_');
$otherOffset = strpos($otherTableName, '_' . $tableName);
if ($offset !== false) {
$assocTable = substr($otherTableName, strlen($tableName . '_'));
} elseif ($otherOffset !== false) {
$assocTable = substr($otherTableName, 0, $otherOffset);
}
if ($assocTable && in_array($assocTable, $tables)) {
$habtmName = $this->_camelize($assocTable);
$assoc = [
'alias' => $habtmName,
'foreignKey' => $foreignKey,
'targetForeignKey' => $this->_modelKey($habtmName),
'joinTable' => $otherTableName,
];
if ($this->plugin) {
$assoc['className'] = $this->plugin . '.' . $assoc['alias'];
}
$associations['belongsToMany'][] = $assoc;
}
}
return $associations;
}
/**
* Get the display field from the model or parameters
*
* @param \Cake\ORM\Table $model The model to introspect.
* @return string
*/
public function getDisplayField($model)
{
if (!empty($this->params['display-field'])) {
return $this->params['display-field'];
}
return $model->getDisplayField();
}
/**
* Get the primary key field from the model or parameters
*
* @param \Cake\ORM\Table $model The model to introspect.
* @return array The columns in the primary key
*/
public function getPrimaryKey($model)
{
if (!empty($this->params['primary-key'])) {
$fields = explode(',', $this->params['primary-key']);
return array_values(array_filter(array_map('trim', $fields)));
}
return (array)$model->getPrimaryKey();
}
/**
* Returns an entity property "schema".
*
* The schema is an associative array, using the property names
* as keys, and information about the property as the value.
*
* The value part consists of at least two keys:
*
* - `kind`: The kind of property, either `column`, which indicates
* that the property stems from a database column, or `association`,
* which identifies a property that is generated for an associated
* table.
* - `type`: The type of the property value. For the `column` kind
* this is the database type associated with the column, and for the
* `association` type it's the FQN of the entity class for the
* associated table.
*
* For `association` properties an additional key will be available
*
* - `association`: Holds an instance of the corresponding association
* class.
*
* @param \Cake\ORM\Table $model The model to introspect.
* @return array The property schema
*/
public function getEntityPropertySchema(Table $model)
{
$properties = [];
$schema = $model->getSchema();
foreach ($schema->columns() as $column) {
$columnSchema = $schema->getColumn($column);
$properties[$column] = [
'kind' => 'column',
'type' => $columnSchema['type'],
'null' => $columnSchema['null'],
];
}
foreach ($model->associations() as $association) {
$entityClass = '\\' . ltrim($association->getTarget()->getEntityClass(), '\\');
if ($entityClass === '\Cake\ORM\Entity') {
$namespace = Configure::read('App.namespace');
list($plugin, ) = pluginSplit($association->getTarget()->getRegistryAlias());
if ($plugin !== null) {
$namespace = $plugin;
}
$namespace = str_replace('/', '\\', trim($namespace, '\\'));
$entityClass = $this->_entityName($association->getTarget()->getAlias());
$entityClass = '\\' . $namespace . '\Model\Entity\\' . $entityClass;
}
$properties[$association->getProperty()] = [
'kind' => 'association',
'association' => $association,
'type' => $entityClass,
];
}
return $properties;
}
/**
* Evaluates the fields and no-fields options, and
* returns if, and which fields should be made accessible.
*
* If no fields are specified and the `no-fields` parameter is
* not set, then all non-primary key fields + association
* fields will be set as accessible.
*
* @param \Cake\ORM\Table $table The table instance to get fields for.
* @return array|bool|null Either an array of fields, `false` in
* case the no-fields option is used, or `null` if none of the
* field options is used.
*/
public function getFields($table)
{
if (!empty($this->params['no-fields'])) {
return false;
}
if (!empty($this->params['fields'])) {
$fields = explode(',', $this->params['fields']);
return array_values(array_filter(array_map('trim', $fields)));
}
$schema = $table->getSchema();
$fields = $schema->columns();
foreach ($table->associations() as $assoc) {
$fields[] = $assoc->getProperty();
}
$primaryKey = $schema->primaryKey();
return array_values(array_diff($fields, $primaryKey));
}
/**
* Get the hidden fields from a model.
*
* Uses the hidden and no-hidden options.
*
* @param \Cake\ORM\Table $model The model to introspect.
* @return array The columns to make accessible
*/
public function getHiddenFields($model)
{
if (!empty($this->params['no-hidden'])) {
return [];
}
if (!empty($this->params['hidden'])) {
$fields = explode(',', $this->params['hidden']);
return array_values(array_filter(array_map('trim', $fields)));
}
$schema = $model->getSchema();
$columns = $schema->columns();
$whitelist = ['token', 'password', 'passwd'];
return array_values(array_intersect($columns, $whitelist));
}
/**
* Generate default validation rules.
*
* @param \Cake\ORM\Table $model The model to introspect.
* @param array $associations The associations list.
* @return array|false The validation rules.
*/
public function getValidation($model, $associations = [])
{
if (!empty($this->params['no-validation'])) {
return [];
}
$schema = $model->getSchema();
$fields = $schema->columns();
if (empty($fields)) {
return false;
}
$validate = [];
$primaryKey = $schema->primaryKey();
$foreignKeys = [];
if (isset($associations['belongsTo'])) {
foreach ($associations['belongsTo'] as $assoc) {
$foreignKeys[] = $assoc['foreignKey'];
}
}
foreach ($fields as $fieldName) {
if (in_array($fieldName, $foreignKeys)) {
continue;
}
$field = $schema->getColumn($fieldName);
$validation = $this->fieldValidation($schema, $fieldName, $field, $primaryKey);
if (!empty($validation)) {
$validate[$fieldName] = $validation;
}
}
return $validate;
}
/**
* Does individual field validation handling.
*
* @param \Cake\Database\Schema\TableSchema $schema The table schema for the current field.
* @param string $fieldName Name of field to be validated.
* @param array $metaData metadata for field
* @param array $primaryKey The primary key field
* @return array Array of validation for the field.
*/
public function fieldValidation($schema, $fieldName, array $metaData, $primaryKey)
{
$ignoreFields = ['lft', 'rght', 'created', 'modified', 'updated'];
if (in_array($fieldName, $ignoreFields)) {
return [];
}
$rules = [];
if ($fieldName === 'email') {
$rules['email'] = [];
} elseif ($metaData['type'] === 'uuid') {
$rules['uuid'] = [];
} elseif ($metaData['type'] === 'integer') {
if ($metaData['unsigned']) {
$rules['nonNegativeInteger'] = [];
} else {
$rules['integer'] = [];
}
} elseif ($metaData['type'] === 'float') {
$rules['numeric'] = [];
if ($metaData['unsigned']) {
$rules['greaterThanOrEqual'] = [
0,
];
}
} elseif ($metaData['type'] === 'decimal') {
$rules['decimal'] = [];
if ($metaData['unsigned']) {
$rules['greaterThanOrEqual'] = [
0,
];
}
} elseif ($metaData['type'] === 'boolean') {
$rules['boolean'] = [];
} elseif ($metaData['type'] === 'date') {
$rules['date'] = [];
} elseif ($metaData['type'] === 'time') {
$rules['time'] = [];
} elseif ($metaData['type'] === 'datetime') {
$rules['dateTime'] = [];
} elseif ($metaData['type'] === 'timestamp') {
$rules['dateTime'] = [];
} elseif ($metaData['type'] === 'inet') {
$rules['ip'] = [];
} elseif ($metaData['type'] === 'string' || $metaData['type'] === 'text') {
$rules['scalar'] = [];
if ($metaData['length'] > 0) {
$rules['maxLength'] = [$metaData['length']];
}
}
$validation = [];
foreach ($rules as $rule => $args) {
$validation[$rule] = [
'rule' => $rule,
'args' => $args,
];
}
if (in_array($fieldName, $primaryKey)) {
$validation['allowEmpty'] = [
'rule' => $this->getEmptyMethod($fieldName, $metaData),
'args' => ['null', "'create'"],
];
} elseif ($metaData['null'] === true) {
$validation['allowEmpty'] = [
'rule' => $this->getEmptyMethod($fieldName, $metaData),
'args' => [],
];
} else {
if ($metaData['default'] === null || $metaData['default'] === false) {
$validation['requirePresence'] = [
'rule' => 'requirePresence',
'args' => ["'create'"],
];
}
$validation['notEmpty'] = [
'rule' => $this->getEmptyMethod($fieldName, $metaData, 'not'),
'args' => [],
];
}
foreach ($schema->constraints() as $constraint) {
$constraint = $schema->getConstraint($constraint);
if (!in_array($fieldName, $constraint['columns']) || count($constraint['columns']) > 1) {
continue;
}
$notDatetime = !in_array($metaData['type'], ['datetime', 'timestamp', 'date', 'time']);
if ($constraint['type'] === TableSchema::CONSTRAINT_UNIQUE && $notDatetime) {
$validation['unique'] = ['rule' => 'validateUnique', 'provider' => 'table'];
}
}
return $validation;
}
/**
* Get the specific allow empty method for field based on metadata.
*
* @param string $fieldName Field name.
* @param array $metaData Field meta data.
* @param string $prefix Method name prefix.
* @return string
*/
protected function getEmptyMethod($fieldName, array $metaData, $prefix = 'allow')
{
switch ($metaData['type']) {
case 'date':
return $prefix . 'EmptyDate';
case 'time':
return $prefix . 'EmptyTime';
case 'datetime':
case 'timestamp':
return $prefix . 'EmptyDateTime';
}
if (preg_match('/file|image/', $fieldName)) {
return $prefix . 'EmptyFile';
}
return $prefix . 'EmptyString';
}
/**
* Generate default rules checker.
*
* @param \Cake\ORM\Table $model The model to introspect.
* @param array $associations The associations for the model.
* @return array The rules to be applied.
*/
public function getRules($model, array $associations)
{
if (!empty($this->params['no-rules'])) {
return [];
}
$schema = $model->getSchema();
$fields = $schema->columns();
if (empty($fields)) {
return [];
}
$rules = [];
foreach ($fields as $fieldName) {
if (in_array($fieldName, ['username', 'email', 'login'])) {
$rules[$fieldName] = ['name' => 'isUnique'];
}
}
foreach ($schema->constraints() as $name) {
$constraint = $schema->getConstraint($name);
if ($constraint['type'] !== TableSchema::CONSTRAINT_UNIQUE) {
continue;
}
if (count($constraint['columns']) > 1) {
continue;
}
$rules[$constraint['columns'][0]] = ['name' => 'isUnique'];
}
if (empty($associations['belongsTo'])) {
return $rules;
}
foreach ($associations['belongsTo'] as $assoc) {
$rules[$assoc['foreignKey']] = ['name' => 'existsIn', 'extra' => $assoc['alias']];
}
return $rules;
}
/**
* Get behaviors
*
* @param \Cake\ORM\Table $model The model to generate behaviors for.
* @return array Behaviors
*/
public function getBehaviors($model)
{
$behaviors = [];
$schema = $model->getSchema();
$fields = $schema->columns();
if (empty($fields)) {
return [];
}
if (in_array('created', $fields) || in_array('modified', $fields)) {
$behaviors['Timestamp'] = [];
}
if (
in_array('lft', $fields) && $schema->getColumnType('lft') === 'integer' &&
in_array('rght', $fields) && $schema->getColumnType('rght') === 'integer' &&
in_array('parent_id', $fields)
) {
$behaviors['Tree'] = [];
}
$counterCache = $this->getCounterCache($model);
if (!empty($counterCache)) {
$behaviors['CounterCache'] = $counterCache;
}
return $behaviors;
}
/**
* Get CounterCaches
*
* @param \Cake\ORM\Table $model The table to get counter cache fields for.
* @return array CounterCache configurations
*/
public function getCounterCache($model)
{
$belongsTo = $this->findBelongsTo($model, ['belongsTo' => []]);
$counterCache = [];
foreach ($belongsTo['belongsTo'] as $otherTable) {
$otherAlias = $otherTable['alias'];
$otherModel = $this->getTableObject($this->_camelize($otherAlias), Inflector::underscore($otherAlias));
try {
$otherSchema = $otherModel->getSchema();
} catch (\Cake\Database\Exception $e) {
continue;
}
$otherFields = $otherSchema->columns();
$alias = $model->getAlias();
$field = Inflector::singularize(Inflector::underscore($alias)) . '_count';
if (in_array($field, $otherFields, true)) {
$counterCache[] = "'{$otherAlias}' => ['{$field}']";
}
}
return $counterCache;
}
/**
* Bake an entity class.
*
* @param \Cake\ORM\Table $model Model name or object
* @param array $data An array to use to generate the Table
* @return string|null
*/
public function bakeEntity($model, array $data = [])
{
if (!empty($this->params['no-entity'])) {
return null;
}
$name = $this->_entityName($model->getAlias());
$namespace = Configure::read('App.namespace');
$pluginPath = '';
if ($this->plugin) {
$namespace = $this->_pluginNamespace($this->plugin);
$pluginPath = $this->plugin . '.';
}
$data += [
'name' => $name,
'namespace' => $namespace,
'plugin' => $this->plugin,
'pluginPath' => $pluginPath,
'primaryKey' => [],
];
$this->BakeTemplate->set($data);
$out = $this->BakeTemplate->generate('Model/entity');
$path = $this->getPath();
$filename = $path . 'Entity' . DS . $name . '.php';
$this->out("\n" . sprintf('Baking entity class for %s...', $name), 1, Shell::QUIET);
$this->createFile($filename, $out);
$emptyFile = $path . 'Entity' . DS . 'empty';
$this->_deleteEmptyFile($emptyFile);
return $out;
}
/**
* Bake a table class.
*
* @param \Cake\ORM\Table $model Model name or object
* @param array $data An array to use to generate the Table
* @return string|null
*/
public function bakeTable($model, array $data = [])
{
if (!empty($this->params['no-table'])) {
return null;
}
$namespace = Configure::read('App.namespace');
$pluginPath = '';
if ($this->plugin) {
$namespace = $this->_pluginNamespace($this->plugin);
}
$name = $model->getAlias();
$entity = $this->_entityName($model->getAlias());
$data += [
'plugin' => $this->plugin,
'pluginPath' => $pluginPath,
'namespace' => $namespace,
'name' => $name,
'entity' => $entity,
'associations' => [],
'primaryKey' => 'id',
'displayField' => null,
'table' => null,
'validation' => [],
'rulesChecker' => [],
'behaviors' => [],
'connection' => $this->connection,
];
$this->BakeTemplate->set($data);
$out = $this->BakeTemplate->generate('Model/table');
$path = $this->getPath();
$filename = $path . 'Table' . DS . $name . 'Table.php';
$this->out("\n" . sprintf('Baking table class for %s...', $name), 1, Shell::QUIET);
$this->createFile($filename, $out);
// Work around composer caching that classes/files do not exist.
// Check for the file as it might not exist in tests.
if (file_exists($filename)) {
require_once $filename;
}
TableRegistry::getTableLocator()->clear();
$emptyFile = $path . 'Table' . DS . 'empty';
$this->_deleteEmptyFile($emptyFile);
return $out;
}
/**
* Outputs the a list of possible models or controllers from database
*
* @return array
*/
public function listAll()
{
if (!empty($this->_tables)) {
return $this->_tables;
}
$this->_modelNames = [];
$this->_tables = $this->_getAllTables();
foreach ($this->_tables as $table) {
$this->_modelNames[] = $this->_camelize($table);
}
return $this->_tables;
}
/**
* Outputs the a list of unskipped models or controllers from database
*
* @return array
*/
public function listUnskipped()
{
$this->listAll();
return array_diff($this->_tables, $this->skipTables);
}
/**
* Models never have routing prefixes applied.
*
* @return string
*/
protected function _getPrefix()
{
return '';
}
/**
* Get an Array of all the tables in the supplied connection
* will halt the script if no tables are found.
*
* @return array Array of tables in the database.
* @throws \InvalidArgumentException When connection class
* does not have a schemaCollection method.
*/
protected function _getAllTables()
{
$db = ConnectionManager::get($this->connection);
if (!method_exists($db, 'schemaCollection')) {
$this->abort(
'Connections need to implement schemaCollection() to be used with bake.'
);
}
$schema = $db->getSchemaCollection();
$tables = $schema->listTables();
if (empty($tables)) {
$this->abort('Your database does not have any tables.');
}
sort($tables);
return $tables;
}
/**
* Get the table name for the model being baked.
*
* Uses the `table` option if it is set.
*
* @param string $name Table name
* @return string
*/
public function getTable($name)
{
if (isset($this->params['table'])) {
return $this->params['table'];
}
return Inflector::underscore($name);
}
/**
* Gets the option parser instance and configures it.
*
* @return \Cake\Console\ConsoleOptionParser
*/
public function getOptionParser()
{
$parser = parent::getOptionParser();
$parser->setDescription(
'Bake table and entity classes.'
)->addArgument('name', [
'help' => 'Name of the model to bake (without the Table suffix). ' .
'You can use Plugin.name to bake plugin models.',
])->addSubcommand('all', [
'help' => 'Bake all model files with associations and validation.',
])->addOption('table', [
'help' => 'The table name to use if you have non-conventional table names.',
])->addOption('no-entity', [
'boolean' => true,
'help' => 'Disable generating an entity class.',
])->addOption('no-table', [
'boolean' => true,
'help' => 'Disable generating a table class.',
])->addOption('no-validation', [
'boolean' => true,
'help' => 'Disable generating validation rules.',
])->addOption('no-rules', [
'boolean' => true,
'help' => 'Disable generating a rules checker.',
])->addOption('no-associations', [
'boolean' => true,
'help' => 'Disable generating associations.',
])->addOption('no-fields', [
'boolean' => true,
'help' => 'Disable generating accessible fields in the entity.',
])->addOption('fields', [
'help' => 'A comma separated list of fields to make accessible.',
])->addOption('no-hidden', [
'boolean' => true,
'help' => 'Disable generating hidden fields in the entity.',
])->addOption('hidden', [
'help' => 'A comma separated list of fields to hide.',
])->addOption('primary-key', [
'help' => 'The primary key if you would like to manually set one.' .
' Can be a comma separated list if you are using a composite primary key.',
])->addOption('display-field', [
'help' => 'The displayField if you would like to choose one.',
])->addOption('no-test', [
'boolean' => true,
'help' => 'Do not generate a test case skeleton.',
])->addOption('no-fixture', [
'boolean' => true,
'help' => 'Do not generate a test fixture skeleton.',
])->setEpilog(
'Omitting all arguments and options will list the table names you can generate models for'
);
return $parser;
}
/**
* Interact with FixtureTask to automatically bake fixtures when baking models.
*
* @param string $className Name of class to bake fixture for
* @param string|null $useTable Optional table name for fixture to use.
* @return void
* @see FixtureTask::bake
*/
public function bakeFixture($className, $useTable = null)
{
if (!empty($this->params['no-fixture'])) {
return;
}
$this->Fixture->connection = $this->connection;
$this->Fixture->plugin = $this->plugin;
$this->Fixture->interactive = $this->interactive;
$this->Fixture->bake($className, $useTable);
}
/**
* Assembles and writes a unit test file
*
* @param string $className Model class name
* @return string|false
*/
public function bakeTest($className)
{
if (!empty($this->params['no-test'])) {
return false;
}
$this->Test->plugin = $this->plugin;
$this->Test->interactive = $this->interactive;
$this->Test->connection = $this->connection;
return $this->Test->bake('Table', $className);
}
}