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.2 API

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