ObjectManager.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\TestFramework\Unit\Helper;
  7. /**
  8. * Helper class for basic object retrieving, such as blocks, models etc...
  9. *
  10. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  11. */
  12. class ObjectManager
  13. {
  14. /**
  15. * Special cases configuration
  16. *
  17. * @var array
  18. */
  19. protected $_specialCases = [
  20. \Magento\Framework\Model\ResourceModel\AbstractResource::class => '_getResourceModelMock',
  21. \Magento\Framework\TranslateInterface::class => '_getTranslatorMock',
  22. ];
  23. /**
  24. * Test object
  25. *
  26. * @var \PHPUnit\Framework\TestCase
  27. */
  28. protected $_testObject;
  29. /**
  30. * Constructor
  31. *
  32. * @param \PHPUnit\Framework\TestCase $testObject
  33. */
  34. public function __construct(\PHPUnit\Framework\TestCase $testObject)
  35. {
  36. $this->_testObject = $testObject;
  37. }
  38. /**
  39. * Get mock for argument
  40. *
  41. * @param string $argClassName
  42. * @param array $originalArguments
  43. * @return null|object|\PHPUnit_Framework_MockObject_MockObject
  44. */
  45. protected function _createArgumentMock($argClassName, array $originalArguments)
  46. {
  47. $object = null;
  48. if ($argClassName) {
  49. $object = $this->_processSpecialCases($argClassName, $originalArguments);
  50. if (null === $object) {
  51. $object = $this->_getMockWithoutConstructorCall($argClassName);
  52. }
  53. }
  54. return $object;
  55. }
  56. /**
  57. * Process special cases
  58. *
  59. * @param string $className
  60. * @param array $arguments
  61. * @return null|object
  62. */
  63. protected function _processSpecialCases($className, $arguments)
  64. {
  65. $object = null;
  66. $interfaces = class_implements($className);
  67. if (in_array(\Magento\Framework\ObjectManager\ContextInterface::class, $interfaces)) {
  68. $object = $this->getObject($className, $arguments);
  69. } elseif (isset($this->_specialCases[$className])) {
  70. $method = $this->_specialCases[$className];
  71. $object = $this->{$method}($className);
  72. }
  73. return $object;
  74. }
  75. /**
  76. * Retrieve specific mock of core resource model
  77. *
  78. * @return \Magento\Framework\Module\ResourceInterface|\PHPUnit_Framework_MockObject_MockObject
  79. */
  80. protected function _getResourceModelMock()
  81. {
  82. $resourceMock = $this->_testObject->getMockBuilder(\Magento\Framework\Module\ModuleResource::class)
  83. ->disableOriginalConstructor()
  84. ->disableOriginalClone()
  85. ->disableArgumentCloning()
  86. ->disallowMockingUnknownTypes()
  87. ->setMethods(['getIdFieldName', '__sleep', '__wakeup'])
  88. ->getMock();
  89. $resourceMock->expects(
  90. $this->_testObject->any()
  91. )->method(
  92. 'getIdFieldName'
  93. )->will(
  94. $this->_testObject->returnValue('id')
  95. );
  96. return $resourceMock;
  97. }
  98. /**
  99. * Retrieve mock of core translator model
  100. *
  101. * @param string $className
  102. * @return \Magento\Framework\TranslateInterface|\PHPUnit_Framework_MockObject_MockObject
  103. */
  104. protected function _getTranslatorMock($className)
  105. {
  106. $translator = $this->_testObject->getMockBuilder($className)->disableOriginalConstructor()->getMock();
  107. $translateCallback = function ($arguments) {
  108. return is_array($arguments) ? vsprintf(array_shift($arguments), $arguments) : '';
  109. };
  110. $translator->expects(
  111. $this->_testObject->any()
  112. )->method(
  113. 'translate'
  114. )->will(
  115. $this->_testObject->returnCallback($translateCallback)
  116. );
  117. return $translator;
  118. }
  119. /**
  120. * Get mock without call of original constructor
  121. *
  122. * @param string $className
  123. * @return \PHPUnit_Framework_MockObject_MockObject
  124. */
  125. protected function _getMockWithoutConstructorCall($className)
  126. {
  127. $mock = $this->_testObject->getMockBuilder($className)
  128. ->disableOriginalConstructor()
  129. ->disableOriginalClone()
  130. ->disableArgumentCloning()
  131. ->disallowMockingUnknownTypes()
  132. ->getMock();
  133. return $mock;
  134. }
  135. /**
  136. * Get class instance
  137. *
  138. * @param string $className
  139. * @param array $arguments
  140. * @return object
  141. */
  142. public function getObject($className, array $arguments = [])
  143. {
  144. if (is_subclass_of($className, \Magento\Framework\Api\AbstractSimpleObjectBuilder::class)
  145. || is_subclass_of($className, \Magento\Framework\Api\Builder::class)
  146. ) {
  147. return $this->getBuilder($className, $arguments);
  148. }
  149. $constructArguments = $this->getConstructArguments($className, $arguments);
  150. $reflectionClass = new \ReflectionClass($className);
  151. $newObject = $reflectionClass->newInstanceArgs($constructArguments);
  152. foreach (array_diff_key($arguments, $constructArguments) as $key => $value) {
  153. $propertyReflectionClass = $reflectionClass;
  154. while ($propertyReflectionClass) {
  155. if ($propertyReflectionClass->hasProperty($key)) {
  156. $reflectionProperty = $propertyReflectionClass->getProperty($key);
  157. $reflectionProperty->setAccessible(true);
  158. $reflectionProperty->setValue($newObject, $value);
  159. break;
  160. }
  161. $propertyReflectionClass = $propertyReflectionClass->getParentClass();
  162. }
  163. }
  164. return $newObject;
  165. }
  166. /**
  167. * Get data object builder
  168. *
  169. * @param string $className
  170. * @param array $arguments
  171. * @return object
  172. */
  173. protected function getBuilder($className, array $arguments)
  174. {
  175. if (!isset($arguments['objectFactory'])) {
  176. $objectFactory = $this->_testObject->getMockBuilder(\Magento\Framework\Api\ObjectFactory::class)
  177. ->disableOriginalConstructor()
  178. ->disableOriginalClone()
  179. ->disableArgumentCloning()
  180. ->disallowMockingUnknownTypes()
  181. ->setMethods(['populateWithArray', 'populate', 'create'])
  182. ->getMock();
  183. $objectFactory->expects($this->_testObject->any())
  184. ->method('populateWithArray')
  185. ->will($this->_testObject->returnSelf());
  186. $objectFactory->expects($this->_testObject->any())
  187. ->method('populate')
  188. ->will($this->_testObject->returnSelf());
  189. $objectFactory->expects($this->_testObject->any())
  190. ->method('create')
  191. ->will($this->_testObject->returnCallback(
  192. function ($className, $arguments) {
  193. $reflectionClass = new \ReflectionClass($className);
  194. $constructorMethod = $reflectionClass->getConstructor();
  195. $parameters = $constructorMethod->getParameters();
  196. $args = [];
  197. foreach ($parameters as $parameter) {
  198. $parameterName = $parameter->getName();
  199. if (isset($arguments[$parameterName])) {
  200. $args[] = $arguments[$parameterName];
  201. } else {
  202. if ($parameter->isArray()) {
  203. $args[] = [];
  204. } elseif ($parameter->allowsNull()) {
  205. $args[] = null;
  206. } else {
  207. $mock = $this->_getMockWithoutConstructorCall($parameter->getClass()->getName());
  208. $args[] = $mock;
  209. }
  210. }
  211. }
  212. return new $className(...array_values($args));
  213. }
  214. ));
  215. $arguments['objectFactory'] = $objectFactory;
  216. }
  217. return new $className(...array_values($this->getConstructArguments($className, $arguments)));
  218. }
  219. /**
  220. * Retrieve associative array of arguments that used for new object instance creation
  221. *
  222. * @param string $className
  223. * @param array $arguments
  224. * @return array
  225. */
  226. public function getConstructArguments($className, array $arguments = [])
  227. {
  228. $constructArguments = [];
  229. if (!method_exists($className, '__construct')) {
  230. return $constructArguments;
  231. }
  232. $method = new \ReflectionMethod($className, '__construct');
  233. foreach ($method->getParameters() as $parameter) {
  234. $parameterName = $parameter->getName();
  235. $argClassName = null;
  236. $defaultValue = null;
  237. if (array_key_exists($parameterName, $arguments)) {
  238. $constructArguments[$parameterName] = $arguments[$parameterName];
  239. continue;
  240. }
  241. if ($parameter->isDefaultValueAvailable()) {
  242. $defaultValue = $parameter->getDefaultValue();
  243. }
  244. try {
  245. if ($parameter->getClass()) {
  246. $argClassName = $parameter->getClass()->getName();
  247. }
  248. $object = $this->_getMockObject($argClassName, $arguments);
  249. } catch (\ReflectionException $e) {
  250. $parameterString = $parameter->__toString();
  251. $firstPosition = strpos($parameterString, '<required>');
  252. if ($firstPosition !== false) {
  253. $parameterString = substr($parameterString, $firstPosition + 11);
  254. $parameterString = substr($parameterString, 0, strpos($parameterString, ' '));
  255. $object = $this->_testObject->getMockBuilder($parameterString)
  256. ->disableOriginalConstructor()
  257. ->disableOriginalClone()
  258. ->disableArgumentCloning()
  259. ->disallowMockingUnknownTypes()
  260. ->getMock();
  261. }
  262. }
  263. $constructArguments[$parameterName] = null === $object ? $defaultValue : $object;
  264. }
  265. return $constructArguments;
  266. }
  267. /**
  268. * Get collection mock
  269. *
  270. * @param string $className
  271. * @param array $data
  272. * @return \PHPUnit_Framework_MockObject_MockObject
  273. * @throws \InvalidArgumentException
  274. */
  275. public function getCollectionMock($className, array $data)
  276. {
  277. if (!is_subclass_of($className, \Magento\Framework\Data\Collection::class)) {
  278. throw new \InvalidArgumentException(
  279. $className . ' does not instance of \Magento\Framework\Data\Collection'
  280. );
  281. }
  282. $mock = $this->_testObject->getMockBuilder($className)
  283. ->disableOriginalConstructor()
  284. ->disableOriginalClone()
  285. ->disableArgumentCloning()
  286. ->disallowMockingUnknownTypes()
  287. ->getMock();
  288. $iterator = new \ArrayIterator($data);
  289. $mock->expects(
  290. $this->_testObject->any()
  291. )->method(
  292. 'getIterator'
  293. )->will(
  294. $this->_testObject->returnValue($iterator)
  295. );
  296. return $mock;
  297. }
  298. /**
  299. * Helper function that creates a mock object for a given class name.
  300. *
  301. * Will return a real object in some cases to assist in testing.
  302. *
  303. * @param string $argClassName
  304. * @param array $arguments
  305. * @return null|object|\PHPUnit_Framework_MockObject_MockObject
  306. */
  307. private function _getMockObject($argClassName, array $arguments)
  308. {
  309. if (is_subclass_of($argClassName, \Magento\Framework\Api\ExtensibleObjectBuilder::class)) {
  310. $object = $this->getBuilder($argClassName, $arguments);
  311. return $object;
  312. } else {
  313. $object = $this->_createArgumentMock($argClassName, $arguments);
  314. return $object;
  315. }
  316. }
  317. /**
  318. * Set mocked property
  319. *
  320. * @param object $object
  321. * @param string $propertyName
  322. * @param object $propertyValue
  323. * @param string $className The namespace of parent class for injection private property into this class
  324. * @return void
  325. */
  326. public function setBackwardCompatibleProperty($object, $propertyName, $propertyValue, $className = '')
  327. {
  328. $reflection = new \ReflectionClass($className ? $className : get_class($object));
  329. $reflectionProperty = $reflection->getProperty($propertyName);
  330. $reflectionProperty->setAccessible(true);
  331. $reflectionProperty->setValue($object, $propertyValue);
  332. }
  333. }