Snippets

Reach Media Network "Context" for API using "PropertyBag"

Created by Nathan Davis last modified
<?php

namespace REACH\Runtime;

/**
 * Class Context. Provides a singleton "property bag" which can be used to store pieces of state which need to be
 * accessed from unrelated/decoupled code. For example, the API code may set the username property to the requesting
 * user and REACH\model\data_list_change_event can then read the username property when saving records to the database.
 * This allows the data model to remain loosely coupled to the API code.
 *
 * Properties which would be expensive to compute (e.g. username), can be made lazy by assigning a callable to the
 * property instead:
 *
 * $token = security_token::decode($token_string_input); //surprisingly, token does not actually contain the username
 *
 * $context->username = function() use($token) {
 *      return $token->get_entity()->name; //get_entity incurs database access and is "expensive"
 * };
 *
 * //now, using $context->username will result in the callable being executed and the result overwriting
 * //$context->username.
 *
 * The future design intention is that this context object would be transparently serialized and sent along with jobs
 * to lambda. In this way, the security token and username would be transparently sent to lambda without the developer
 * lifting a single finger.
 *
 * @package REACH\Runtime
 */
class Context extends PropertyBag {
    /**
     * @var Context $_instance the singleton instance of Context
     */
    private static $_instance;

    /**
     * Context constructor. Private to prevent direct instantiation.
     */
    private function __construct() {}

    /**
     * Acquires the singleton instance of the Context class
     * @return Context
     */
    public static function getInstance() : Context {
        if(empty(Context::$_instance)) {
            Context::$_instance = new Context();
        }

        return Context::$_instance;
    }

    /**
     * Acquires the value of a property on the Context.
     *
     * @param $name
     * @return mixed
     * @throws \Exception
     */
    public function __get($name) {
        $value = parent::__get($name);

        if(is_callable($value)) {
            $value = call_user_func($value);
            parent::__set($name, $value);
        }

        return $value;
    }
}
<?php

namespace REACH\Runtime;

/**
 * A generic PropertyBag implementation.
 *
 * @package REACH\Runtime
 */
class PropertyBag implements \ArrayAccess, \Iterator, \Countable {
    /**
     * @var array $properties the property bag.
     */
    private $properties = [];

    /**
     * @var (array|null) $iterator_keys keys temporarily saved for iterator
     */
    private $iterator_keys = null;

    /**
     * Sets a property value on the PropertyBag object.

     * @param string $name the name of the property to set
     * @param mixed $value the value to set the property to
     */
    public function __set($name, $value) {
        $this->properties[$name] = $value;
    }

    /**
     * Acquires the value of a property on the PropertyBag.
     *
     * @param $name
     * @return mixed
     * @throws \Exception
     */
    public function __get($name) {
        if(!array_key_exists($name, $this->properties)) {
            throw new \Exception("index `{$name}` does not exist");
        }

        return $this->properties[$name];
    }

    /**
     * Checks if the PropertyBag has a specific property.
     *
     * @param string $name name of the property to check if isset.
     * @return bool
     */
    public function __isset($name) {
        return isset($this->properties[$name]);
    }

    /**
     * Deletes a property from the Context.
     *
     * @param string $name
     */
    public function __unset($name) {
        unset($this->properties[$name]);
    }

    /**
     * Converts the PropertyBag into a plain associative array.
     * @param boolean $eager_load_properties if true, automatically execute callables
     * @return array
     */
    public function to_array($eager_load_properties = false) {
        if($eager_load_properties) {
            foreach($this as $key => $value) {
                if(is_callable($value)) {
                    $this->__set($key, call_user_func($value));
                }
            }
        }

        return $this->properties;
    }

    /**
     * Serializes the PropertyBag into a string.
     * @param boolean $eager_load_properties if true, automatically execute callables
     * @param callable|null $serializer serializer function, accepts one parameter, the object to serialize
     * @return string
     */
    public function serialize($eager_load_properties = false, Callable $serializer = null) {
        if(empty($serializer)) {
            $serializer = 'json_encode';
        }

        return call_user_func($serializer, $this->to_array($eager_load_properties));
    }

    /**
     * Returns the properties defined on the Context.
     * @return array
     */
    public function keys(): array {
        return array_keys($this->properties);
    }

    /**
     * Returns the values of the properties defined on the Context;
     * @return array
     */
    public function values(): array {
        return array_values($this->properties);
    }

    /**
     * @inheritDoc
     */
    public function current() {
        return $this->__get($this->iterator_keys[0]);
    }

    /**
     * @inheritDoc
     */
    public function next() {
        array_shift($this->iterator_keys);
    }

    /**
     * @inheritDoc
     */
    public function key() {
        return $this->iterator_keys[0];
    }

    /**
     * @inheritDoc
     */
    public function valid() {
        return count($this->iterator_keys) > 0;
    }

    /**
     * @inheritDoc
     */
    public function rewind() {
        $this->iterator_keys = array_keys($this->properties);
    }

    /**
     * @inheritDoc
     */
    public function offsetExists($offset) {
        return $this->__isset($offset);
    }

    /**
     * @inheritDoc
     * @throws \Exception
     */
    public function offsetGet($offset) {
        return $this->__get($offset);
    }

    /**
     * @inheritDoc
     */
    public function offsetSet($offset, $value) {
        $this->__set($offset, $value);
    }

    /**
     * @inheritDoc
     */
    public function offsetUnset($offset) {
        $this->__unset($offset);
    }

    /**
     * @inheritDoc
     */
    public function count() {
        return count(array_keys($this->properties));
    }
}

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.