PHP Classes

File: src/webfan/hps/SerializeCode.php

Recommend this page to a friend!
  Classes of Till Wehowski   Lazy PHP Event Handler   src/webfan/hps/SerializeCode.php   Download  
File: src/webfan/hps/SerializeCode.php
Role: Class source
Content type: text/plain
Description: Class source
Class: Lazy PHP Event Handler
Register event handlers to be loaded dynamically
Author: By
Last change:
Date: 2 years ago
Size: 7,835 bytes
 

Contents

Class file image Download
<?php
namespace webfan\hps;
use
SuperClosure;
use \
Closure;
use
SuperClosure\Exception\ClosureUnserializationException;

/**
 * This class acts as a wrapper for a closure, and allows it to be serialized.
 *
 * With the combined power of the Reflection API, code parsing, and the infamous
 * `eval()` function, you can serialize a closure, unserialize it somewhere
 * else (even a different PHP process), and execute it.
 */
class SerializeCode implements \Serializable
{

   
/**
     * The closure being wrapped for serialization.
     *
     * @var Closure
     */
   
protected $closure;

   
/**
     * The serializer doing the serialization work.
     *
     * @var SerializerInterface
     */
   
protected $serializer;

   
/**
     * The data from unserialization.
     *
     * @var array
     */
   
protected $data;

 
   
/**
     * Create a new serializable closure instance.
     *
     * @param Closure $closure
     * @param SerializerInterface|null $serializer
     */
   
public function __construct(
      
// \Closure $closure,
       
$closure,
        \
SuperClosure\SerializerInterface $serializer = null
   
) {
       
$this->closure = $closure;
       
$this->serializer = $serializer ?: new CodeSerializer;
    }

   
/**
     * Return the original closure object.
     *
     * @return Closure
     */
   
public function getClosure()
    {
        return
$this->closure;
    }

   
/**
     * Delegates the closure invocation to the actual closure object.
     *
     * Important Notes:
     *
     * - `ReflectionFunction::invokeArgs()` should not be used here, because it
     * does not work with closure bindings.
     * - Args passed-by-reference lose their references when proxied through
     * `__invoke()`. This is an unfortunate, but understandable, limitation
     * of PHP that will probably never change.
     *
     * @return mixed
     */
   
public function __invoke()
    {
        return
call_user_func_array($this->closure, func_get_args());
    }

   
/**
     * Clones the SerializableClosure with a new bound object and class scope.
     *
     * The method is essentially a wrapped proxy to the Closure::bindTo method.
     *
     * @param mixed $newthis The object to which the closure should be bound,
     * or NULL for the closure to be unbound.
     * @param mixed $newscope The class scope to which the closure is to be
     * associated, or 'static' to keep the current one.
     * If an object is given, the type of the object will
     * be used instead. This determines the visibility of
     * protected and private methods of the bound object.
     *
     * @return SerializableClosure
     * @link http://www.php.net/manual/en/closure.bindto.php
     */
   
public function bindTo($newthis, $newscope = 'static')
    {
        return new
self(
           
$this->closure->bindTo($newthis, $newscope),
           
$this->serializer
       
);
    }

   
/**
     * Serializes the code, context, and binding of the closure.
     *
     * @return string|null
     * @link http://php.net/manual/en/serializable.serialize.php
     */
   
public function serialize()
    {
        try {
           
$this->data = $this->data ?: $this->serializer->getData($this->closure, true);
           
$bin = new \frdl\webfan\Serialize\Binary\bin();
           
   
           
       
$this->data['context'] = $bin->serialize($this->data['context']);
       
$this->data['code'] = $bin->serialize($this->data['code']);
       
$this->data['scope'] = $bin->serialize($this->data['scope']);
       
$this->data['binding'] = $bin->serialize($this->data['binding']);
       
           
           
//$result = $bin->serialize(serialize($this->data));
           
$result = serialize($this->data);
  
// print_r($result);
           
           
           
          
return $result;
           

           
           
        } catch (\
Exception $e) {
           
trigger_error(
               
'Serialization of closure failed: ' . $e->getMessage(),
               
E_USER_NOTICE
           
);
           
// Note: The serialize() method of Serializable must return a string
            // or null and cannot throw exceptions.
           
return null;
        }
    }

   
/**
     * Unserializes the closure.
     *
     * Unserializes the closure's data and recreates the closure using a
     * simulation of its original context. The used variables (context) are
     * extracted into a fresh scope prior to redefining the closure. The
     * closure is also rebound to its former object and scope.
     *
     * @param string $serialized
     *
     * @throws ClosureUnserializationException
     * @link http://php.net/manual/en/serializable.unserialize.php
     */
   
public function unserialize($serialized)
    {
       
// Unserialize the closure data and reconstruct the closure object.
       
$bin = new \frdl\webfan\Serialize\Binary\bin();
       
               
        
// $b = $bin->unserialize($serialized);
         // var_dump($b);
       
       
       
       
$this->data = unserialize($serialized);
       
       
$this->data['context'] = $bin->unserialize($this->data['context']);
       
$this->data['code'] = $bin->unserialize($this->data['code']);
       
$this->data['scope'] = $bin->unserialize($this->data['scope']);
       
$this->data['binding'] = $bin->unserialize($this->data['binding']);
       
        
       
       
$this->closure = __reconstruct_closure($this->data);
       

       
       
// Throw an exception if the closure could not be reconstructed.
       
if (!$this->closure instanceof \Closure) {
            throw new \
SuperClosure\Exception\ClosureUnserializationException(
               
'The closure is corrupted and cannot be unserialized.'
           
);
        }

       
// Rebind the closure to its former binding and scope.
       
if ($this->data['binding'] || $this->data['isStatic']) {
           
$this->closure = $this->closure->bindTo(
               
$this->data['binding'],
               
$this->data['scope']
            );
        }
       
       
//print_r($this->closure);
   
}

   
/**
     * Returns closure data for `var_dump()`.
     *
     * @return array
     */
   
public function __debugInfo()
    {
        return
$this->data ?: $this->serializer->getData($this->closure, true);
    }
}

/**
 * Reconstruct a closure.
 *
 * HERE BE DRAGONS!
 *
 * The infamous `eval()` is used in this method, along with the error
 * suppression operator, and variable variables (i.e., double dollar signs) to
 * perform the unserialization logic. I'm sorry, world!
 *
 * This is also done inside a plain function instead of a method so that the
 * binding and scope of the closure are null.
 *
 * @param array $__data Unserialized closure data.
 *
 * @return Closure|null
 * @internal
 */
function __reconstruct_closure(array $__data)
{
   
// Simulate the original context the closure was created in.
   
foreach ($__data['context'] as $__var_name => &$__value) {
        if (
$__value instanceof SerializeCode) {
           
// Unbox any SerializableClosures in the context.
           
$__value = $__value->getClosure();
        } elseif (
$__value === CodeSerializer::RECURSION) {
           
// Track recursive references (there should only be one).
           
$__recursive_reference = $__var_name;
        }

       
// Import the variable into this scope.
       
${$__var_name} = $__value;
    }

   
// Evaluate the code to recreate the closure.
   
try {
        if (isset(
$__recursive_reference)) {
           
// Special handling for recursive closures.
           
eval("\${$__recursive_reference} = {$__data['code']};");
           
$__closure = ${$__recursive_reference};
        } else {
            eval(
"\$__closure = {$__data['code']};");
        }
    } catch (\
ParseError $e) {
       
// Discard the parse error.
   
}

    return isset(
$__closure) ? $__closure : null;
}