ICanBoogie/Accessor v1.0.2
  • Namespace
  • Class

Namespaces

  • ICanBoogie
    • Accessor

Classes

  • AccessorReflection

Interfaces

  • HasAccessor

Traits

  • AccessorCamelTrait
  • AccessorSnakeTrait
  • AccessorTrait
  • FormatAsCamel
  • FormatAsSnake
  • SerializableTrait
  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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 
<?php

/*
 * This file is part of the ICanBoogie package.
 *
 * (c) Olivier Laviale <olivier.laviale@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace ICanBoogie\Accessor;

use ICanBoogie\PropertyNotDefined;
use ICanBoogie\PropertyNotReadable;
use ICanBoogie\PropertyNotWritable;

/**
 * Implements ICanBoogie's accessor pattern.
 *
 * @package ICanBoogie\Accessor
 */
trait AccessorTrait
{
    use FormatAsSnake;

    /**
     * @inheritdoc
     */
    public function __get($property)
    {
        return $this->accessor_get($property);
    }

    /**
     * @inheritdoc
     */
    public function __set($property, $value)
    {
        $this->accessor_set($property, $value);
    }

    /**
     * Whether an object has a property.
     *
     * The property can be defined by the class or handled by a getter or setter, or both.
     *
     * @param string $property
     *
     * @return bool `true` if the object has a property, `false` otherwise.
     */
    public function has_property($property)
    {
        return property_exists($this, $property)
        || $this->has_method(static::accessor_format($property, HasAccessor::ACCESSOR_TYPE_GETTER))
        || $this->has_method(static::accessor_format($property, HasAccessor::ACCESSOR_TYPE_GETTER, HasAccessor::ACCESSOR_IS_LAZY))
        || $this->has_method(static::accessor_format($property, HasAccessor::ACCESSOR_TYPE_SETTER))
        || $this->has_method(static::accessor_format($property, HasAccessor::ACCESSOR_TYPE_SETTER, HasAccessor::ACCESSOR_IS_LAZY));
    }

    /**
     * Whether an object has a method.
     *
     * @param string $method
     *
     * @return bool `true` if the object has a method, `false` otherwise.
     */
    public function has_method($method)
    {
        return method_exists($this, $method);
    }

    /**
     * Returns the value of an inaccessible property.
     *
     * Multiple callbacks are tried in order to retrieve the value of the property:
     *
     * 1. `get_<property>`: Get and return the value of the property.
     * 2. `lazy_get_<property>`: Get, set and return the value of the property. Because new
     * properties are created as public the callback is only called once which is ideal for lazy
     * loading.
     * 3. The prototype is queried for callbacks for the `get_<property>` and
     * `lazy_get_<property>` methods.
     * 4. Finally, the `ICanBoogie\Object::property` event is fired to try and retrieve the value
     * of the property.
     *
     * @param string $property
     *
     * @return mixed
     */
    private function accessor_get($property)
    {
        $method = static::accessor_format($property, HasAccessor::ACCESSOR_TYPE_GETTER);

        if ($this->has_method($method))
        {
            return $this->$method();
        }

        $method = static::accessor_format($property, HasAccessor::ACCESSOR_TYPE_GETTER, HasAccessor::ACCESSOR_IS_LAZY);

        if ($this->has_method($method))
        {
            return $this->$property = $this->$method();
        }

        $this->assert_property_is_readable($property);
    } //@codeCoverageIgnore

    /**
     * Sets the value of an inaccessible property.
     *
     * The method is called because the property does not exists, its visibility is
     * _protected_ or _private_, or because although it is visible is was unset and is no
     * longer accessible.
     *
     * A `set_<property>` method can be used the handle virtual properties, for instance a
     * `minute` property that would alter a `second` property.
     *
     * A `lazy_set_<property>` method can be used to set properties that are protected or
     * private, which can be used to make properties write-only for example.
     *
     * @param string $property
     * @param mixed $value
     */
    private function accessor_set($property, $value)
    {
        $method = static::accessor_format($property, HasAccessor::ACCESSOR_TYPE_SETTER);

        if ($this->has_method($method))
        {
            $this->$method($value);

            return;
        }

        $method = static::accessor_format($property, HasAccessor::ACCESSOR_TYPE_SETTER, HasAccessor::ACCESSOR_IS_LAZY);

        if ($this->has_method($method))
        {
            $this->$property = $this->$method($value);

            return;
        }

        $this->assert_property_is_writable($property);

        $this->$property = $value;
    }

    /**
     * Asserts that a property is readable.
     *
     * @param string $property
     *
     * @throws PropertyNotDefined when the property is not defined.
     * @throws PropertyNotReadable when the property is not accessible or is write-only
     * (the property is not defined and only a setter is available).
     */
    private function assert_property_is_readable($property)
    {
        $reflexion_class = new \ReflectionClass($this);

        try
        {
            $reflexion_property = $reflexion_class->getProperty($property);

            if (!$reflexion_property->isPublic())
            {
                throw new PropertyNotReadable([ $property, $this ]);
            }
        }
        catch (\ReflectionException $e)
        {
            #
            # An exception may occur if the property is not defined, we don't care about that.
            #
        }

        $this->assert_no_accessor($property, HasAccessor::ACCESSOR_TYPE_SETTER, 'ICanBoogie\PropertyNotReadable');

        $properties = array_keys(get_object_vars($this));

        if ($properties)
        {
            throw new PropertyNotDefined(sprintf('Unknown or inaccessible property "%s" for object of class "%s" (available properties: %s).', $property, get_class($this), implode(', ', $properties)));
        }

        throw new PropertyNotDefined([ $property, $this ]);
    }

    /**
     * Asserts that a property is writable.
     *
     * @param string $property
     *
     * @throws PropertyNotWritable when the property doesn't exists, has no lazy getter and is
     * not public; or when only a getter is implemented.
     */
    private function assert_property_is_writable($property)
    {
        if (property_exists($this, $property) && !$this->has_method(static::accessor_format($property, HasAccessor::ACCESSOR_TYPE_GETTER, HasAccessor::ACCESSOR_IS_LAZY)))
        {
            $reflection = new \ReflectionObject($this);
            $property_reflection = $reflection->getProperty($property);

            if (!$property_reflection->isPublic())
            {
                throw new PropertyNotWritable([ $property, $this ]);
            }

            return;
        }

        $this->assert_no_accessor($property, HasAccessor::ACCESSOR_TYPE_GETTER, 'ICanBoogie\PropertyNotWritable');
    }

    /**
     * Asserts that an accessor is not implemented.
     *
     * @param string $property
     * @param string $type One of {@link HasAccessor::ACCESSOR_TYPE_GETTER}
     * and {@link HasAccessor::ACCESSOR_TYPE_SETTER}.
     * @param string $exception_class
     */
    private function assert_no_accessor($property, $type, $exception_class)
    {
        if ($this->has_method(static::accessor_format($property, $type)))
        {
            throw new $exception_class([ $property, $this ]);
        }
    }
}
ICanBoogie/Accessor v1.0.2 API documentation generated by ApiGen