PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.4.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.4.2
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Dependencies / SuperClosure / SerializableClosure.php

SerializableClosure.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 3.4.2, at includes/Dependencies/SuperClosure/SerializableClosure.php

218 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php namespace WPDeveloper\BetterDocs\Dependencies\SuperClosure;
2
3 use Closure;
4 use WPDeveloper\BetterDocs\Dependencies\SuperClosure\Exception\ClosureUnserializationException;
5
6 /**
7 * This class acts as a wrapper for a closure, and allows it to be serialized.
8 *
9 * With the combined power of the Reflection API, code parsing, and the infamous
10 * `eval()` function, you can serialize a closure, unserialize it somewhere
11 * else (even a different PHP process), and execute it.
12 */
13 class SerializableClosure implements \Serializable
14 {
15 /**
16 * The closure being wrapped for serialization.
17 *
18 * @var Closure
19 */
20 private $closure;
21
22 /**
23 * The serializer doing the serialization work.
24 *
25 * @var SerializerInterface
26 */
27 private $serializer;
28
29 /**
30 * The data from unserialization.
31 *
32 * @var array
33 */
34 private $data;
35
36 /**
37 * Create a new serializable closure instance.
38 *
39 * @param Closure $closure
40 * @param SerializerInterface|null $serializer
41 */
42 public function __construct(
43 \Closure $closure,
44 SerializerInterface $serializer = null
45 ) {
46 $this->closure = $closure;
47 $this->serializer = $serializer ?: new Serializer;
48 }
49
50 /**
51 * Return the original closure object.
52 *
53 * @return Closure
54 */
55 public function getClosure()
56 {
57 return $this->closure;
58 }
59
60 /**
61 * Delegates the closure invocation to the actual closure object.
62 *
63 * Important Notes:
64 *
65 * - `ReflectionFunction::invokeArgs()` should not be used here, because it
66 * does not work with closure bindings.
67 * - Args passed-by-reference lose their references when proxied through
68 * `__invoke()`. This is an unfortunate, but understandable, limitation
69 * of PHP that will probably never change.
70 *
71 * @return mixed
72 */
73 public function __invoke()
74 {
75 return call_user_func_array($this->closure, func_get_args());
76 }
77
78 /**
79 * Clones the SerializableClosure with a new bound object and class scope.
80 *
81 * The method is essentially a wrapped proxy to the Closure::bindTo method.
82 *
83 * @param mixed $newthis The object to which the closure should be bound,
84 * or NULL for the closure to be unbound.
85 * @param mixed $newscope The class scope to which the closure is to be
86 * associated, or 'static' to keep the current one.
87 * If an object is given, the type of the object will
88 * be used instead. This determines the visibility of
89 * protected and private methods of the bound object.
90 *
91 * @return SerializableClosure
92 * @link http://www.php.net/manual/en/closure.bindto.php
93 */
94 public function bindTo($newthis, $newscope = 'static')
95 {
96 return new self(
97 $this->closure->bindTo($newthis, $newscope),
98 $this->serializer
99 );
100 }
101
102 /**
103 * Serializes the code, context, and binding of the closure.
104 *
105 * @return string|null
106 * @link http://php.net/manual/en/serializable.serialize.php
107 */
108 public function serialize()
109 {
110 try {
111 $this->data = $this->data ?: $this->serializer->getData($this->closure, true);
112 return serialize($this->data);
113 } catch (\Exception $e) {
114 trigger_error(
115 'Serialization of closure failed: ' . $e->getMessage(),
116 E_USER_NOTICE
117 );
118 // Note: The serialize() method of Serializable must return a string
119 // or null and cannot throw exceptions.
120 return null;
121 }
122 }
123
124 /**
125 * Unserializes the closure.
126 *
127 * Unserializes the closure's data and recreates the closure using a
128 * simulation of its original context. The used variables (context) are
129 * extracted into a fresh scope prior to redefining the closure. The
130 * closure is also rebound to its former object and scope.
131 *
132 * @param string $serialized
133 *
134 * @throws ClosureUnserializationException
135 * @link http://php.net/manual/en/serializable.unserialize.php
136 */
137 public function unserialize($serialized)
138 {
139 // Unserialize the closure data and reconstruct the closure object.
140 $this->data = unserialize($serialized);
141 $this->closure = __reconstruct_closure($this->data);
142
143 // Throw an exception if the closure could not be reconstructed.
144 if (!$this->closure instanceof Closure) {
145 throw new ClosureUnserializationException(
146 'The closure is corrupted and cannot be unserialized.'
147 );
148 }
149
150 // Rebind the closure to its former binding and scope.
151 if ($this->data['binding'] || $this->data['isStatic']) {
152 $this->closure = $this->closure->bindTo(
153 $this->data['binding'],
154 $this->data['scope']
155 );
156 }
157 }
158
159 /**
160 * Returns closure data for `var_dump()`.
161 *
162 * @return array
163 */
164 public function __debugInfo()
165 {
166 return $this->data ?: $this->serializer->getData($this->closure, true);
167 }
168 }
169
170 /**
171 * Reconstruct a closure.
172 *
173 * HERE BE DRAGONS!
174 *
175 * The infamous `eval()` is used in this method, along with the error
176 * suppression operator, and variable variables (i.e., double dollar signs) to
177 * perform the unserialization logic. I'm sorry, world!
178 *
179 * This is also done inside a plain function instead of a method so that the
180 * binding and scope of the closure are null.
181 *
182 * @param array $__data Unserialized closure data.
183 *
184 * @return Closure|null
185 * @internal
186 */
187 function __reconstruct_closure(array $__data)
188 {
189 // Simulate the original context the closure was created in.
190 foreach ($__data['context'] as $__var_name => &$__value) {
191 if ($__value instanceof SerializableClosure) {
192 // Unbox any SerializableClosures in the context.
193 $__value = $__value->getClosure();
194 } elseif ($__value === Serializer::RECURSION) {
195 // Track recursive references (there should only be one).
196 $__recursive_reference = $__var_name;
197 }
198
199 // Import the variable into this scope.
200 ${$__var_name} = $__value;
201 }
202
203 // Evaluate the code to recreate the closure.
204 try {
205 if (isset($__recursive_reference)) {
206 // Special handling for recursive closures.
207 @eval("\${$__recursive_reference} = {$__data['code']};");
208 $__closure = ${$__recursive_reference};
209 } else {
210 @eval("\$__closure = {$__data['code']};");
211 }
212 } catch (\ParseError $e) {
213 // Discard the parse error.
214 }
215
216 return isset($__closure) ? $__closure : null;
217 }
218