PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.0
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 4.9.0, at includes/Dependencies/SuperClosure/SerializableClosure.php

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