1<?php 2 3/* 4 * This file is part of the Prophecy. 5 * (c) Konstantin Kudryashov <ever.zet@gmail.com> 6 * Marcello Duarte <marcello.duarte@gmail.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Prophecy\Doubler; 13 14use Doctrine\Instantiator\Instantiator; 15use Prophecy\Doubler\ClassPatch\ClassPatchInterface; 16use Prophecy\Doubler\Generator\ClassMirror; 17use Prophecy\Doubler\Generator\ClassCreator; 18use Prophecy\Exception\InvalidArgumentException; 19use ReflectionClass; 20 21/** 22 * Cached class doubler. 23 * Prevents mirroring/creation of the same structure twice. 24 * 25 * @author Konstantin Kudryashov <ever.zet@gmail.com> 26 */ 27class Doubler 28{ 29 private $mirror; 30 private $creator; 31 private $namer; 32 33 /** 34 * @var ClassPatchInterface[] 35 */ 36 private $patches = array(); 37 38 /** 39 * @var \Doctrine\Instantiator\Instantiator 40 */ 41 private $instantiator; 42 43 /** 44 * Initializes doubler. 45 * 46 * @param ClassMirror $mirror 47 * @param ClassCreator $creator 48 * @param NameGenerator $namer 49 */ 50 public function __construct(ClassMirror $mirror = null, ClassCreator $creator = null, 51 NameGenerator $namer = null) 52 { 53 $this->mirror = $mirror ?: new ClassMirror; 54 $this->creator = $creator ?: new ClassCreator; 55 $this->namer = $namer ?: new NameGenerator; 56 } 57 58 /** 59 * Returns list of registered class patches. 60 * 61 * @return ClassPatchInterface[] 62 */ 63 public function getClassPatches() 64 { 65 return $this->patches; 66 } 67 68 /** 69 * Registers new class patch. 70 * 71 * @param ClassPatchInterface $patch 72 */ 73 public function registerClassPatch(ClassPatchInterface $patch) 74 { 75 $this->patches[] = $patch; 76 77 @usort($this->patches, function (ClassPatchInterface $patch1, ClassPatchInterface $patch2) { 78 return $patch2->getPriority() - $patch1->getPriority(); 79 }); 80 } 81 82 /** 83 * Creates double from specific class or/and list of interfaces. 84 * 85 * @param ReflectionClass $class 86 * @param ReflectionClass[] $interfaces Array of ReflectionClass instances 87 * @param array $args Constructor arguments 88 * 89 * @return DoubleInterface 90 * 91 * @throws \Prophecy\Exception\InvalidArgumentException 92 */ 93 public function double(ReflectionClass $class = null, array $interfaces, array $args = null) 94 { 95 foreach ($interfaces as $interface) { 96 if (!$interface instanceof ReflectionClass) { 97 throw new InvalidArgumentException(sprintf( 98 "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n". 99 "a second argument to `Doubler::double(...)`, but got %s.", 100 is_object($interface) ? get_class($interface).' class' : gettype($interface) 101 )); 102 } 103 } 104 105 $classname = $this->createDoubleClass($class, $interfaces); 106 $reflection = new ReflectionClass($classname); 107 108 if (null !== $args) { 109 return $reflection->newInstanceArgs($args); 110 } 111 if ((null === $constructor = $reflection->getConstructor()) 112 || ($constructor->isPublic() && !$constructor->isFinal())) { 113 return $reflection->newInstance(); 114 } 115 116 if (!$this->instantiator) { 117 $this->instantiator = new Instantiator(); 118 } 119 120 return $this->instantiator->instantiate($classname); 121 } 122 123 /** 124 * Creates double class and returns its FQN. 125 * 126 * @param ReflectionClass $class 127 * @param ReflectionClass[] $interfaces 128 * 129 * @return string 130 */ 131 protected function createDoubleClass(ReflectionClass $class = null, array $interfaces) 132 { 133 $name = $this->namer->name($class, $interfaces); 134 $node = $this->mirror->reflect($class, $interfaces); 135 136 foreach ($this->patches as $patch) { 137 if ($patch->supports($node)) { 138 $patch->apply($node); 139 } 140 } 141 142 $this->creator->create($name, $node); 143 144 return $name; 145 } 146} 147