1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 
<?php

declare(strict_types=1);

namespace Wtf;

use Psr\Container\ContainerInterface;

class Root
{
    /**
     * PSR-11 Container.
     *
     * @var ContainerInterface
     */
    protected $container;

    /**
     * Storage for magic getter/setter.
     *
     * @var array
     */
    protected $data = [];

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * Call method or getter/setter for property.
     *
     * @param string $method
     * @param array  $params
     *
     * @throws \Exception if method not implemented in class
     *
     * @return mixed Data from object property
     */
    public function __call(?string $method = null, array $params = [])
    {
        $parts = \preg_split('/([A-Z][^A-Z]*)/', $method, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
        $type = \array_shift($parts);

        // Call current class method
        if (\method_exists($this, $method)) {
            return \call_user_func_array([$this, $method], $params);
        }

        // Call method from container
        if ($this->container->has($method)) {
            return \call_user_func_array($this->container[$method], $params);
        }

        // Call getter/setter
        if ('get' === $type || 'set' === $type) {
            $property = \strtolower(\implode('_', $parts));
            $params = (isset($params[0])) ? [$property, $params[0]] : [$property];

            return \call_user_func_array([$this, $type], $params);
        }

        throw new \Exception('Method "'.$method.'" not implemented.');
    }

    /**
     * Magic get from container.
     *
     * @param string $name
     *
     * @return mixed
     */
    public function __get(string $name)
    {
        if ($this->container->has($name)) {
            return $this->container->get($name);
        }

        return null;
    }

    /**
     * Get property data, eg get('post_id').
     *
     * @param string $property
     * @param mixed  $default  Default value if property not exists
     *
     * @return mixed
     */
    public function get(string $property, $default = null)
    {
        return $this->data[$property] ?? $default;
    }

    /**
     * Set property data, eg set('post_id',1).
     *
     * @param string $property
     * @param mixed  $data
     *
     * @return $this
     */
    public function set(string $property, $data = null): self
    {
        $this->data[$property] = $data;

        return $this;
    }

    /**
     * Return all entity data as array.
     *
     * @return array
     */
    public function getData(): array
    {
        return $this->data;
    }

    /**
     * Set all data to entity.
     *
     * @param array $data
     *
     * @return Root
     */
    public function setData(array $data): self
    {
        $this->data = \array_merge($this->data, $data);

        return $this;
    }
}