shrink0r/monatic

View on GitHub
src/Maybe.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

namespace Shrink0r\Monatic;

use Symfony\Component\PropertyAccess\PropertyAccess;

/**
 * Wraps a given value and provides fluent access to it's underlying properties.
 */
class Maybe implements MonadInterface
{
    /**
     * @var mixed $value;
     */
    private $value;

    /**
     * @var PropertyAccess $accessor
     */
    private $accessor;

    /**
     * Wraps the given value inside a new Maybe monad.
     *
     * @param mixed $value
     *
     * @return Maybe Returns None if the given value is null.
     */
    public static function unit($value)
    {
        return new static($value);
    }

    /**
     * Returns the monad's value and applies an optional code-block before returning it.
     *
     * @param callable $codeBlock
     *
     * @return mixed
     */
    public function get(callable $codeBlock = null)
    {
        if (is_callable($codeBlock)) {
            return call_user_func($codeBlock, $this->value);
        }
        return $this->value;
    }

    /**
     * Runs the given code-block on the monad's value, if the value isn't null.
     *
     * @param callable $codeBlock Is expected to return the next value to be unitped.
     *
     * @return Maybe Returns None if the next value is null.
     */
    public function bind(callable $codeBlock)
    {
        if ($this->value === null) {
            return new None;
        }
        return static::unit(call_user_func($codeBlock, $this->value));
    }

    /**
     * Retrieves the monad's value for the underlying property by name.
     *
     * This adds syntatic sugar to the Maybe monad, allowing to chain property/array access.
     * Example: Maybe::unit($arr)->keyone->keytwo->etc->get();
     *
     * @param string $propertyName
     *
     * @return Maybe Returns None if the property/key doesn't exist or equals null.
     */
    public function __get($propertyName)
    {
        return $this->bind(function ($value) use ($propertyName) {
            return $this->accessValue($value, $propertyName);
        });
    }

    /**
     * Calls an arbitrary method on the monad's value and returns a monad with the result.
     *
     * @param string $name
     * @param array $arguments
     *
     * @return MonadInterface
     */
    public function __call($methodName, array $arguments)
    {
        return $this->bind(function ($value) use ($methodName, $arguments) {
            $callable = [ $value, $methodName ];

            $result = null;
            if (is_callable($callable)) {
                $result = call_user_func_array([$value, $methodName], $arguments);
            }

            return static::unit($result);
        });
    }

    /**
     * Creates a new Maybe monad.
     *
     * @param mixed $value
     */
    protected function __construct($value)
    {
        $this->value = ($value instanceof MonadInterface) ? $value->get() : $value;
        $this->accessor = PropertyAccess::createPropertyAccessor();
    }

    /**
     * Internal helper method that is used to retrieve an underlying value from a given context (object/array).
     * If the given context is scalar, it is returned as the resulting value.
     *
     * @param mixed $context The context to retrieve the value from.
     * @param string $key The property-name/array-key to use in order to read the value from the $context.
     *
     * @return mixed
     */
    protected function accessValue($context, $key)
    {
        if (is_array($context)) {
            return $this->accessor->getValue($context, "[{$key}]");
        }
        if (is_object($context)) {
            return $this->accessor->getValue($context, $key);
        }
        return $context;
    }
}