PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/main/lib/engine/autowire/binder.php

https://gitlab.com/alexprowars/bitrix
PHP | 442 lines | 308 code | 68 blank | 66 comment | 28 complexity | ea7117b8ffb79502ab8de0160944ef1b MD5 | raw file
  1. <?php
  2. namespace Bitrix\Main\Engine\AutoWire;
  3. use Bitrix\Main\Result;
  4. class Binder
  5. {
  6. const STATUS_FOUND = true;
  7. const STATUS_NOT_FOUND = false;
  8. private $instance;
  9. private $method;
  10. /** @var array */
  11. private $configuration = [];
  12. /** @var \SplObjectStorage|Parameter[] */
  13. private static $globalAutoWiredParameters;
  14. /** @var Parameter[] */
  15. private $autoWiredParameters = [];
  16. /** @var \ReflectionFunctionAbstract */
  17. private $reflectionFunctionAbstract;
  18. /** @var array */
  19. private $methodParams = null;
  20. /** @var array */
  21. private $args = null;
  22. public function __construct($instance, $method, $configuration = [])
  23. {
  24. $this->instance = $instance;
  25. $this->method = $method;
  26. $this->configuration = $configuration;
  27. if ($this->instance === null)
  28. {
  29. $this->buildReflectionFunction();
  30. }
  31. else
  32. {
  33. $this->buildReflectionMethod();
  34. }
  35. }
  36. final public static function buildForFunction($callable, $configuration = [])
  37. {
  38. return new static(null, $callable, $configuration);
  39. }
  40. final public static function buildForMethod($instance, $method, $configuration = [])
  41. {
  42. return new static($instance, $method, $configuration);
  43. }
  44. private function buildReflectionMethod()
  45. {
  46. $this->reflectionFunctionAbstract = new \ReflectionMethod($this->instance, $this->method);
  47. $this->reflectionFunctionAbstract->setAccessible(true);
  48. }
  49. private function buildReflectionFunction()
  50. {
  51. $this->reflectionFunctionAbstract = new \ReflectionFunction($this->method);
  52. }
  53. /**
  54. * @return mixed
  55. */
  56. public function getInstance()
  57. {
  58. return $this->instance;
  59. }
  60. /**
  61. * @return mixed
  62. */
  63. public function getMethod()
  64. {
  65. return $this->method;
  66. }
  67. /**
  68. * @return array
  69. */
  70. public function getConfiguration()
  71. {
  72. return $this->configuration;
  73. }
  74. /**
  75. * @param array $configuration
  76. *
  77. * @return Binder
  78. */
  79. public function setConfiguration($configuration)
  80. {
  81. $this->configuration = $configuration;
  82. return $this;
  83. }
  84. /**
  85. * @param Parameter[] $parameters
  86. *
  87. * @return $this
  88. */
  89. public function setAutoWiredParameters(array $parameters)
  90. {
  91. $this->autoWiredParameters = [];
  92. foreach ($parameters as $parameter)
  93. {
  94. $this->appendAutoWiredParameter($parameter);
  95. }
  96. return $this;
  97. }
  98. public function appendAutoWiredParameter(Parameter $parameter)
  99. {
  100. $this->autoWiredParameters[] = $parameter;
  101. return $this;
  102. }
  103. /**
  104. * Register globally auto wired parameter. The method was added in backwards compatibility reason.
  105. * @param Parameter $parameter
  106. * @return void
  107. */
  108. public static function registerGlobalAutoWiredParameter(Parameter $parameter)
  109. {
  110. if (self::$globalAutoWiredParameters === null)
  111. {
  112. self::$globalAutoWiredParameters = new \SplObjectStorage();
  113. }
  114. if (!self::$globalAutoWiredParameters->contains($parameter))
  115. {
  116. self::$globalAutoWiredParameters[$parameter] = $parameter;
  117. }
  118. }
  119. /**
  120. * @param Parameter $parameter
  121. * @return void
  122. */
  123. public static function unRegisterGlobalAutoWiredParameter(Parameter $parameter): void
  124. {
  125. if (self::$globalAutoWiredParameters === null)
  126. {
  127. return;
  128. }
  129. if (self::$globalAutoWiredParameters->contains($parameter))
  130. {
  131. self::$globalAutoWiredParameters->detach($parameter);
  132. }
  133. }
  134. private function getPriorityByParameter(Parameter $parameter)
  135. {
  136. return $parameter->getPriority();
  137. }
  138. /**
  139. * @return Parameter[]
  140. */
  141. public function getAutoWiredParameters()
  142. {
  143. return $this->autoWiredParameters;
  144. }
  145. public function setSourcesParametersToMap(array $parameters)
  146. {
  147. $this->configuration['sourceParameters'] = $parameters;
  148. return $this;
  149. }
  150. public function getSourcesParametersToMap()
  151. {
  152. return $this->configuration['sourceParameters']?: [];
  153. }
  154. public function appendSourcesParametersToMap(array $parameter)
  155. {
  156. if (!isset($this->configuration['sourceParameters']))
  157. {
  158. $this->configuration['sourceParameters'] = [];
  159. }
  160. $this->configuration['sourceParameters'][] = $parameter;
  161. return $this;
  162. }
  163. /**
  164. * Invokes method with binded parameters.
  165. * return @mixed
  166. */
  167. final public function invoke()
  168. {
  169. try
  170. {
  171. if($this->reflectionFunctionAbstract instanceof \ReflectionMethod)
  172. {
  173. return $this->reflectionFunctionAbstract->invokeArgs($this->instance, $this->getArgs());
  174. }
  175. elseif ($this->reflectionFunctionAbstract instanceof \ReflectionFunction)
  176. {
  177. return $this->reflectionFunctionAbstract->invokeArgs($this->getArgs());
  178. }
  179. }
  180. catch (\TypeError $exception)
  181. {
  182. throw $exception;
  183. // $this->processException($exception);
  184. }
  185. catch (\ErrorException $exception)
  186. {
  187. throw $exception;
  188. // $this->processException($exception);
  189. }
  190. return null;
  191. }
  192. /**
  193. * Returns list of method params.
  194. * @return array
  195. */
  196. final public function getMethodParams()
  197. {
  198. if ($this->methodParams === null)
  199. {
  200. $this->bindParams();
  201. }
  202. return $this->methodParams;
  203. }
  204. /**
  205. * Sets list of method params.
  206. * @param array $params List of parameters.
  207. *
  208. * @return $this
  209. */
  210. final public function setMethodParams(array $params)
  211. {
  212. $this->methodParams = $params;
  213. $this->args = array_values($params);
  214. return $this;
  215. }
  216. /**
  217. * Returns list of method params which possible use in call_user_func_array().
  218. * @return array
  219. */
  220. final public function getArgs()
  221. {
  222. if ($this->args === null)
  223. {
  224. $this->bindParams();
  225. }
  226. return $this->args;
  227. }
  228. private function bindParams()
  229. {
  230. $this->args = $this->methodParams = [];
  231. foreach ($this->reflectionFunctionAbstract->getParameters() as $param)
  232. {
  233. $value = $this->getParameterValue($param);
  234. $this->args[] = $this->methodParams[$param->getName()] = $value;
  235. }
  236. return $this->args;
  237. }
  238. /**
  239. * @param \ReflectionParameter $reflectionParameter
  240. *
  241. * @return \SplPriorityQueue|Parameter[]
  242. */
  243. private function getAutoWiredByClass(\ReflectionParameter $reflectionParameter)
  244. {
  245. $result = new \SplPriorityQueue();
  246. foreach ($this->getAllAutoWiredParameters() as $parameter)
  247. {
  248. if ($parameter->match($reflectionParameter))
  249. {
  250. $result->insert($parameter, $this->getPriorityByParameter($parameter));
  251. }
  252. }
  253. return $result;
  254. }
  255. /**
  256. * @return Parameter[]
  257. */
  258. private function getAllAutoWiredParameters()
  259. {
  260. $list = $this->getAutoWiredParameters();
  261. foreach (self::$globalAutoWiredParameters as $globalAutoWiredParameter)
  262. {
  263. $list[] = $globalAutoWiredParameter;
  264. }
  265. return $list;
  266. }
  267. protected function constructValue(\ReflectionParameter $parameter, Parameter $autoWireParameter, Result $captureResult): Result
  268. {
  269. $result = new Result();
  270. $constructedValue = $autoWireParameter->constructValue($parameter, $captureResult);
  271. $result->setData([
  272. 'value' => $constructedValue,
  273. ]);
  274. return $result;
  275. }
  276. private function getParameterValue(\ReflectionParameter $parameter)
  277. {
  278. $sourceParameters = $this->getSourcesParametersToMap();
  279. if ($parameter->getClass())
  280. {
  281. foreach ($this->getAutoWiredByClass($parameter) as $autoWireParameter)
  282. {
  283. $result = $autoWireParameter->captureData($parameter, $sourceParameters, $this->getAllAutoWiredParameters());
  284. if (!$result->isSuccess())
  285. {
  286. continue;
  287. }
  288. $constructedValue = null;
  289. $constructResult = $this->constructValue($parameter, $autoWireParameter, $result);
  290. if ($constructResult->isSuccess())
  291. {
  292. ['value' => $constructedValue] = $constructResult->getData();
  293. }
  294. if ($constructedValue === null)
  295. {
  296. if ($parameter->isDefaultValueAvailable())
  297. {
  298. return $parameter->getDefaultValue();
  299. }
  300. throw new BinderArgumentException(
  301. "Could not construct parameter {{$parameter->getName()}}",
  302. $parameter,
  303. $constructResult->getErrors(),
  304. );
  305. }
  306. return $constructedValue;
  307. }
  308. if ($parameter->isDefaultValueAvailable())
  309. {
  310. return $parameter->getDefaultValue();
  311. }
  312. $exceptionMessage = "Could not find value for parameter to build auto wired argument {{$parameter->getClass()->getName()} \${$parameter->getName()}}";
  313. if ($result !== null && $result->getErrorMessages())
  314. {
  315. $exceptionMessage = $result->getErrorMessages()[0];
  316. }
  317. throw new BinderArgumentException(
  318. $exceptionMessage,
  319. $parameter
  320. );
  321. }
  322. $value = $this->findParameterInSourceList($parameter->getName(), $status);
  323. if ($status === self::STATUS_NOT_FOUND)
  324. {
  325. if ($parameter->isDefaultValueAvailable())
  326. {
  327. return $parameter->getDefaultValue();
  328. }
  329. throw new BinderArgumentException(
  330. "Could not find value for parameter {{$parameter->getName()}}",
  331. $parameter
  332. );
  333. }
  334. elseif ($parameter->getType() instanceof \ReflectionNamedType)
  335. {
  336. /** @var \ReflectionNamedType $reflectionType */
  337. $reflectionType = $parameter->getType();
  338. $declarationChecker = new TypeDeclarationChecker($reflectionType, $value);
  339. if (!$declarationChecker->isSatisfied())
  340. {
  341. throw new BinderArgumentException(
  342. "Invalid value {{$value}} to match with parameter {{$parameter->getName()}}. Should be value of type {$reflectionType->getName()}.",
  343. $parameter
  344. );
  345. }
  346. }
  347. if ($parameter->isArray())
  348. {
  349. $value = (array)$value;
  350. }
  351. return $value;
  352. }
  353. private function findParameterInSourceList($name, &$status)
  354. {
  355. $status = self::STATUS_FOUND;
  356. foreach ($this->getSourcesParametersToMap() as $source)
  357. {
  358. if (isset($source[$name]))
  359. {
  360. return $source[$name];
  361. }
  362. if ($source instanceof \ArrayAccess && $source->offsetExists($name))
  363. {
  364. return $source[$name];
  365. }
  366. elseif (is_array($source) && array_key_exists($name, $source))
  367. {
  368. return $source[$name];
  369. }
  370. }
  371. $status = self::STATUS_NOT_FOUND;
  372. return null;
  373. }
  374. }