Blame | Last modification | View Log | RSS feed
<?php/*** The FixtureTask handles creating and updating fixture files.** 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 CakePHP(tm) v 1.3* @license http://www.opensource.org/licenses/mit-license.php MIT License*/App::uses('AppShell', 'Console/Command');App::uses('BakeTask', 'Console/Command/Task');App::uses('Model', 'Model');/*** Task class for creating and updating fixtures files.** @package Cake.Console.Command.Task*/class FixtureTask extends BakeTask {/*** Tasks to be loaded by this Task** @var array*/public $tasks = array('DbConfig', 'Model', 'Template');/*** path to fixtures directory** @var string*/public $path = null;/*** Schema instance** @var CakeSchema*/protected $_Schema = null;/*** Override initialize** @param ConsoleOutput $stdout A ConsoleOutput object for stdout.* @param ConsoleOutput $stderr A ConsoleOutput object for stderr.* @param ConsoleInput $stdin A ConsoleInput object for stdin.*/public function __construct($stdout = null, $stderr = null, $stdin = null) {parent::__construct($stdout, $stderr, $stdin);$this->path = APP . 'Test' . DS . 'Fixture' . DS;}/*** get the option parser.** @return void*/public function getOptionParser() {$parser = parent::getOptionParser();return $parser->description(__d('cake_console', 'Generate fixtures for use with the test suite. You can use `bake fixture all` to bake all fixtures.'))->addArgument('name', array('help' => __d('cake_console', 'Name of the fixture to bake. Can use Plugin.name to bake plugin fixtures.')))->addOption('count', array('help' => __d('cake_console', 'When using generated data, the number of records to include in the fixture(s).'),'short' => 'n','default' => 10))->addOption('connection', array('help' => __d('cake_console', 'Which database configuration to use for baking.'),'short' => 'c','default' => 'default'))->addOption('plugin', array('help' => __d('cake_console', 'CamelCased name of the plugin to bake fixtures for.'),'short' => 'p',))->addOption('schema', array('help' => __d('cake_console', 'Importing schema for fixtures rather than hardcoding it.'),'short' => 's','boolean' => true))->addOption('theme', array('short' => 't','help' => __d('cake_console', 'Theme to use when baking code.')))->addOption('force', array('short' => 'f','help' => __d('cake_console', 'Force overwriting existing files without prompting.')))->addOption('records', array('help' => __d('cake_console', 'Used with --count and <name>/all commands to pull [n] records from the live tables, where [n] is either --count or the default of 10.'),'short' => 'r','boolean' => true))->epilog(__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.'));}/*** Execution method always used for tasks* Handles dispatching to interactive, named, or all processes.** @return void*/public function execute() {parent::execute();if (empty($this->args)) {$this->_interactive();}if (isset($this->args[0])) {$this->interactive = false;if (!isset($this->connection)) {$this->connection = 'default';}if (strtolower($this->args[0]) === 'all') {return $this->all();}$model = $this->_modelName($this->args[0]);$this->bake($model);}}/*** Bake All the Fixtures at once. Will only bake fixtures for models that exist.** @return void*/public function all() {$this->interactive = false;$this->Model->interactive = false;$tables = $this->Model->listAll($this->connection, false);foreach ($tables as $table) {$model = $this->_modelName($table);$importOptions = array();if (!empty($this->params['schema'])) {$importOptions['schema'] = $model;}$this->bake($model, false, $importOptions);}}/*** Interactive baking function** @return void*/protected function _interactive() {$this->DbConfig->interactive = $this->Model->interactive = $this->interactive = true;$this->hr();$this->out(__d('cake_console', "Bake Fixture\nPath: %s", $this->getPath()));$this->hr();if (!isset($this->connection)) {$this->connection = $this->DbConfig->getConfig();}$modelName = $this->Model->getName($this->connection);$useTable = $this->Model->getTable($modelName, $this->connection);$importOptions = $this->importOptions($modelName);$this->bake($modelName, $useTable, $importOptions);}/*** Interacts with the User to setup an array of import options. For a fixture.** @param string $modelName Name of model you are dealing with.* @return array Array of import options.*/public function importOptions($modelName) {$options = array();if (!empty($this->params['schema'])) {$options['schema'] = $modelName;} else {$doSchema = $this->in(__d('cake_console', 'Would you like to import schema for this fixture?'), array('y', 'n'), 'n');if ($doSchema === 'y') {$options['schema'] = $modelName;}}if (!empty($this->params['records'])) {$doRecords = 'y';} else {$doRecords = $this->in(__d('cake_console', 'Would you like to use record importing for this fixture?'), array('y', 'n'), 'n');}if ($doRecords === 'y') {$options['records'] = true;}if ($doRecords === 'n') {$prompt = __d('cake_console', "Would you like to build this fixture with data from %s's table?", $modelName);$fromTable = $this->in($prompt, array('y', 'n'), 'n');if (strtolower($fromTable) === 'y') {$options['fromTable'] = true;}}return $options;}/*** Assembles and writes a Fixture file** @param string $model Name of model to bake.* @param string $useTable Name of table to use.* @param array $importOptions Options for public $import* @return string Baked fixture content*/public function bake($model, $useTable = false, $importOptions = array()) {App::uses('CakeSchema', 'Model');$table = $schema = $records = $import = $modelImport = null;$importBits = array();if (!$useTable) {$useTable = Inflector::tableize($model);} elseif ($useTable != Inflector::tableize($model)) {$table = $useTable;}if (!empty($importOptions)) {if (isset($importOptions['schema'])) {$modelImport = true;$importBits[] = "'model' => '{$importOptions['schema']}'";}if (isset($importOptions['records'])) {$importBits[] = "'records' => true";}if ($this->connection !== 'default') {$importBits[] .= "'connection' => '{$this->connection}'";}if (!empty($importBits)) {$import = sprintf("array(%s)", implode(', ', $importBits));}}$this->_Schema = new CakeSchema();$data = $this->_Schema->read(array('models' => false, 'connection' => $this->connection));if (!isset($data['tables'][$useTable])) {$this->error('Could not find your selected table ' . $useTable);return false;}$tableInfo = $data['tables'][$useTable];if ($modelImport === null) {$schema = $this->_generateSchema($tableInfo);}if (empty($importOptions['records']) && !isset($importOptions['fromTable'])) {$recordCount = 1;if (isset($this->params['count'])) {$recordCount = $this->params['count'];}$records = $this->_makeRecordString($this->_generateRecords($tableInfo, $recordCount));}if (!empty($this->params['records']) || isset($importOptions['fromTable'])) {$records = $this->_makeRecordString($this->_getRecordsFromTable($model, $useTable));}$out = $this->generateFixtureFile($model, compact('records', 'table', 'schema', 'import'));return $out;}/*** Generate the fixture file, and write to disk** @param string $model name of the model being generated* @param string $otherVars Contents of the fixture file.* @return string Content saved into fixture file.*/public function generateFixtureFile($model, $otherVars) {$defaults = array('table' => null, 'schema' => null, 'records' => null, 'import' => null, 'fields' => null);$vars = array_merge($defaults, $otherVars);$path = $this->getPath();$filename = Inflector::camelize($model) . 'Fixture.php';$this->Template->set('model', $model);$this->Template->set($vars);$content = $this->Template->generate('classes', 'fixture');$this->out("\n" . __d('cake_console', 'Baking test fixture for %s...', $model), 1, Shell::QUIET);$this->createFile($path . $filename, $content);return $content;}/*** Get the path to the fixtures.** @return string Path for the fixtures*/public function getPath() {$path = $this->path;if (isset($this->plugin)) {$path = $this->_pluginPath($this->plugin) . 'Test' . DS . 'Fixture' . DS;}return $path;}/*** Generates a string representation of a schema.** @param array $tableInfo Table schema array* @return string fields definitions*/protected function _generateSchema($tableInfo) {$schema = trim($this->_Schema->generateTable('f', $tableInfo), "\n");return substr($schema, 13, -1);}/*** Generate String representation of Records** @param array $tableInfo Table schema array* @param integer $recordCount* @return array Array of records to use in the fixture.*/protected function _generateRecords($tableInfo, $recordCount = 1) {$records = array();for ($i = 0; $i < $recordCount; $i++) {$record = array();foreach ($tableInfo as $field => $fieldInfo) {if (empty($fieldInfo['type'])) {continue;}$insert = '';switch ($fieldInfo['type']) {case 'integer':case 'float':$insert = $i + 1;break;case 'string':case 'binary':$isPrimaryUuid = (isset($fieldInfo['key']) && strtolower($fieldInfo['key']) === 'primary' &&isset($fieldInfo['length']) && $fieldInfo['length'] == 36);if ($isPrimaryUuid) {$insert = String::uuid();} else {$insert = "Lorem ipsum dolor sit amet";if (!empty($fieldInfo['length'])) {$insert = substr($insert, 0, (int)$fieldInfo['length'] - 2);}}break;case 'timestamp':$insert = time();break;case 'datetime':$insert = date('Y-m-d H:i:s');break;case 'date':$insert = date('Y-m-d');break;case 'time':$insert = date('H:i:s');break;case 'boolean':$insert = 1;break;case 'text':$insert = "Lorem ipsum dolor sit amet, aliquet feugiat.";$insert .= " Convallis morbi fringilla gravida,";$insert .= " phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin";$insert .= " venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla";$insert .= " vestibulum massa neque ut et, id hendrerit sit,";$insert .= " feugiat in taciti enim proin nibh, tempor dignissim, rhoncus";$insert .= " duis vestibulum nunc mattis convallis.";break;}$record[$field] = $insert;}$records[] = $record;}return $records;}/*** Convert a $records array into a a string.** @param array $records Array of records to be converted to string* @return string A string value of the $records array.*/protected function _makeRecordString($records) {$out = "array(\n";foreach ($records as $record) {$values = array();foreach ($record as $field => $value) {$val = var_export($value, true);if ($val === 'NULL') {$val = 'null';}$values[] = "\t\t\t'$field' => $val";}$out .= "\t\tarray(\n";$out .= implode(",\n", $values);$out .= "\n\t\t),\n";}$out .= "\t)";return $out;}/*** Interact with the user to get a custom SQL condition and use that to extract data* to build a fixture.** @param string $modelName name of the model to take records from.* @param string $useTable Name of table to use.* @return array Array of records.*/protected function _getRecordsFromTable($modelName, $useTable = null) {if ($this->interactive) {$condition = null;$prompt = __d('cake_console', "Please provide a SQL fragment to use as conditions\nExample: WHERE 1=1");while (!$condition) {$condition = $this->in($prompt, null, 'WHERE 1=1');}$prompt = __d('cake_console', "How many records do you want to import?");$recordCount = $this->in($prompt, null, 10);} else {$condition = 'WHERE 1=1';$recordCount = (isset($this->params['count']) ? $this->params['count'] : 10);}$modelObject = new Model(array('name' => $modelName, 'table' => $useTable, 'ds' => $this->connection));$records = $modelObject->find('all', array('conditions' => $condition,'recursive' => -1,'limit' => $recordCount));$schema = $modelObject->schema(true);$out = array();foreach ($records as $record) {$row = array();foreach ($record[$modelObject->alias] as $field => $value) {if ($schema[$field]['type'] === 'boolean') {$value = (int)(bool)$value;}$row[$field] = $value;}$out[] = $row;}return $out;}}