CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.3 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.3
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Acl
        • Auth
    • Core
    • Error
    • Event
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
      • Validator
    • Network
      • Email
      • Http
    • Routing
      • Filter
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper

Classes

  • BakeTask
  • ControllerTask
  • DbConfigTask
  • ExtractTask
  • FixtureTask
  • ModelTask
  • PluginTask
  • ProjectTask
  • TemplateTask
  • TestTask
  • ViewTask
  1: <?php
  2: /**
  3:  * The TestTask handles creating and updating test files.
  4:  *
  5:  * PHP 5
  6:  *
  7:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8:  * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  9:  *
 10:  * Licensed under The MIT License
 11:  * For full copyright and license information, please see the LICENSE.txt
 12:  * Redistributions of files must retain the above copyright notice.
 13:  *
 14:  * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 15:  * @link          http://cakephp.org CakePHP(tm) Project
 16:  * @since         CakePHP(tm) v 1.3
 17:  * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 18:  */
 19: 
 20: App::uses('AppShell', 'Console/Command');
 21: App::uses('BakeTask', 'Console/Command/Task');
 22: App::uses('ClassRegistry', 'Utility');
 23: 
 24: /**
 25:  * Task class for creating and updating test files.
 26:  *
 27:  * @package       Cake.Console.Command.Task
 28:  */
 29: class TestTask extends BakeTask {
 30: 
 31: /**
 32:  * path to TESTS directory
 33:  *
 34:  * @var string
 35:  */
 36:     public $path = TESTS;
 37: 
 38: /**
 39:  * Tasks used.
 40:  *
 41:  * @var array
 42:  */
 43:     public $tasks = array('Template');
 44: 
 45: /**
 46:  * class types that methods can be generated for
 47:  *
 48:  * @var array
 49:  */
 50:     public $classTypes = array(
 51:         'Model' => 'Model',
 52:         'Controller' => 'Controller',
 53:         'Component' => 'Controller/Component',
 54:         'Behavior' => 'Model/Behavior',
 55:         'Helper' => 'View/Helper'
 56:     );
 57: 
 58: /**
 59:  * Mapping between packages, and their baseclass + package.
 60:  * This is used to generate App::uses() call to autoload base
 61:  * classes if a developer has forgotten to do so.
 62:  *
 63:  * @var array
 64:  */
 65:     public $baseTypes = array(
 66:         'Model' => array('Model', 'Model'),
 67:         'Behavior' => array('ModelBehavior', 'Model'),
 68:         'Controller' => array('Controller', 'Controller'),
 69:         'Component' => array('Component', 'Controller'),
 70:         'Helper' => array('Helper', 'View')
 71:     );
 72: 
 73: /**
 74:  * Internal list of fixtures that have been added so far.
 75:  *
 76:  * @var array
 77:  */
 78:     protected $_fixtures = array();
 79: 
 80: /**
 81:  * Execution method always used for tasks
 82:  *
 83:  * @return void
 84:  */
 85:     public function execute() {
 86:         parent::execute();
 87:         $count = count($this->args);
 88:         if (!$count) {
 89:             $this->_interactive();
 90:         }
 91: 
 92:         if ($count === 1) {
 93:             $this->_interactive($this->args[0]);
 94:         }
 95: 
 96:         if ($count > 1) {
 97:             $type = Inflector::classify($this->args[0]);
 98:             if ($this->bake($type, $this->args[1])) {
 99:                 $this->out('<success>Done</success>');
100:             }
101:         }
102:     }
103: 
104: /**
105:  * Handles interactive baking
106:  *
107:  * @param string $type
108:  * @return string|boolean
109:  */
110:     protected function _interactive($type = null) {
111:         $this->interactive = true;
112:         $this->hr();
113:         $this->out(__d('cake_console', 'Bake Tests'));
114:         $this->out(__d('cake_console', 'Path: %s', $this->getPath()));
115:         $this->hr();
116: 
117:         if ($type) {
118:             $type = Inflector::camelize($type);
119:             if (!isset($this->classTypes[$type])) {
120:                 $this->error(__d('cake_console', 'Incorrect type provided. Please choose one of %s', implode(', ', array_keys($this->classTypes))));
121:             }
122:         } else {
123:             $type = $this->getObjectType();
124:         }
125:         $className = $this->getClassName($type);
126:         return $this->bake($type, $className);
127:     }
128: 
129: /**
130:  * Completes final steps for generating data to create test case.
131:  *
132:  * @param string $type Type of object to bake test case for ie. Model, Controller
133:  * @param string $className the 'cake name' for the class ie. Posts for the PostsController
134:  * @return string|boolean
135:  */
136:     public function bake($type, $className) {
137:         $plugin = null;
138:         if ($this->plugin) {
139:             $plugin = $this->plugin . '.';
140:         }
141: 
142:         $realType = $this->mapType($type, $plugin);
143:         $fullClassName = $this->getRealClassName($type, $className);
144: 
145:         if ($this->typeCanDetectFixtures($type) && $this->isLoadableClass($realType, $fullClassName)) {
146:             $this->out(__d('cake_console', 'Bake is detecting possible fixtures...'));
147:             $testSubject = $this->buildTestSubject($type, $className);
148:             $this->generateFixtureList($testSubject);
149:         } elseif ($this->interactive) {
150:             $this->getUserFixtures();
151:         }
152:         list($baseClass, $baseType) = $this->getBaseType($type);
153:         App::uses($baseClass, $baseType);
154:         App::uses($fullClassName, $realType);
155: 
156:         $methods = array();
157:         if (class_exists($fullClassName)) {
158:             $methods = $this->getTestableMethods($fullClassName);
159:         }
160:         $mock = $this->hasMockClass($type, $fullClassName);
161:         list($preConstruct, $construction, $postConstruct) = $this->generateConstructor($type, $fullClassName, $plugin);
162:         $uses = $this->generateUses($type, $realType, $fullClassName);
163: 
164:         $this->out("\n" . __d('cake_console', 'Baking test case for %s %s ...', $className, $type), 1, Shell::QUIET);
165: 
166:         $this->Template->set('fixtures', $this->_fixtures);
167:         $this->Template->set('plugin', $plugin);
168:         $this->Template->set(compact(
169:             'className', 'methods', 'type', 'fullClassName', 'mock',
170:             'realType', 'preConstruct', 'postConstruct', 'construction',
171:             'uses'
172:         ));
173:         $out = $this->Template->generate('classes', 'test');
174: 
175:         $filename = $this->testCaseFileName($type, $className);
176:         $made = $this->createFile($filename, $out);
177:         if ($made) {
178:             return $out;
179:         }
180:         return false;
181:     }
182: 
183: /**
184:  * Interact with the user and get their chosen type. Can exit the script.
185:  *
186:  * @return string Users chosen type.
187:  */
188:     public function getObjectType() {
189:         $this->hr();
190:         $this->out(__d('cake_console', 'Select an object type:'));
191:         $this->hr();
192: 
193:         $keys = array();
194:         $i = 0;
195:         foreach ($this->classTypes as $option => $package) {
196:             $this->out(++$i . '. ' . $option);
197:             $keys[] = $i;
198:         }
199:         $keys[] = 'q';
200:         $selection = $this->in(__d('cake_console', 'Enter the type of object to bake a test for or (q)uit'), $keys, 'q');
201:         if ($selection === 'q') {
202:             return $this->_stop();
203:         }
204:         $types = array_keys($this->classTypes);
205:         return $types[$selection - 1];
206:     }
207: 
208: /**
209:  * Get the user chosen Class name for the chosen type
210:  *
211:  * @param string $objectType Type of object to list classes for i.e. Model, Controller.
212:  * @return string Class name the user chose.
213:  */
214:     public function getClassName($objectType) {
215:         $type = ucfirst(strtolower($objectType));
216:         $typeLength = strlen($type);
217:         $type = $this->classTypes[$type];
218:         if ($this->plugin) {
219:             $plugin = $this->plugin . '.';
220:             $options = App::objects($plugin . $type);
221:         } else {
222:             $options = App::objects($type);
223:         }
224:         $this->out(__d('cake_console', 'Choose a %s class', $objectType));
225:         $keys = array();
226:         foreach ($options as $key => $option) {
227:             $this->out(++$key . '. ' . $option);
228:             $keys[] = $key;
229:         }
230:         while (empty($selection)) {
231:             $selection = $this->in(__d('cake_console', 'Choose an existing class, or enter the name of a class that does not exist'));
232:             if (is_numeric($selection) && isset($options[$selection - 1])) {
233:                 $selection = $options[$selection - 1];
234:             }
235:             if ($type !== 'Model') {
236:                 $selection = substr($selection, 0, $typeLength * - 1);
237:             }
238:         }
239:         return $selection;
240:     }
241: 
242: /**
243:  * Checks whether the chosen type can find its own fixtures.
244:  * Currently only model, and controller are supported
245:  *
246:  * @param string $type The Type of object you are generating tests for eg. controller
247:  * @return boolean
248:  */
249:     public function typeCanDetectFixtures($type) {
250:         $type = strtolower($type);
251:         return in_array($type, array('controller', 'model'));
252:     }
253: 
254: /**
255:  * Check if a class with the given package is loaded or can be loaded.
256:  *
257:  * @param string $package The package of object you are generating tests for eg. controller
258:  * @param string $class the Classname of the class the test is being generated for.
259:  * @return boolean
260:  */
261:     public function isLoadableClass($package, $class) {
262:         App::uses($class, $package);
263:         list($plugin, $ns) = pluginSplit($package);
264:         if ($plugin) {
265:             App::uses("{$plugin}AppController", $package);
266:             App::uses("{$plugin}AppModel", $package);
267:             App::uses("{$plugin}AppHelper", $package);
268:         }
269:         return class_exists($class);
270:     }
271: 
272: /**
273:  * Construct an instance of the class to be tested.
274:  * So that fixtures can be detected
275:  *
276:  * @param string $type The Type of object you are generating tests for eg. controller
277:  * @param string $class the Classname of the class the test is being generated for.
278:  * @return object And instance of the class that is going to be tested.
279:  */
280:     public function buildTestSubject($type, $class) {
281:         ClassRegistry::flush();
282:         App::import($type, $class);
283:         $class = $this->getRealClassName($type, $class);
284:         if (strtolower($type) === 'model') {
285:             $instance = ClassRegistry::init($class);
286:         } else {
287:             $instance = new $class();
288:         }
289:         return $instance;
290:     }
291: 
292: /**
293:  * Gets the real class name from the cake short form. If the class name is already
294:  * suffixed with the type, the type will not be duplicated.
295:  *
296:  * @param string $type The Type of object you are generating tests for eg. controller
297:  * @param string $class the Classname of the class the test is being generated for.
298:  * @return string Real classname
299:  */
300:     public function getRealClassName($type, $class) {
301:         if (strtolower($type) === 'model' || empty($this->classTypes[$type])) {
302:             return $class;
303:         }
304: 
305:         $position = strpos($class, $type);
306: 
307:         if ($position !== false && strlen($class) - $position == strlen($type)) {
308:             return $class;
309:         }
310:         return $class . $type;
311:     }
312: 
313: /**
314:  * Map the types that TestTask uses to concrete types that App::uses can use.
315:  *
316:  * @param string $type The type of thing having a test generated.
317:  * @param string $plugin The plugin name.
318:  * @return string
319:  * @throws CakeException When invalid object types are requested.
320:  */
321:     public function mapType($type, $plugin) {
322:         $type = ucfirst($type);
323:         if (empty($this->classTypes[$type])) {
324:             throw new CakeException(__d('cake_dev', 'Invalid object type.'));
325:         }
326:         $real = $this->classTypes[$type];
327:         if ($plugin) {
328:             $real = trim($plugin, '.') . '.' . $real;
329:         }
330:         return $real;
331:     }
332: 
333: /**
334:  * Get the base class and package name for a given type.
335:  *
336:  * @param string $type The type the class having a test
337:  *   generated for is in.
338:  * @return array Array of (class, type)
339:  * @throws CakeException on invalid types.
340:  */
341:     public function getBaseType($type) {
342:         if (empty($this->baseTypes[$type])) {
343:             throw new CakeException(__d('cake_dev', 'Invalid type name'));
344:         }
345:         return $this->baseTypes[$type];
346:     }
347: 
348: /**
349:  * Get methods declared in the class given.
350:  * No parent methods will be returned
351:  *
352:  * @param string $className Name of class to look at.
353:  * @return array Array of method names.
354:  */
355:     public function getTestableMethods($className) {
356:         $classMethods = get_class_methods($className);
357:         $parentMethods = get_class_methods(get_parent_class($className));
358:         $thisMethods = array_diff($classMethods, $parentMethods);
359:         $out = array();
360:         foreach ($thisMethods as $method) {
361:             if (substr($method, 0, 1) !== '_' && $method != strtolower($className)) {
362:                 $out[] = $method;
363:             }
364:         }
365:         return $out;
366:     }
367: 
368: /**
369:  * Generate the list of fixtures that will be required to run this test based on
370:  * loaded models.
371:  *
372:  * @param object $subject The object you want to generate fixtures for.
373:  * @return array Array of fixtures to be included in the test.
374:  */
375:     public function generateFixtureList($subject) {
376:         $this->_fixtures = array();
377:         if (is_a($subject, 'Model')) {
378:             $this->_processModel($subject);
379:         } elseif (is_a($subject, 'Controller')) {
380:             $this->_processController($subject);
381:         }
382:         return array_values($this->_fixtures);
383:     }
384: 
385: /**
386:  * Process a model recursively and pull out all the
387:  * model names converting them to fixture names.
388:  *
389:  * @param Model $subject A Model class to scan for associations and pull fixtures off of.
390:  * @return void
391:  */
392:     protected function _processModel($subject) {
393:         $this->_addFixture($subject->name);
394:         $associated = $subject->getAssociated();
395:         foreach ($associated as $alias => $type) {
396:             $className = $subject->{$alias}->name;
397:             if (!isset($this->_fixtures[$className])) {
398:                 $this->_processModel($subject->{$alias});
399:             }
400:             if ($type === 'hasAndBelongsToMany') {
401:                 if (!empty($subject->hasAndBelongsToMany[$alias]['with'])) {
402:                     list(, $joinModel) = pluginSplit($subject->hasAndBelongsToMany[$alias]['with']);
403:                 } else {
404:                     $joinModel = Inflector::classify($subject->hasAndBelongsToMany[$alias]['joinTable']);
405:                 }
406:                 if (!isset($this->_fixtures[$joinModel])) {
407:                     $this->_processModel($subject->{$joinModel});
408:                 }
409:             }
410:         }
411:     }
412: 
413: /**
414:  * Process all the models attached to a controller
415:  * and generate a fixture list.
416:  *
417:  * @param Controller $subject A controller to pull model names off of.
418:  * @return void
419:  */
420:     protected function _processController($subject) {
421:         $subject->constructClasses();
422:         $models = array(Inflector::classify($subject->name));
423:         if (!empty($subject->uses)) {
424:             $models = $subject->uses;
425:         }
426:         foreach ($models as $model) {
427:             list(, $model) = pluginSplit($model);
428:             $this->_processModel($subject->{$model});
429:         }
430:     }
431: 
432: /**
433:  * Add classname to the fixture list.
434:  * Sets the app. or plugin.plugin_name. prefix.
435:  *
436:  * @param string $name Name of the Model class that a fixture might be required for.
437:  * @return void
438:  */
439:     protected function _addFixture($name) {
440:         if ($this->plugin) {
441:             $prefix = 'plugin.' . Inflector::underscore($this->plugin) . '.';
442:         } else {
443:             $prefix = 'app.';
444:         }
445:         $fixture = $prefix . Inflector::underscore($name);
446:         $this->_fixtures[$name] = $fixture;
447:     }
448: 
449: /**
450:  * Interact with the user to get additional fixtures they want to use.
451:  *
452:  * @return array Array of fixtures the user wants to add.
453:  */
454:     public function getUserFixtures() {
455:         $proceed = $this->in(__d('cake_console', 'Bake could not detect fixtures, would you like to add some?'), array('y', 'n'), 'n');
456:         $fixtures = array();
457:         if (strtolower($proceed) === 'y') {
458:             $fixtureList = $this->in(__d('cake_console', "Please provide a comma separated list of the fixtures names you'd like to use.\nExample: 'app.comment, app.post, plugin.forums.post'"));
459:             $fixtureListTrimmed = str_replace(' ', '', $fixtureList);
460:             $fixtures = explode(',', $fixtureListTrimmed);
461:         }
462:         $this->_fixtures = array_merge($this->_fixtures, $fixtures);
463:         return $fixtures;
464:     }
465: 
466: /**
467:  * Is a mock class required for this type of test?
468:  * Controllers require a mock class.
469:  *
470:  * @param string $type The type of object tests are being generated for eg. controller.
471:  * @return boolean
472:  */
473:     public function hasMockClass($type) {
474:         $type = strtolower($type);
475:         return $type === 'controller';
476:     }
477: 
478: /**
479:  * Generate a constructor code snippet for the type and classname
480:  *
481:  * @param string $type The Type of object you are generating tests for eg. controller
482:  * @param string $fullClassName The Classname of the class the test is being generated for.
483:  * @param string $plugin The plugin name.
484:  * @return array Constructor snippets for the thing you are building.
485:  */
486:     public function generateConstructor($type, $fullClassName, $plugin) {
487:         $type = strtolower($type);
488:         $pre = $construct = $post = '';
489:         if ($type === 'model') {
490:             $construct = "ClassRegistry::init('{$plugin}$fullClassName');\n";
491:         }
492:         if ($type === 'behavior') {
493:             $construct = "new $fullClassName();\n";
494:         }
495:         if ($type === 'helper') {
496:             $pre = "\$View = new View();\n";
497:             $construct = "new {$fullClassName}(\$View);\n";
498:         }
499:         if ($type === 'component') {
500:             $pre = "\$Collection = new ComponentCollection();\n";
501:             $construct = "new {$fullClassName}(\$Collection);\n";
502:         }
503:         return array($pre, $construct, $post);
504:     }
505: 
506: /**
507:  * Generate the uses() calls for a type & classname
508:  *
509:  * @param string $type The Type of object you are generating tests for eg. controller
510:  * @param string $realType The package name for the class.
511:  * @param string $className The Classname of the class the test is being generated for.
512:  * @return array An array containing used classes
513:  */
514:     public function generateUses($type, $realType, $className) {
515:         $uses = array();
516:         $type = strtolower($type);
517:         if ($type === 'component') {
518:             $uses[] = array('ComponentCollection', 'Controller');
519:             $uses[] = array('Component', 'Controller');
520:         }
521:         if ($type === 'helper') {
522:             $uses[] = array('View', 'View');
523:             $uses[] = array('Helper', 'View');
524:         }
525:         $uses[] = array($className, $realType);
526:         return $uses;
527:     }
528: 
529: /**
530:  * Make the filename for the test case. resolve the suffixes for controllers
531:  * and get the plugin path if needed.
532:  *
533:  * @param string $type The Type of object you are generating tests for eg. controller
534:  * @param string $className the Classname of the class the test is being generated for.
535:  * @return string filename the test should be created on.
536:  */
537:     public function testCaseFileName($type, $className) {
538:         $path = $this->getPath() . 'Case' . DS;
539:         $type = Inflector::camelize($type);
540:         if (isset($this->classTypes[$type])) {
541:             $path .= $this->classTypes[$type] . DS;
542:         }
543:         $className = $this->getRealClassName($type, $className);
544:         return str_replace('/', DS, $path) . Inflector::camelize($className) . 'Test.php';
545:     }
546: 
547: /**
548:  * get the option parser.
549:  *
550:  * @return void
551:  */
552:     public function getOptionParser() {
553:         $parser = parent::getOptionParser();
554:         return $parser->description(__d('cake_console', 'Bake test case skeletons for classes.'))
555:             ->addArgument('type', array(
556:                 'help' => __d('cake_console', 'Type of class to bake, can be any of the following: controller, model, helper, component or behavior.'),
557:                 'choices' => array(
558:                     'Controller', 'controller',
559:                     'Model', 'model',
560:                     'Helper', 'helper',
561:                     'Component', 'component',
562:                     'Behavior', 'behavior'
563:                 )
564:             ))->addArgument('name', array(
565:                 'help' => __d('cake_console', 'An existing class to bake tests for.')
566:             ))->addOption('plugin', array(
567:                 'short' => 'p',
568:                 'help' => __d('cake_console', 'CamelCased name of the plugin to bake tests for.')
569:             ))->epilog(__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.'));
570:     }
571: 
572: }
573: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs