PHP Classes

File: src/webfan/hps/CodeSerializer.php

Recommend this page to a friend!
  Classes of Till Wehowski   Lazy PHP Event Handler   src/webfan/hps/CodeSerializer.php   Download  
File: src/webfan/hps/CodeSerializer.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,572 bytes
 

Contents

Class file image Download
<?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.'
           
);
        }
    }
}