SerializableClosure.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <?php
  2. /* ===========================================================================
  3. * Copyright (c) 2018-2019 Zindex Software
  4. *
  5. * Licensed under the MIT License
  6. * =========================================================================== */
  7. namespace Opis\Closure;
  8. use Closure;
  9. use Serializable;
  10. use SplObjectStorage;
  11. use ReflectionObject;
  12. /**
  13. * Provides a wrapper for serialization of closures
  14. */
  15. class SerializableClosure implements Serializable
  16. {
  17. /**
  18. * @var Closure Wrapped closure
  19. *
  20. * @see \Opis\Closure\SerializableClosure::getClosure()
  21. */
  22. protected $closure;
  23. /**
  24. * @var ReflectionClosure A reflection instance for closure
  25. *
  26. * @see \Opis\Closure\SerializableClosure::getReflector()
  27. */
  28. protected $reflector;
  29. /**
  30. * @var mixed Used at deserialization to hold variables
  31. *
  32. * @see \Opis\Closure\SerializableClosure::unserialize()
  33. * @see \Opis\Closure\SerializableClosure::getReflector()
  34. */
  35. protected $code;
  36. /**
  37. * @var string Closure's ID
  38. */
  39. protected $reference;
  40. /**
  41. * @var string Closure scope
  42. */
  43. protected $scope;
  44. /**
  45. * @var ClosureContext Context of closure, used in serialization
  46. */
  47. protected static $context;
  48. /**
  49. * @var ISecurityProvider|null
  50. */
  51. protected static $securityProvider;
  52. /** Array recursive constant*/
  53. const ARRAY_RECURSIVE_KEY = '¯\_(ツ)_/¯';
  54. /**
  55. * Constructor
  56. *
  57. * @param Closure $closure Closure you want to serialize
  58. */
  59. public function __construct(Closure $closure)
  60. {
  61. $this->closure = $closure;
  62. if (static::$context !== null) {
  63. $this->scope = static::$context->scope;
  64. $this->scope->toserialize++;
  65. }
  66. }
  67. /**
  68. * Get the Closure object
  69. *
  70. * @return Closure The wrapped closure
  71. */
  72. public function getClosure()
  73. {
  74. return $this->closure;
  75. }
  76. /**
  77. * Get the reflector for closure
  78. *
  79. * @return ReflectionClosure
  80. */
  81. public function getReflector()
  82. {
  83. if ($this->reflector === null) {
  84. $this->reflector = new ReflectionClosure($this->closure, $this->code);
  85. $this->code = null;
  86. }
  87. return $this->reflector;
  88. }
  89. /**
  90. * Implementation of magic method __invoke()
  91. */
  92. public function __invoke()
  93. {
  94. return call_user_func_array($this->closure, func_get_args());
  95. }
  96. /**
  97. * Implementation of Serializable::serialize()
  98. *
  99. * @return string The serialized closure
  100. */
  101. public function serialize()
  102. {
  103. if ($this->scope === null) {
  104. $this->scope = new ClosureScope();
  105. $this->scope->toserialize++;
  106. }
  107. $this->scope->serializations++;
  108. $scope = $object = null;
  109. $reflector = $this->getReflector();
  110. if($reflector->isBindingRequired()){
  111. $object = $reflector->getClosureThis();
  112. static::wrapClosures($object, $this->scope);
  113. if($scope = $reflector->getClosureScopeClass()){
  114. $scope = $scope->name;
  115. }
  116. } elseif($reflector->isScopeRequired()) {
  117. if($scope = $reflector->getClosureScopeClass()){
  118. $scope = $scope->name;
  119. }
  120. }
  121. $this->reference = spl_object_hash($this->closure);
  122. $this->scope[$this->closure] = $this;
  123. $use = $this->transformUseVariables($reflector->getUseVariables());
  124. $code = $reflector->getCode();
  125. $this->mapByReference($use);
  126. $ret = \serialize(array(
  127. 'use' => $use,
  128. 'function' => $code,
  129. 'scope' => $scope,
  130. 'this' => $object,
  131. 'self' => $this->reference,
  132. ));
  133. if (static::$securityProvider !== null) {
  134. $data = static::$securityProvider->sign($ret);
  135. $ret = '@' . $data['hash'] . '.' . $data['closure'];
  136. }
  137. if (!--$this->scope->serializations && !--$this->scope->toserialize) {
  138. $this->scope = null;
  139. }
  140. return $ret;
  141. }
  142. /**
  143. * Transform the use variables before serialization.
  144. *
  145. * @param array $data The Closure's use variables
  146. * @return array
  147. */
  148. protected function transformUseVariables($data)
  149. {
  150. return $data;
  151. }
  152. /**
  153. * Implementation of Serializable::unserialize()
  154. *
  155. * @param string $data Serialized data
  156. * @throws SecurityException
  157. */
  158. public function unserialize($data)
  159. {
  160. ClosureStream::register();
  161. if (static::$securityProvider !== null) {
  162. if ($data[0] !== '@') {
  163. throw new SecurityException("The serialized closure is not signed. ".
  164. "Make sure you use a security provider for both serialization and unserialization.");
  165. }
  166. if ($data[1] !== '{') {
  167. $separator = strpos($data, '.');
  168. if ($separator === false) {
  169. throw new SecurityException('Invalid signed closure');
  170. }
  171. $hash = substr($data, 1, $separator - 1);
  172. $closure = substr($data, $separator + 1);
  173. $data = ['hash' => $hash, 'closure' => $closure];
  174. unset($hash, $closure);
  175. } else {
  176. $data = json_decode(substr($data, 1), true);
  177. }
  178. if (!is_array($data) || !static::$securityProvider->verify($data)) {
  179. throw new SecurityException("Your serialized closure might have been modified and it's unsafe to be unserialized. " .
  180. "Make sure you use the same security provider, with the same settings, " .
  181. "both for serialization and unserialization.");
  182. }
  183. $data = $data['closure'];
  184. } elseif ($data[0] === '@') {
  185. if ($data[1] !== '{') {
  186. $separator = strpos($data, '.');
  187. if ($separator === false) {
  188. throw new SecurityException('Invalid signed closure');
  189. }
  190. $hash = substr($data, 1, $separator - 1);
  191. $closure = substr($data, $separator + 1);
  192. $data = ['hash' => $hash, 'closure' => $closure];
  193. unset($hash, $closure);
  194. } else {
  195. $data = json_decode(substr($data, 1), true);
  196. }
  197. if (!is_array($data) || !isset($data['closure']) || !isset($data['hash'])) {
  198. throw new SecurityException('Invalid signed closure');
  199. }
  200. $data = $data['closure'];
  201. }
  202. $this->code = \unserialize($data);
  203. // unset data
  204. unset($data);
  205. $this->code['objects'] = array();
  206. if ($this->code['use']) {
  207. $this->scope = new ClosureScope();
  208. $this->code['use'] = $this->resolveUseVariables($this->code['use']);
  209. $this->mapPointers($this->code['use']);
  210. extract($this->code['use'], EXTR_OVERWRITE | EXTR_REFS);
  211. $this->scope = null;
  212. }
  213. $this->closure = include(ClosureStream::STREAM_PROTO . '://' . $this->code['function']);
  214. if($this->code['this'] === $this){
  215. $this->code['this'] = null;
  216. }
  217. if ($this->code['scope'] !== null || $this->code['this'] !== null) {
  218. $this->closure = $this->closure->bindTo($this->code['this'], $this->code['scope']);
  219. }
  220. if(!empty($this->code['objects'])){
  221. foreach ($this->code['objects'] as $item){
  222. $item['property']->setValue($item['instance'], $item['object']->getClosure());
  223. }
  224. }
  225. $this->code = $this->code['function'];
  226. }
  227. /**
  228. * Resolve the use variables after unserialization.
  229. *
  230. * @param array $data The Closure's transformed use variables
  231. * @return array
  232. */
  233. protected function resolveUseVariables($data)
  234. {
  235. return $data;
  236. }
  237. /**
  238. * Wraps a closure and sets the serialization context (if any)
  239. *
  240. * @param Closure $closure Closure to be wrapped
  241. *
  242. * @return self The wrapped closure
  243. */
  244. public static function from(Closure $closure)
  245. {
  246. if (static::$context === null) {
  247. $instance = new static($closure);
  248. } elseif (isset(static::$context->scope[$closure])) {
  249. $instance = static::$context->scope[$closure];
  250. } else {
  251. $instance = new static($closure);
  252. static::$context->scope[$closure] = $instance;
  253. }
  254. return $instance;
  255. }
  256. /**
  257. * Increments the context lock counter or creates a new context if none exist
  258. */
  259. public static function enterContext()
  260. {
  261. if (static::$context === null) {
  262. static::$context = new ClosureContext();
  263. }
  264. static::$context->locks++;
  265. }
  266. /**
  267. * Decrements the context lock counter and destroy the context when it reaches to 0
  268. */
  269. public static function exitContext()
  270. {
  271. if (static::$context !== null && !--static::$context->locks) {
  272. static::$context = null;
  273. }
  274. }
  275. /**
  276. * @param string $secret
  277. */
  278. public static function setSecretKey($secret)
  279. {
  280. if(static::$securityProvider === null){
  281. static::$securityProvider = new SecurityProvider($secret);
  282. }
  283. }
  284. /**
  285. * @param ISecurityProvider $securityProvider
  286. */
  287. public static function addSecurityProvider(ISecurityProvider $securityProvider)
  288. {
  289. static::$securityProvider = $securityProvider;
  290. }
  291. /**
  292. * Remove security provider
  293. */
  294. public static function removeSecurityProvider()
  295. {
  296. static::$securityProvider = null;
  297. }
  298. /**
  299. * @return null|ISecurityProvider
  300. */
  301. public static function getSecurityProvider()
  302. {
  303. return static::$securityProvider;
  304. }
  305. /**
  306. * Wrap closures
  307. *
  308. * @internal
  309. * @param $data
  310. * @param ClosureScope|SplObjectStorage|null $storage
  311. */
  312. public static function wrapClosures(&$data, SplObjectStorage $storage = null)
  313. {
  314. if($storage === null){
  315. $storage = static::$context->scope;
  316. }
  317. if($data instanceof Closure){
  318. $data = static::from($data);
  319. } elseif (is_array($data)){
  320. if(isset($data[self::ARRAY_RECURSIVE_KEY])){
  321. return;
  322. }
  323. $data[self::ARRAY_RECURSIVE_KEY] = true;
  324. foreach ($data as $key => &$value){
  325. if($key === self::ARRAY_RECURSIVE_KEY){
  326. continue;
  327. }
  328. static::wrapClosures($value, $storage);
  329. }
  330. unset($value);
  331. unset($data[self::ARRAY_RECURSIVE_KEY]);
  332. } elseif($data instanceof \stdClass){
  333. if(isset($storage[$data])){
  334. $data = $storage[$data];
  335. return;
  336. }
  337. $data = $storage[$data] = clone($data);
  338. foreach ($data as &$value){
  339. static::wrapClosures($value, $storage);
  340. }
  341. unset($value);
  342. } elseif (is_object($data) && ! $data instanceof static){
  343. if(isset($storage[$data])){
  344. $data = $storage[$data];
  345. return;
  346. }
  347. $instance = $data;
  348. $reflection = new ReflectionObject($instance);
  349. if(!$reflection->isUserDefined()){
  350. $storage[$instance] = $data;
  351. return;
  352. }
  353. $storage[$instance] = $data = $reflection->newInstanceWithoutConstructor();
  354. do{
  355. if(!$reflection->isUserDefined()){
  356. break;
  357. }
  358. foreach ($reflection->getProperties() as $property){
  359. if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
  360. continue;
  361. }
  362. $property->setAccessible(true);
  363. $value = $property->getValue($instance);
  364. if(is_array($value) || is_object($value)){
  365. static::wrapClosures($value, $storage);
  366. }
  367. $property->setValue($data, $value);
  368. };
  369. } while($reflection = $reflection->getParentClass());
  370. }
  371. }
  372. /**
  373. * Unwrap closures
  374. *
  375. * @internal
  376. * @param $data
  377. * @param SplObjectStorage|null $storage
  378. */
  379. public static function unwrapClosures(&$data, SplObjectStorage $storage = null)
  380. {
  381. if($storage === null){
  382. $storage = static::$context->scope;
  383. }
  384. if($data instanceof static){
  385. $data = $data->getClosure();
  386. } elseif (is_array($data)){
  387. if(isset($data[self::ARRAY_RECURSIVE_KEY])){
  388. return;
  389. }
  390. $data[self::ARRAY_RECURSIVE_KEY] = true;
  391. foreach ($data as $key => &$value){
  392. if($key === self::ARRAY_RECURSIVE_KEY){
  393. continue;
  394. }
  395. static::unwrapClosures($value, $storage);
  396. }
  397. unset($data[self::ARRAY_RECURSIVE_KEY]);
  398. }elseif ($data instanceof \stdClass){
  399. if(isset($storage[$data])){
  400. return;
  401. }
  402. $storage[$data] = true;
  403. foreach ($data as &$property){
  404. static::unwrapClosures($property, $storage);
  405. }
  406. } elseif (is_object($data) && !($data instanceof Closure)){
  407. if(isset($storage[$data])){
  408. return;
  409. }
  410. $storage[$data] = true;
  411. $reflection = new ReflectionObject($data);
  412. do{
  413. if(!$reflection->isUserDefined()){
  414. break;
  415. }
  416. foreach ($reflection->getProperties() as $property){
  417. if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
  418. continue;
  419. }
  420. $property->setAccessible(true);
  421. $value = $property->getValue($data);
  422. if(is_array($value) || is_object($value)){
  423. static::unwrapClosures($value, $storage);
  424. $property->setValue($data, $value);
  425. }
  426. };
  427. } while($reflection = $reflection->getParentClass());
  428. }
  429. }
  430. /**
  431. * Creates a new closure from arbitrary code,
  432. * emulating create_function, but without using eval
  433. *
  434. * @param string$args
  435. * @param string $code
  436. * @return Closure
  437. */
  438. public static function createClosure($args, $code)
  439. {
  440. ClosureStream::register();
  441. return include(ClosureStream::STREAM_PROTO . '://function(' . $args. '){' . $code . '};');
  442. }
  443. /**
  444. * Internal method used to map closure pointers
  445. * @internal
  446. * @param $data
  447. */
  448. protected function mapPointers(&$data)
  449. {
  450. $scope = $this->scope;
  451. if ($data instanceof static) {
  452. $data = &$data->closure;
  453. } elseif (is_array($data)) {
  454. if(isset($data[self::ARRAY_RECURSIVE_KEY])){
  455. return;
  456. }
  457. $data[self::ARRAY_RECURSIVE_KEY] = true;
  458. foreach ($data as $key => &$value){
  459. if($key === self::ARRAY_RECURSIVE_KEY){
  460. continue;
  461. } elseif ($value instanceof static) {
  462. $data[$key] = &$value->closure;
  463. } elseif ($value instanceof SelfReference && $value->hash === $this->code['self']){
  464. $data[$key] = &$this->closure;
  465. } else {
  466. $this->mapPointers($value);
  467. }
  468. }
  469. unset($value);
  470. unset($data[self::ARRAY_RECURSIVE_KEY]);
  471. } elseif ($data instanceof \stdClass) {
  472. if(isset($scope[$data])){
  473. return;
  474. }
  475. $scope[$data] = true;
  476. foreach ($data as $key => &$value){
  477. if ($value instanceof SelfReference && $value->hash === $this->code['self']){
  478. $data->{$key} = &$this->closure;
  479. } elseif(is_array($value) || is_object($value)) {
  480. $this->mapPointers($value);
  481. }
  482. }
  483. unset($value);
  484. } elseif (is_object($data) && !($data instanceof Closure)){
  485. if(isset($scope[$data])){
  486. return;
  487. }
  488. $scope[$data] = true;
  489. $reflection = new ReflectionObject($data);
  490. do{
  491. if(!$reflection->isUserDefined()){
  492. break;
  493. }
  494. foreach ($reflection->getProperties() as $property){
  495. if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
  496. continue;
  497. }
  498. $property->setAccessible(true);
  499. $item = $property->getValue($data);
  500. if ($item instanceof SerializableClosure || ($item instanceof SelfReference && $item->hash === $this->code['self'])) {
  501. $this->code['objects'][] = array(
  502. 'instance' => $data,
  503. 'property' => $property,
  504. 'object' => $item instanceof SelfReference ? $this : $item,
  505. );
  506. } elseif (is_array($item) || is_object($item)) {
  507. $this->mapPointers($item);
  508. $property->setValue($data, $item);
  509. }
  510. }
  511. } while($reflection = $reflection->getParentClass());
  512. }
  513. }
  514. /**
  515. * Internal method used to map closures by reference
  516. *
  517. * @internal
  518. * @param mixed &$data
  519. */
  520. protected function mapByReference(&$data)
  521. {
  522. if ($data instanceof Closure) {
  523. if($data === $this->closure){
  524. $data = new SelfReference($this->reference);
  525. return;
  526. }
  527. if (isset($this->scope[$data])) {
  528. $data = $this->scope[$data];
  529. return;
  530. }
  531. $instance = new static($data);
  532. if (static::$context !== null) {
  533. static::$context->scope->toserialize--;
  534. } else {
  535. $instance->scope = $this->scope;
  536. }
  537. $data = $this->scope[$data] = $instance;
  538. } elseif (is_array($data)) {
  539. if(isset($data[self::ARRAY_RECURSIVE_KEY])){
  540. return;
  541. }
  542. $data[self::ARRAY_RECURSIVE_KEY] = true;
  543. foreach ($data as $key => &$value){
  544. if($key === self::ARRAY_RECURSIVE_KEY){
  545. continue;
  546. }
  547. $this->mapByReference($value);
  548. }
  549. unset($value);
  550. unset($data[self::ARRAY_RECURSIVE_KEY]);
  551. } elseif ($data instanceof \stdClass) {
  552. if(isset($this->scope[$data])){
  553. $data = $this->scope[$data];
  554. return;
  555. }
  556. $instance = $data;
  557. $this->scope[$instance] = $data = clone($data);
  558. foreach ($data as &$value){
  559. $this->mapByReference($value);
  560. }
  561. unset($value);
  562. } elseif (is_object($data) && !$data instanceof SerializableClosure){
  563. if(isset($this->scope[$data])){
  564. $data = $this->scope[$data];
  565. return;
  566. }
  567. $instance = $data;
  568. $reflection = new ReflectionObject($data);
  569. if(!$reflection->isUserDefined()){
  570. $this->scope[$instance] = $data;
  571. return;
  572. }
  573. $this->scope[$instance] = $data = $reflection->newInstanceWithoutConstructor();
  574. do{
  575. if(!$reflection->isUserDefined()){
  576. break;
  577. }
  578. foreach ($reflection->getProperties() as $property){
  579. if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
  580. continue;
  581. }
  582. $property->setAccessible(true);
  583. $value = $property->getValue($instance);
  584. if(is_array($value) || is_object($value)){
  585. $this->mapByReference($value);
  586. }
  587. $property->setValue($data, $value);
  588. }
  589. } while($reflection = $reflection->getParentClass());
  590. }
  591. }
  592. }