<?php  
namespace webfan\hps; 
 
use SuperClosure; 
 
use SuperClosure\Analyzer\AstAnalyzer as DefaultAnalyzer; 
use SuperClosure\Analyzer\ClosureAnalyzer; 
use SuperClosure\Exception\ClosureSerializationException; 
use SuperClosure\Exception\ClosureUnserializationException; 
 
/** 
 * This is the serializer class used for serializing Closure objects. 
 * 
 * We're abstracting away all the details, impossibilities, and scary things 
 * that happen within. 
 */ 
//extends \SuperClosure\Serializer 
class CodeSerializer implements \SuperClosure\SerializerInterface 
{ 
    /** 
     * The special value marking a recursive reference to a closure. 
     * 
     * @var string 
     */ 
    const RECURSION = "{{RECURSION}}"; 
 
    /** 
     * The keys of closure data required for serialization. 
     * 
     * @var array 
     */ 
    protected static $dataToKeep = [ 
        'code'     => true, 
        'context'  => true, 
        'binding'  => true, 
        'scope'    => true, 
        'isStatic' => true, 
    ]; 
 
    /** 
     * The closure analyzer instance. 
     * 
     * @var ClosureAnalyzer 
     */ 
    protected $analyzer; 
 
    /** 
     * The HMAC key to sign serialized closures. 
     * 
     * @var string 
     */ 
    private $signingKey; 
 
    /** 
     * Create a new serializer instance. 
     * 
     * @param ClosureAnalyzer|null $analyzer   Closure analyzer instance. 
     * @param string|null          $signingKey HMAC key to sign closure data. 
     */ 
    public function __construct( 
        \SuperClosure\Analyzer\ClosureAnalyzer $analyzer = null, 
        $signingKey = null 
    ) { 
        $this->analyzer = $analyzer ?: new  \SuperClosure\Analyzer\DefaultAnalyzer; 
        $this->signingKey = $signingKey; 
    } 
 
    /** 
     * @inheritDoc 
     */ 
    public function serialize(\Closure $closure) 
    { 
        $serialized = serialize(new SerializeCode($closure, $this)); 
         
        if ($serialized === null) { 
            throw new \SuperClosure\Exception\ClosureSerializationException( 
                'The closure could not be serialized.' 
            ); 
        } 
 
        if ($this->signingKey) { 
            $signature = $this->calculateSignature($serialized); 
            $serialized = '%' . base64_encode($signature) . $serialized; 
        } 
 
        return $serialized; 
    } 
 
    /** 
     * @inheritDoc 
     */ 
    public function unserialize($serialized) 
    { 
        // Strip off the signature from the front of the string. 
        $signature = null; 
        if ($serialized[0] === '%') { 
            $signature = base64_decode(substr($serialized, 1, 44)); 
            $serialized = substr($serialized, 45); 
        } 
 
        // If a key was provided, then verify the signature. 
        if ($this->signingKey) { 
            $this->verifySignature($signature, $serialized); 
        } 
 
        set_error_handler(function () {}); 
        $unserialized = unserialize($serialized); 
        restore_error_handler(); 
        if ($unserialized === false) { 
            throw new \SuperClosure\Exception\ClosureUnserializationException( 
                'The closure could not be unserialized.' 
            ); 
        } elseif (!$unserialized instanceof SerializeCode) { 
            throw new \SuperClosure\Exception\ClosureUnserializationException( 
                'The closure did not unserialize to a SuperClosure.' 
            ); 
        } 
 
        return $unserialized->getClosure(); 
    } 
 
    /** 
     * @inheritDoc 
     */ 
    public function getData(\Closure $closure, $forSerialization = false) 
    { 
        // Use the closure analyzer to get data about the closure. 
        $data = $this->analyzer->analyze($closure); 
 
        // If the closure data is getting retrieved solely for the purpose of 
        // serializing the closure, then make some modifications to the data. 
        if ($forSerialization) { 
            // If there is no reference to the binding, don't serialize it. 
            if (!$data['hasThis']) { 
                $data['binding'] = null; 
            } 
 
            // Remove data about the closure that does not get serialized. 
            $data = array_intersect_key($data, self::$dataToKeep); 
 
            // Wrap any other closures within the context. 
            foreach ($data['context'] as &$value) { 
                if ($value instanceof \Closure) { 
                    $value = ($value === $closure) 
                        ? self::RECURSION 
                        : new SerializeCode($value, $this); 
                } 
            } 
        } 
 
        return $data; 
    } 
 
    /** 
     * Recursively traverses and wraps all Closure objects within the value. 
     * 
     * NOTE: THIS MAY NOT WORK IN ALL USE CASES, SO USE AT YOUR OWN RISK. 
     * 
     * @param mixed $data Any variable that contains closures. 
     * @param SerializerInterface $serializer The serializer to use. 
     */ 
    public static function wrapClosures(&$data, \SuperClosure\SerializerInterface $serializer) 
    { 
        if ($data instanceof \Closure) { 
            // Handle and wrap closure objects. 
            $reflection = new \ReflectionFunction($data); 
            if ($binding = $reflection->getClosureThis()) { 
                self::wrapClosures($binding, $serializer); 
                $scope = $reflection->getClosureScopeClass(); 
                $scope = $scope ? $scope->getName() : 'static'; 
                $data = $data->bindTo($binding, $scope); 
            } 
            $data = new SerializeCode($data, $serializer); 
 
        } elseif (is_array($data) || $data instanceof \stdClass || $data instanceof \Traversable) { 
            // Handle members of traversable values. 
            foreach ($data as &$value) { 
                self::wrapClosures($value, $serializer); 
            } 
        } elseif (is_object($data) && !$data instanceof \Serializable) { 
            // Handle objects that are not already explicitly serializable. 
            $reflection = new \ReflectionObject($data); 
            if (!$reflection->hasMethod('__sleep')) { 
                foreach ($reflection->getProperties() as $property) { 
                    if ($property->isPrivate() || $property->isProtected()) { 
                        $property->setAccessible(true); 
                    } 
                    $value = $property->getValue($data); 
                    self::wrapClosures($value, $serializer); 
                    $property->setValue($data, $value); 
                } 
            } 
        } 
    } 
 
    /** 
     * Calculates a signature for a closure's serialized data. 
     * 
     * @param string $data Serialized closure data. 
     * 
     * @return string Signature of the closure's data. 
     */ 
    private function calculateSignature($data) 
    { 
        return hash_hmac('sha256', $data, $this->signingKey, true); 
    } 
 
    /** 
     * Verifies the signature for a closure's serialized data. 
     * 
     * @param string $signature The provided signature of the data. 
     * @param string $data      The data for which to verify the signature. 
     * 
     * @throws ClosureUnserializationException if the signature is invalid. 
     */ 
    private function verifySignature($signature, $data) 
    { 
        // Verify that the provided signature matches the calculated signature. 
        if (!hash_equals($signature, $this->calculateSignature($data))) { 
            throw new \SuperClosure\Exception\ClosureUnserializationException('The signature of the' 
                . ' closure\'s data is invalid, which means the serialized ' 
                . 'closure has been modified and is unsafe to unserialize.' 
            ); 
        } 
    } 
} 
 
 |