<?php

namespace Flagception\Constraint\Provider;

use Symfony\Component\ExpressionLanguage\ExpressionFunction;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;

/**
 * Class DateProvider
 *
 * @author Michel Chowanski <michel.chowanski@bestit-online.de>
 * @package Flagception\Constraint\Provider
 */
class DateProvider implements ExpressionFunctionProviderInterface
{
    /**
     * {@inheritdoc}
     */
    public function getFunctions()
    {
        return array(
            new ExpressionFunction(
                'date',
                function ($value) {
                    return sprintf('date(%1$s, time())', $value);
                },
                function ($arguments, $str) {
                    return date($str, time());
                }
            ),
        );
    }
}


<?php

namespace Flagception\Manager;

use Flagception\Activator\FeatureActivatorInterface;
use Flagception\Decorator\ContextDecoratorInterface;
use Flagception\Model\Context;

/**
 * Class FeatureManager
 *
 * @author Michel Chowanski <michel.chowanski@bestit-online.de>
 * @package Flagception\Manager
 */
class FeatureManager implements FeatureManagerInterface
{
    /**
     * The feature activator
     *
     * @var FeatureActivatorInterface
     */
    private $activator;

    /**
     * The context decorator
     *
     * @var ContextDecoratorInterface|null
     */
    private $decorator;

    /**
     * FeatureManager constructor.
     *
     * @param FeatureActivatorInterface $activator
     * @param ContextDecoratorInterface|null $decorator
     */
    public function __construct(FeatureActivatorInterface $activator, ContextDecoratorInterface $decorator = null)
    {
        $this->activator = $activator;
        $this->decorator = $decorator;
    }

    /**
     * {@inheritdoc}
     */
    public function isActive($name, Context $context = null)
    {
        if ($context === null) {
            $context = new Context();
        }

        if ($this->decorator !== null) {
            $context = $this->decorator->decorate($context);
        }

        return $this->activator->isActive($name, $context);
    }
}


<?php

namespace Flagception\Activator;

use Flagception\Exception\InvalidArgumentException;
use Flagception\Model\Context;

/**
 * Activator for fetching feature states by cookie
 *
 * @author Michel Chowanski <michel.chowanski@bestit-online.de>
 * @package Flagception\Activator
 */
class CookieActivator implements FeatureActivatorInterface
{
    /**
     * Activator should act as whitelist
     */
    const WHITELIST = 'whitelist';

    /**
     * Activator should act as blacklist
     */
    const BLACKLIST = 'blacklist';

    /**
     * Features collection
     *
     * @var array
     */
    private $features;

    /**
     * Cookie name
     *
     * @var string
     */
    private $name;

    /**
     * Cookie separator
     *
     * @var string
     */
    private $separator;

    /**
     * Mode (whitelist / blacklist)
     *
     * @var string
     */
    private $mode;

    /**
     * CookieActivator constructor.
     *
     * @param array $features
     * @param string $name
     * @param string $separator
     * @param string $mode
     *
     * @throws InvalidArgumentException
     */
    public function __construct(array $features, $name = 'flagception', $separator = ',', $mode = self::WHITELIST)
    {
        if (!in_array($mode, [self::BLACKLIST, self::WHITELIST], true)) {
            throw new InvalidArgumentException(
                sprintf(
                    'Invalid mode argument for cookie activator: "%s". Expected: "whitelist" or "blacklist"',
                    $mode
                )
            );
        }

        $this->features = $features;
        $this->name = $name;
        $this->separator = $separator;
        $this->mode = $mode;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'cookie';
    }

    /**
     * {@inheritdoc}
     */
    public function isActive($name, Context $context)
    {
        // Disable features which aren't whitelisted
        if ($this->mode === self::WHITELIST && !in_array($name, $this->features, true)) {
            return false;
        }

        // Disable features which are blacklisted
        if ($this->mode === self::BLACKLIST && in_array($name, $this->features, true)) {
            return false;
        }

        // Disable if non cookie exists
        if (!array_key_exists($this->name, $_COOKIE)) {
            return false;
        }

        // Enable, if feature is set in cookie
        return in_array($name, array_map('trim', explode($this->separator, $_COOKIE[$this->name])), true);
    }
}


<?php

namespace Flagception\Model;

use Flagception\Exception\AlreadyDefinedException;
use Serializable;

/**
 * Class Context
 *
 * @author Michel Chowanski <michel.chowanski@bestit-online.de>
 * @package Flagception\Model
 */
class Context implements Serializable
{
    /**
     * Storage for all context values
     *
     * @var array
     */
    private $storage = [];

    /**
     * Add a context value. The key must be unique and cannot be replaced
     *
     * @param string $name
     * @param mixed $value
     *
     * @return void
     * @throws AlreadyDefinedException
     */
    public function add($name, $value)
    {
        if (array_key_exists($name, $this->storage)) {
            throw new AlreadyDefinedException(sprintf('Context value with key `%s` already defined', $name));
        }

        $this->storage[$name] = $value;
    }

    /**
     * Replace a context value
     *
     * @param string $name
     * @param mixed $value
     *
     * @return void
     */
    public function replace($name, $value)
    {
        $this->storage[$name] = $value;
    }

    /**
     * Get context value of given string or default value
     *
     * @param string $name
     * @param mixed $default
     *
     * @return mixed
     */
    public function get($name, $default = null)
    {
        return array_key_exists($name, $this->storage) ? $this->storage[$name] : $default;
    }

    /**
     * Get all context values (key => value pairs)
     *
     * @return array
     */
    public function all()
    {
        return $this->storage;
    }

    /**
     * Has given context value
     *
     * @param string $name
     *
     * @return bool
     */
    public function has($name)
    {
        return array_key_exists($name, $this->storage);
    }

    /**
     * {@inheritdoc}
     */
    public function serialize()
    {
        return serialize($this->storage);
    }

    /**
     * {@inheritdoc}
     */
    public function unserialize($serialized)
    {
        $this->storage = unserialize($serialized);
    }
}


<?php

namespace ByJG\AnyDataset\Core;

use ByJG\AnyDataset\Core\Exception\DatabaseException;
use ByJG\Util\XmlUtil;
use InvalidArgumentException;

/**
 * AnyDataset is a simple way to store data using only XML file.
 * Your structure is hierarquical and each "row" contains "fields" but these structure can vary for each row.
 * Anydataset files have extension ".anydata.xml" and have many classes to put and get data into anydataset xml file.
 * Anydataset class just read and write files. To search elements you need use AnyIterator
 * and IteratorFilter. Each row have a class Row.

 * XML Structure
 *
 * <anydataset>
 *    <row>
 *        <field name="fieldname1">value of fieldname 1</field>
 *        <field name="fieldname2">value of fieldname 2</field>
 *        <field name="fieldname3">value of fieldname 3</field>
 *    </row>
 *    <row>
 *        <field name="fieldname1">value of fieldname 1</field>
 *        <field name="fieldname4">value of fieldname 4</field>
 *    </row>
 * </anydataset>
 *

 * How to use:
 *
 * $any = new AnyDataset();
 *

 *
*@see Row
 * @see AnyIterator
 * @see IteratorFilter

 */
class AnyDataset
{

    /**
     * Internal structure represent the current Row
     *
     * @var Row[]
     */
    private $collection;

    /**
     * Current node anydataset works
     * @var int
     */
    private $currentRow;

    /**
     * Path to anydataset file
     * @var string
     */
    private $path;

    /**
     * @param string $file
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
     * @throws \ByJG\Util\Exception\XmlUtilException
     */
    public function __construct($file = null)
    {
        $this->collection = array();
        $this->currentRow = -1;

        $this->path = null;
        if (!is_null($file)) {
            if (!is_string($file)) {
                throw new \InvalidArgumentException('I expected a string as a file name');
            }

            $ext = pathinfo($file, PATHINFO_EXTENSION);
            if (empty($ext)) {
                $file .= '.anydata.xml';
            }
            $this->path = $file;

            $this->createFrom($this->path);
        }
    }

    /**
     * Private method used to read and populate anydataset class from specified file
     *
     * @param string $filepath Path and Filename to be read
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
     * @throws \ByJG\Util\Exception\XmlUtilException
     */
    private function createFrom($filepath)
    {
        if (file_exists($filepath)) {
            $anyDataSet = XmlUtil::createXmlDocumentFromFile($filepath);
            $this->collection = array();

            $rows = $anyDataSet->getElementsByTagName("row");
            foreach ($rows as $row) {
                $sr = new Row();
                $fields = $row->getElementsByTagName("field");
                foreach ($fields as $field) {
                    $attr = $field->attributes->getNamedItem("name");
                    if (is_null($attr)) {
                        throw new \InvalidArgumentException('Malformed anydataset file ' . basename($filepath));
                    }

                    $sr->addField($attr->nodeValue, $field->nodeValue);
                }
                $sr->acceptChanges();
                $this->collection[] = $sr;
            }
            $this->currentRow = count($this->collection) - 1;
        }
    }

    /**
     * Returns the AnyDataset XML representative structure.
     *
     * @return string XML String
     * @throws \ByJG\Util\Exception\XmlUtilException
     */
    public function xml()
    {
        return $this->getAsDom()->saveXML();
    }

    /**
     * Returns the AnyDataset XmlDocument representive object
     *
     * @return \DOMDocument XmlDocument object
     * @throws \ByJG\Util\Exception\XmlUtilException
     */
    public function getAsDom()
    {
        $anyDataSet = XmlUtil::createXmlDocumentFromStr("<anydataset></anydataset>");
        $nodeRoot = $anyDataSet->getElementsByTagName("anydataset")->item(0);
        foreach ($this->collection as $sr) {
            $row = $sr->getAsDom();
            $nodeRow = $row->getElementsByTagName("row")->item(0);
            $newRow = XmlUtil::createChild($nodeRoot, "row");
            XmlUtil::addNodeFromNode($newRow, $nodeRow);
        }

        return $anyDataSet;
    }

    /**
     * @param string $file
     * @throws DatabaseException
     * @throws \ByJG\Util\Exception\XmlUtilException
     */
    public function save($file = null)
    {
        if (!is_null($file)) {
            if (!is_string($file)) {
                throw new InvalidArgumentException('Invalid file name');
            }

            $this->path = $file;
        }

        if (is_null($this->path)) {
            throw new DatabaseException("No such file path to save anydataset");
        }

        XmlUtil::saveXmlDocument($this->getAsDom(), $this->path);
    }

    /**
     * Append one row to AnyDataset.
     *
     * @param Row $singleRow
     * @return void
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
     */
    public function appendRow($singleRow = null)
    {
        if (!is_null($singleRow)) {
            if ($singleRow instanceof Row) {
                $this->collection[] = $singleRow;
                $singleRow->acceptChanges();
            } elseif (is_array($singleRow)) {
                $this->collection[] = new Row($singleRow);
            } else {
                throw new InvalidArgumentException("You must pass an array or a Row object");
            }
        } else {
            $singleRow = new Row();
            $this->collection[] = $singleRow;
            $singleRow->acceptChanges();
        }
        $this->currentRow = count($this->collection) - 1;
    }

    /**
     * Enter description here...
     *
     * @param GenericIterator $iterator
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
     */
    public function import($iterator)
    {
        foreach ($iterator as $singleRow) {
            $this->appendRow($singleRow);
        }
    }

    /**
     * Insert one row before specified position.
     *
     * @param int $rowNumber
     * @param mixed $row
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
     */
    public function insertRowBefore($rowNumber, $row = null)
    {
        if ($rowNumber > count($this->collection)) {
            $this->appendRow($row);
        } else {
            $singleRow = $row;
            if (!($row instanceof Row)) {
                $singleRow = new Row($row);
            }
            array_splice($this->collection, $rowNumber, 0, '');
            $this->collection[$rowNumber] = $singleRow;
        }
    }

    /**
     *
     * @param mixed $row
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
     */
    public function removeRow($row = null)
    {
        if (is_null($row)) {
            $row = $this->currentRow;
        }
        if ($row instanceof Row) {
            $iPos = 0;
            foreach ($this->collection as $sr) {
                if ($sr->toArray() == $row->toArray()) {
                    $this->removeRow($iPos);
                    break;
                }
                $iPos++;
            }
            return;
        }

        if ($row == 0) {
            $this->collection = array_slice($this->collection, 1);
        } else {
            $this->collection = array_slice($this->collection, 0, $row) + array_slice($this->collection, $row);
        }
    }

    /**
     * Add a single string field to an existing row
     *
     * @param string $name - Field name
     * @param string $value - Field value
     * @return void
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
     */
    public function addField($name, $value)
    {
        if ($this->currentRow < 0) {
            $this->appendRow();
        }
        $this->collection[$this->currentRow]->addField($name, $value);
    }

    /**
     * Get an Iterator filtered by an IteratorFilter
     * @param IteratorFilter $itf
     * @return GenericIterator
     */
    public function getIterator(IteratorFilter $itf = null)
    {
        if (is_null($itf)) {
            return new AnyIterator($this->collection);
        }

        return new AnyIterator($itf->match($this->collection));
    }

    /**
     * @desc
     * @param IteratorFilter $itf
     * @param string $fieldName
     * @return array
     */
    public function getArray($itf, $fieldName)
    {
        $iterator = $this->getIterator($itf);
        $result = array();
        while ($iterator->hasNext()) {
            $singleRow = $iterator->moveNext();
            $result [] = $singleRow->get($fieldName);
        }
        return $result;
    }

    /**
     *
     * @param string $field
     * @return void
     */
    public function sort($field)
    {
        if (count($this->collection) == 0) {
            return;
        }

        $this->collection = $this->quickSortExec($this->collection, $field);

        return;
    }

    /**
     * @param Row[] $seq
     * @param $field
     * @return array
     */
    protected function quickSortExec($seq, $field)
    {
        if (!count($seq)) {
            return $seq;
        }

        $key = $seq[0];
        $left = $right = array();

        $cntSeq = count($seq);
        for ($i = 1; $i < $cntSeq; $i ++) {
            if ($seq[$i]->get($field) <= $key->get($field)) {
                $left[] = $seq[$i];
            } else {
                $right[] = $seq[$i];
            }
        }

        return array_merge(
            $this->quickSortExec($left, $field),
            [ $key ],
            $this->quickSortExec($right, $field)
        );
    }
}


<?php

namespace ByJG\AnyDataset\Core;

use ByJG\Serializer\BinderObject;
use ByJG\Serializer\DumpToArrayInterface;
use ByJG\Util\XmlUtil;
use UnexpectedValueException;

class Row extends BinderObject implements DumpToArrayInterface
{

    /**
     * \DOMNode represents a Row
     * @var \DOMElement
     */
    private $node = null;
    private $row = null;
    private $originalRow = null;

    /**
     * Row constructor
     *
     * @param array()
     * @throws \ByJG\Serializer\Exception\InvalidArgumentException
     */
    public function __construct($instance = null)
    {
        if (is_null($instance)) {
            $this->row = array();
        } elseif (is_array($instance)) {
            $this->row = $instance;
        } else {
            $this->row = array();
            $this->bind($instance);
        }

        $this->acceptChanges();
    }

    /**
     * Add a string field to row
     * @param string $name
     * @param string $value
     */
    public function addField($name, $value)
    {
        if (!array_key_exists($name, $this->row)) {
            $this->row[$name] = $value;
        } elseif (is_array($this->row[$name])) {
            $this->row[$name][] = $value;
        } else {
            $this->row[$name] = array($this->row[$name], $value);
        }
        $this->informChanges();
    }

    /**
     * @param string $name - Field name
     * @return string
     * @desc et the string value from a field name
     */
    public function get($name)
    {
        if (!array_key_exists($name, $this->row)) {
            return null;
        }

        $result = $this->row[$name];
        if (is_array($result)) {
            return array_shift($result);
        } else {
            return $result;
        }
    }

    /**
     * Get array from a single field
     *
     * @param string $fieldName
     * @return array
     */
    public function getAsArray($fieldName)
    {
        if (!array_key_exists($fieldName, $this->row)) {
            return [];
        }

        $result = $this->row[$fieldName];

        if (empty($result)) {
            return [];
        }

        return (array)$result;
    }

    /**
     * Return all Field Names from current Row
     * @return array
     */
    public function getFieldNames()
    {
        return array_keys($this->row);
    }

    /**
     * Set a string value to existing field name
     * @param string $name
     * @param string $value
     */
    public function set($name, $value)
    {
        if (!array_key_exists($name, $this->row)) {
            $this->addField($name, $value);
        } else {
            $this->row[$name] = $value;
        }
        $this->informChanges();
    }

    /**
     * Remove specified field name from row.
     *
     * @param string $fieldName
     */
    public function removeField($fieldName)
    {
        if (array_key_exists($fieldName, $this->row)) {
            unset($this->row[$fieldName]);
            $this->informChanges();
        }
    }

    /**
     * Remove specified field name with specified value name from row.
     *
     * @param string $fieldName
     * @param $value
     */
    public function removeValue($fieldName, $value)
    {
        $result = $this->row[$fieldName];
        if (!is_array($result)) {
            if ($value == $result) {
                unset($this->row[$fieldName]);
                $this->informChanges();
            }
        } else {
            $qty = count($result);
            for ($i = 0; $i < $qty; $i++) {
                if ($result[$i] == $value) {
                    unset($result[$i]);
                    $this->informChanges();
                }
            }
            $this->row[$fieldName] = array_values($result);
        }
    }

    /**
     * Update a specific field and specific value with new value
     *
     * @param String $fieldName
     * @param String $oldvalue
     * @param String $newvalue
     */
    public function replaceValue($fieldName, $oldvalue, $newvalue)
    {
        $result = $this->row[$fieldName];
        if (!is_array($result)) {
            if ($oldvalue == $result) {
                $this->row[$fieldName] = $newvalue;
                $this->informChanges();
            }
        } else {
            for ($i = count($result) - 1; $i >= 0; $i--) {
                if ($result[$i] == $oldvalue) {
                    $this->row[$fieldName][$i] = $newvalue;
                    $this->informChanges();
                }
            }
        }
    }

    /**
     * Get the \DOMElement row objet
     *
     * @return \DOMElement
     * @throws \ByJG\Util\Exception\XmlUtilException
     */
    public function getAsDom()
    {
        if (is_null($this->node)) {
            $this->node = XmlUtil::createXmlDocumentFromStr("<row></row>");
            $root = $this->node->getElementsByTagName("row")->item(0);
            foreach ($this->row as $key => $value) {
                if (!is_array($value)) {
                    $field = XmlUtil::createChild($root, "field", $value);
                    XmlUtil::addAttribute($field, "name", $key);
                } else {
                    foreach ($value as $valueItem) {
                        $field = XmlUtil::createChild($root, "field", $valueItem);
                        XmlUtil::addAttribute($field, "name", $key);
                    }
                }
            }
        }
        return $this->node;
    }

    public function toArray()
    {
        return $this->row;
    }

    /**
     *
     * @return array
     */
    public function getAsJSON()
    {
        if (is_array($this->row)) {
            return json_decode(json_encode($this->row));
        } else {
            throw new UnexpectedValueException(
                'I expected that getRawFormat is array() but ' . gettype($this->row) . ' was given'
            );
        }
    }

    /**
     * @return array
     */
    public function getAsRaw()
    {
        return $this->originalRow;
    }

    /**
     *
     * @return bool
     */
    public function hasChanges()
    {
        return ($this->row != $this->originalRow);
    }

    /**
     *
     */
    public function acceptChanges()
    {
        $this->originalRow = $this->row;
    }

    /**
     *
     */
    public function rejectChanges()
    {
        $this->row = $this->originalRow;
    }

    protected function informChanges()
    {
        $this->node = null;
    }

    /**
     * Override Specific implementation of setPropValue to Row
     *
     * @param Row $obj
     * @param string $propName
     * @param string $value
     */
    protected function setPropValue($obj, $propName, $value)
    {
        $obj->set($propName, $value);
    }
}


<?php

/**
 * GoCardless pre-authorisation functions
 *
 * @package GoCardless\PreAuthorization
 */

/**
 * GoCardless pre-authorization class
 *
 */
class GoCardless_PreAuthorization extends GoCardless_Resource {

  /**
   * The API endpoint for pre-authorizations
   *
   * @var string $endpoint
   */
  public static $endpoint = '/pre_authorizations';

  /**
   * Instantiate a new instance of the pre-auth object
   *
   * @param object $client The client to use for the pre-auth object
   * @param array $attrs The properties of the pre-auth
   *
   * @return object The pre-auth object
   */
  function __construct($client, array $attrs = null) {

    $this->client = $client;

    if (is_array($attrs)) {
      foreach ($attrs as $key => $value) {
        $this->$key = $value;
      }
    }

  }

  /**
   * Fetch a pre-authorisation object from the API
   *
   * @param string $id The id of the pre-authorisation to fetch
   *
   * @return object The pre-authorisation object
   */
  public static function find($id) {

    $endpoint = self::$endpoint . '/' . $id;

    return new self(GoCardless::$client, GoCardless::$client->request('get',
      $endpoint));

  }

  /**
   * Fetch a bill item from the API
   *
   * @param object $client The client object to use to make the query
   * @param string $id The id of the bill to fetch
   *
   * @return object The bill object
   */
  public static function find_with_client($client, $id) {

    $endpoint = self::$endpoint . '/' . $id;

    return new self($client, $client->request('get', $endpoint));

  }

  /**
   * Create a bill under an existing pre-authorization
   *
   * @param array $attrs The properties of the bill being created
   *
   * @return object The pre-authorisations object
   */
  public function create_bill($attrs) {

    if ( ! isset($attrs['amount'])) {
      throw new GoCardless_ArgumentsException('Amount required');
    }

    $params = array(
      'bill' => array(
        'amount'                => $attrs['amount'],
        'pre_authorization_id'  => $this->id
      )
    );

    if (isset($attrs['name'])) {
      $params['bill']['name'] = $attrs['name'];
    }

    if (isset($attrs['description'])) {
      $params['bill']['description'] = $attrs['description'];
    }

    if (isset($attrs['charge_customer_at'])) {
      $params['bill']['charge_customer_at'] = $attrs['charge_customer_at'];
    }

    $endpoint = GoCardless_Bill::$endpoint;

    return new GoCardless_Bill($this->client, $this->client->request('post',
      $endpoint, $params));

  }

  /**
   * Cancel a pre-authorisation
   *
   * @return object The result of the cancel query
   */
  public function cancel() {

    $endpoint = self::$endpoint . '/' . $this->id . '/cancel';

    return new self($this->client, $this->client->request('put', $endpoint));

  }

}


<?php

/**
 * GoCardless merchant functions
 *
 * @package GoCardless\Merchant
 */

/**
 * GoCardless merchant class
 *
 */
class GoCardless_Merchant extends GoCardless_Resource {

  /**
   * The API endpoint for merchants
   *
   * @var string $endpoint
   */
  public static $endpoint = '/merchants';

  /**
   * Instantiate a new instance of the merchant object
   *
   * @param object $client The client to use for the merchant object
   * @param array $attrs The properties of the merchant
   *
   * @return object The merchant object
   */
  function __construct($client, array $attrs = null) {

    $this->client = $client;

    if (is_array($attrs)) {
      foreach ($attrs as $key => $value) {
        $this->$key = $value;
      }
    }

  }

  /**
   * Fetch a merchant object from the API
   *
   * @param string $id The id of the merchant to fetch
   *
   * @return object The merchant object
   */
  public static function find($id) {

    $client = GoCardless::$client;

    return new self($client, $client->request('get', self::$endpoint . '/' .
      $id));

  }

  /**
   * Fetch a merchant from the API
   *
   * @param object $client The client object to use to make the query
   * @param string $id The id of the merchant to fetch
   *
   * @return object The bill object
   */
  public static function find_with_client($client, $id) {

    return new self($client, $client->request('get', self::$endpoint . '/' .
      $id));

  }

}


<?php

/**
 * GoCardless bill functions
 *
 * @package GoCardless\Bill
 */

/**
 * GoCardless bill class
 *
 */
class GoCardless_Bill extends GoCardless_Resource {

  /**
   * The API endpoint for bills
   *
   * @var string $endpoint
   */
  public static $endpoint = '/bills';

  /**
   * Instantiate a new instance of the bill object
   *
   * @param object $client The client to use for the bill object
   * @param array $attrs The properties of the bill
   *
   * @return object The bill object
   */
  function __construct($client, array $attrs = null) {

    $this->client = $client;

    if (is_array($attrs)) {
      foreach ($attrs as $key => $value) {
        $this->$key = $value;
      }
    }

  }

  /**
   * Fetch a bill item from the API
   *
   * @param string $id The id of the bill to fetch
   * @param object $client The client object to use to make the query
   *
   * @return object The bill object
   */
  public static function find($id, $client = null) {

    $endpoint = self::$endpoint . '/' . $id;

    if ($client == null) {
      $client = GoCardless::$client;
    }

    return new self($client, $client->request('get', $endpoint));

  }

  /**
   * Fetch a bill item from the API
   *
   * @param object $client The client object to use to make the query
   * @param string $id The id of the bill to fetch
   *
   * @return object The bill object
   */
  public static function find_with_client($client, $id) {

    $endpoint = self::$endpoint . '/' . $id;

    return new self($client, $client->request('get', $endpoint));

  }

  /**
   * Attempt to collect a bill with status 'failed' again
   *
   * @return object The result of the retry query
   */
  public function retry() {

    $endpoint = self::$endpoint . '/' . $this->id . '/retry';

    return new self($this->client, $this->client->request('post', $endpoint));

  }

  /**
   * Fetch the payout for a bill, if a payout_id is recorded
   * @return object A GoCardless_Payout object representing the payout
   */
  public function payout() {
    if (!$this->payout_id) { throw new GoCardless_ClientException("Cannot fetch payout for a bill that has not been paid out"); }
    return GoCardless_Payout::find_with_client($this->client, $this->payout_id);
  }

  /**
   * Cancel a bill in the API
   *
   * @return object The result of the cancel query
   */
  public function cancel() {

    $endpoint = self::$endpoint . '/' . $this->id . '/cancel';

    return new self($this->client, $this->client->request('put', $endpoint));

  }

}


<?php

/**
 * GoCardless client functions
 *
 * @package GoCardless\Client
 */

/**
 * GoCardless client class
 *
 */
class GoCardless_Client {

  /**
   * Account details for this instance of GoCardless_Client
   *
   * @var array $account_details
   */
  public $account_details;

  /**
   * The base_url for the API to use for all client requests
   *
   * @var string $base_url
   */
  public $base_url;

  /**
   * Array of possible base_urls to use
   *
   * @var array $base_urls
   */
  public static $base_urls = array(
    'production'  => 'https://gocardless.com',
    'sandbox'     => 'https://sandbox.gocardless.com'
  );

  /**
   * The path to use to call the API
   *
   * @var string $api_path
   */
  public static $api_path = '/api/v1';

  /**
   * The url to redirect the user to
   *
   * @var string $redirect_uri
   */
  public $redirect_uri;

  /**
   * Constructor, creates a new instance of GoCardless_Client
   *
   * @param array $account_details Parameters
   */
  public function __construct($account_details) {

    // Fetch account_details
    foreach ($account_details as $key => $value) {
      $this->account_details[$key] = $value;
    }

    // Check for app_id
    if ( ! isset($this->account_details['app_id'])) {
      throw new GoCardless_ClientException('No app_id specified');
    }

    // Check for app_secret
    if ( ! isset($this->account_details['app_secret'])) {
      throw new GoCardless_ClientException('No app_secret specfied');
    }

    // If environment is not set then default to sandbox
    if ( ! isset(GoCardless::$environment)) {
      GoCardless::$environment = 'sandbox';
    }

    // Take base_url from array
    if (isset($account_details['base_url'])) {
      $this->base_url = $account_details['base_url'];
      unset($account_details['base_url']);
    } else {
      // Otherwise set it based on environment
      $this->base_url = self::$base_urls[GoCardless::$environment];
    }

  }

  /**
   * Generate the OAuth authorize url
   *
   * @param array $options The parameters to use
   *
   * @return string The generated url
   */
  public function authorize_url($options = null) {

    if ( ! isset($options['redirect_uri'])) {
      throw new GoCardless_ArgumentsException('redirect_uri required');
    }

    $required_options = array(
      "client_id" => $this->account_details['app_id'],
      "scope" => "manage_merchant",
      "response_type" => "code"
    );

    $params = array_merge($required_options, $options);
    $request = GoCardless_Utils::generate_query_string($params);

    return $this->base_url . "/oauth/authorize/?" . $request;

  }

  /**
   * Fetch an access token for the current user
   *
   * @param array $params The parameters to use
   *
   * @return array Array containing the Merchant ID ('merchant_id') and Access Token ('access_token')
   */
  public function fetch_access_token($params) {

    if ( ! isset($params['redirect_uri'])) {
      throw new GoCardless_ArgumentsException('redirect_uri required');
    }

    $params['http_authorization'] = $this->account_details['app_id'] . ':' .
      $this->account_details['app_secret'];

    $response = $this->request('post', '/oauth/access_token', $params);

    $merchant = explode(':', $response['scope']);
    $merchant_id = isset($merchant[1]) ? $merchant[1] : null;
    $access_token = $response['access_token'];

    return array(
      'merchant_id'   => $merchant_id,
      'access_token'  => $access_token
    );

  }

  /**
   * Generate a URL to give a user to create a new bill
   *
   * @param array $params Parameters to use to generate the URL
   *
   * @return string The generated URL
   */
  public function new_bill_url($params) {
    return $this->new_limit_url('bill', $params);
  }

  /**
   * Generate a URL to give a user to create a new subscription
   *
   * @param array $params Parameters to use to generate the URL
   *
   * @return string The generated URL
   */
  public function new_subscription_url($params) {
    return $this->new_limit_url('subscription', $params);
  }

  /**
   * Generate a URL to give a user to create a new pre-authorized payment
   *
   * @param array $params Parameters to use to generate the URL
   *
   * @return string The generated URL
   */
  public function new_pre_authorization_url($params) {
    return $this->new_limit_url('pre_authorization', $params);
  }

  /**
   * Returns the merchant associated with the client's access token
   *
   * @param string $id The id of the merchant to fetch
   *
   * @return object The merchant object
   */
  public function merchant($id = null) {

    if ($id == null) {
      $id = $this->account_details['merchant_id'];
    }

    return GoCardless_Merchant::find_with_client($this, $id);

  }

  /**
   * Get a specific bill
   *
   * @param string $id The id of the bill to fetch
   *
   * @return object The bill object matching the id requested
   */
  public function bill($id) {
    return GoCardless_Bill::find_with_client($this, $id);
  }

  /**
   * Get a specific subscription
   *
   * @param string $id The id of the subscription to fetch
   *
   * @return object The subscription matching the id requested
   */
  public function subscription($id) {
    return GoCardless_Subscription::find_with_client($this, $id);
  }

  /**
   * Get a specific pre_authorization
   *
   * @param string $id The id of the pre_authorization to fetch
   *
   * @return object The pre-authorization matching the id requested
   */
  public function pre_authorization($id) {
    return GoCardless_PreAuthorization::find_with_client($this, $id);
  }

  /**
   * Get a specific user
   *
   * @param string $id The id of the user to fetch
   *
   * @return object The user object matching the id requested
   */
  public function user($id) {
    return GoCardless_User::find_with_client($this, $id);
  }

  /**
   * Create a new bill under a given pre-authorization
   *
   * @param array $params Must include pre_authorization_id and amount
   *
   * @return string The new bill object
   */
  public function create_bill($params) {

    if ( ! isset($params['pre_authorization_id'])) {
      throw new GoCardless_ArgumentsException('pre_authorization_id missing');
    }

    $pre_auth = new GoCardless_PreAuthorization($this, array(
      'id' => $params['pre_authorization_id']
    ));

    return $pre_auth->create_bill(array('amount' => $params['amount']));

  }

  /**
   * Send an HTTP request to confirm the creation of a new payment resource
   *
   * @param array $params Parameters to send with the request
   *
   * @return Bill/Subscription/PreAuthorization The confirmed resource
   */
  public function confirm_resource($params) {

    // Define confirm endpoint
    $endpoint = '/confirm';

    // First validate signature
    // Then send confirm request

    // List of required params
    $required_params = array(
      'resource_id', 'resource_type'
    );

    // Loop through required params
    // Add to $data or throw exception if missing
    foreach ($required_params as $key => $value) {
      if ( ! isset($params[$value])) {
        throw new GoCardless_ArgumentsException("$value missing");
      }
      $data[$value] = $params[$value];
    }

    // state is optional
    if (isset($params['state'])) {
      $data['state'] = $params['state'];
    }

    // resource_uri is optional
    if (isset($params['resource_uri'])) {
      $data['resource_uri'] = $params['resource_uri'];
    }

    $sig_validation_data = array(
      'data'      => $data,
      'secret'    => $this->account_details['app_secret'],
      'signature' => $params['signature']
    );

    if ($this->validate_signature($sig_validation_data) == false) {
      throw new GoCardless_SignatureException();
    }

    // Sig valid, now send confirm request
    $confirm_params = array(
      'resource_id'   => $params['resource_id'],
      'resource_type' => $params['resource_type']
    );

    // Use HTTP Basic Authorization
    $confirm_params['http_authorization'] = $this->account_details['app_id']
      . ':' . $this->account_details['app_secret'];

    // If no method-specific redirect sent, use class level if available
    if ( ! isset($params['redirect_uri']) && isset($this->redirect_uri)) {
      $confirm_params['redirect_uri'] = $this->redirect_uri;
    }

    // Do query
    $response = $this->request('post', $endpoint, $confirm_params);

    if ($response['success'] == true) {

      $endpoint = '/' . $params['resource_type'] . 's/' .
        $params['resource_id'];

      $class_name = 'GoCardless_' .
        GoCardless_Utils::camelize($params['resource_type']);

      return new $class_name($this, $this->request('get', $endpoint));

    } else {

      throw new GoCardless_ClientException('Failed to fetch the confirmed
        resource.');

    }

  }

  /**
   * Generate a new payment url
   *
   * @param string $type Payment type
   * @param string $params The specific parameters for this payment
   *
   * @return string The new payment URL
   */
  public function new_limit_url($type, $params) {

    // $params are passed in
    // Optional $params are saved in $request and removed from $params
    // $params now only contains params for the payments
    // $payment_params is created containing sub-object named after $type
    // Merge $payment_params, $request and mandatory params
    // Sign
    // Generate query string
    // Return resulting url

    // Declare empty array
    $request = array();

    // Add in merchant id
    $params['merchant_id'] = $this->account_details['merchant_id'];

    // Define optional parameters
    $opt_params = array(
      'redirect_uri',
      'cancel_uri',
      'state'
    );

    // Loop through optional parameters
    foreach ($opt_params as $opt_param) {
      if (isset($params[$opt_param])) {
        $request[$opt_param] = $params[$opt_param];
        unset($params[$opt_param]);
      }
    }

    // If no method-specific redirect submitted then
    // use class level if available
    if ( ! isset($request['redirect_uri']) && isset($this->redirect_uri)) {
      $request['redirect_uri'] = $this->redirect_uri;
    }

    // Create array of payment params
    $payment_params = array($type => $params);

    // Put together all the bits: passed params inc payment params & mandatory
    $request = array_merge($request, $payment_params,
      $this->generate_mandatory_params());

    // Generate signature
    $request['signature'] = GoCardless_Utils::generate_signature($request,
      $this->account_details['app_secret']);

    // Generate query string from all parameters
    $query_string = GoCardless_Utils::generate_query_string($request);

    // Generate url NB. Pluralises resource
    return $this->base_url . '/connect/' . $type . 's/new?' . $query_string;

  }

  /**
   * Make a request to the API
   *
   * @param string $method The request method to use
   * @param string $endpoint The API endpoint to call
   * @param string $params The parameters to send with the request
   *
   * @return object The returned object
   */
  public function request($method, $endpoint, $params = array()) {

    // If there is no http_authorization, try checking for access_token
    if ( ! isset($params['http_authorization'])) {

      // No http_authorization and no access_token? Fail
      if ( ! isset($this->account_details['access_token'])) {
        throw new GoCardless_ClientException('Access token missing');
      }

      // access_token found so set Authorization header to contain bearer
      $params['http_bearer'] = $this->account_details['access_token'];

    }

    // Set application specific user agent suffix if found
    if (isset($this->account_details['ua_tag'])) {
      $params['ua_tag'] = $this->account_details['ua_tag'];
    }

    if (substr($endpoint, 0, 6) == '/oauth') {

      // OAuth API calls don't require /api/v1 base
      $url = $this->base_url . $endpoint;

    } else {

      // http://sandbox.gocardless.com | /api/v1 | /test
      $url = $this->base_url . self::$api_path . $endpoint;

    }

    // Call Request class (might be aliased for testing) with URL & params
    return call_user_func(GoCardless::getClass('Request').'::'.$method, $url,
      $params);

  }

  /**
   * Test whether a webhook is valid or not
   *
   * @param array params The contents of the webhook in array form
   *
   * @return boolean If valid returns true
   */
  public function validate_webhook($params) {

    $sig = $params['signature'];
    unset($params['signature']);

    if ( ! isset($sig)) {
      return false;
    }

    $data = array(
      'data'      => $params,
      'secret'    => $this->account_details['app_secret'],
      'signature' => $sig
    );

    return $this->validate_signature($data);

  }

  /**
   * Confirm whether a signature is valid
   *
   * @param array $params Should include data, secret and signature
   *
   * @return boolean True or false
   */
  public function validate_signature($params) {

    $new_sig = GoCardless_Utils::generate_signature($params['data'],
      $params['secret']);

    return ($new_sig === $params['signature']);

  }

  /**
   * Generate mandatory payment parameters: client_id, nonce and timestamp
   *
   * @return array Mandatory payment parameters
   */
  public function generate_mandatory_params() {

    // Create new UTC date object
    $date = new DateTime(null, new DateTimeZone('UTC'));

    return array(
      'client_id' => $this->account_details['app_id'],
      'nonce'     => GoCardless_Client::generate_nonce(),
      'timestamp' => $date->format('Y-m-d\TH:i:s\Z')
    );

  }

  /**
   * Generates a nonce
   *
   * @return string Base64 encoded nonce
   */
  public function generate_nonce() {

    $n = 1;
    $rand = '';

    do {
      $rand .= rand(1, 256);
      $n++;
    } while ($n <= 45);

    return base64_encode($rand);

  }

}


<?php

/**
 * GoCardless resource functions
 *
 * @package GoCardless\Resource
 */

/**
 * GoCardless resource class
 *
 */
class GoCardless_Resource {

  /**
   * This magic method is used to call subresources
   *
   * @param string $method The name of the method being called
   * @param array $arguments The arguments to pass to the method
   *
   * @return array The subresource index
   */
  public function __call($method, $arguments = array()) {

    // Params may be passed as first argument to method
    // If not, use empty array
    $params = isset($arguments[0]) ? $arguments[0] : array();

    // Check the subresource exists
    if (array_key_exists($method, $this->sub_resource_uris)) {

      // Return the subresource
      return $this->fetch_sub_resource($method, $params);

    }

    // Subresource doesn't exist so error out
    $class = get_class($this);
    trigger_error("Call to undefined method $class::$method()", E_USER_ERROR);

  }

  /**
   * Fetch an object's subresource from the API
   *
   * @param string $type The subresource to fetch
   * @param array $params The params for the subresource query
   *
   * @return object The subresource object
   */
  public function fetch_sub_resource($type, $params = array()) {

    // Generate subresource endpoint by snipping out the
    // right part of the sub_resource_uri
    $endpoint = preg_replace('/api\/v[0-9]+\//', '',
      parse_url($this->sub_resource_uris[$type], PHP_URL_PATH));

    $sub_resource_params = array();

    // Extract params from subresource uri if available and create
    // sub_resource_params array
    if ($param_string = parse_url($this->sub_resource_uris[$type],
      PHP_URL_QUERY)) {

      $split_params = explode('&', $param_string);

      foreach ($split_params as $split_param) {
          $parts = explode('=', $split_param);
          $sub_resource_params[$parts[0]] = $parts[1];
      }

    }

    // Overwrite params from subresource uri with passed params, if found
    $params = array_merge($params, $sub_resource_params);

    // Get class name
    $class = 'GoCardless_' .
      GoCardless_Utils::camelize(GoCardless_Utils::singularize($type));

    $objects = array();

    // Create an array of objects
    foreach ($this->client->request('get', $endpoint, $params) as $value) {
      $objects[] = new $class($this->client, $value);
    }

    // Return the array of objects
    return $objects;

  }

}


<?php

/**
 * GoCardless utility functions
 *
 * @package GoCardless\Utils
 */

/**
 * GoCardless utils class
 *
 */
class GoCardless_Utils {

  /**
   * Generate a signature for a request given the app secret
   *
   * @param array $params The parameters to generate a signature for
   * @param string $key The key to generate the signature with
   *
   * @return string A URL-encoded string of parameters
   */
  public static function generate_signature($params, $key) {

    return hash_hmac('sha256',
      GoCardless_Utils::generate_query_string($params), $key);

  }

  /**
   * Generates, encodes, re-orders variables for the query string.
   *
   * @param array $params The specific parameters for this payment
   * @param array $pairs Pairs
   * @param string $namespace The namespace
   *
   * @return string An encoded string of parameters
   */
  public static function generate_query_string($params, &$pairs = array(),
    $namespace = null) {

    if (is_array($params)) {

      foreach ($params as $k => $v) {

        if (is_int($k)) {
          GoCardless_Utils::generate_query_string($v, $pairs, $namespace .
            '[]');
        } else {
          GoCardless_Utils::generate_query_string($v, $pairs,
            $namespace !== null ? $namespace . "[$k]" : $k);
        }

      }

      if ($namespace !== null) {
        return $pairs;
      }

      if (empty($pairs)) {
        return '';
      }

      usort($pairs, array(__CLASS__, 'sortPairs'));

      $strs = array();
      foreach ($pairs as $pair) {
        $strs[] = $pair[0] . '=' . $pair[1];
      }

      return implode('&', $strs);

    } else {

      $pairs[] = array(rawurlencode($namespace), rawurlencode($params));

    }

  }

  /**
   * Sorts a pair
   *
   * @param array $a
   * @param array $b
   * @return int
   */
  public static function sortPairs($a, $b)
  {
    $keys = strcmp($a[0], $b[0]);

    if ($keys !== 0) {
      return $keys;
    }

    return strcmp($a[1], $b[1]);
  }

  /**
   * Strip underscores and convert to CamelCaps
   *
   * @param string $string The string to process
   *
   * @return string The result
   */
  public static function camelize($string) {

    return implode(array_map('ucfirst', explode('_', $string)));

  }

  /**
   * Convert a word to the singular
   *
   * @param string $string The string to process
   *
   * @return string The result
   */
  public static function singularize($string) {

    if (substr($string, -1) == 's') {
      return substr($string, 0, -1);
    } elseif (substr($string, -1) == 'i') {
      return substr($string, 0, -1) . 'us';
    } else {
      return $string;
    }

  }

}


<?php

/**
 * GoCardless request functions
 *
 * @package GoCardless\Request
 */

/**
 * GoCardless request class
 *
 */
class GoCardless_Request {

  /**
   * Configure a GET request
   *
   * @param string $url The URL to make the request to
   * @param array $params The parameters to use for the POST body
   *
   * @return string The response text
   */
  public static function get($url, $params = array()) {
    return self::call('get', $url, $params);
  }

  /**
   * Configure a POST request
   *
   * @param string $url The URL to make the request to
   * @param array $params The parameters to use for the POST body
   *
   * @return string The response text
   */
  public static function post($url, $params = array()) {
    return self::call('post', $url, $params);
  }

  /**
   * Configure a PUT request
   *
   * @param string $url The URL to make the request to
   * @param array $params The parameters to use for the PUT body
   *
   * @return string The response text
   */
  public static function put($url, $params = array()) {
    return self::call('put', $url, $params);
  }

  /**
   * Makes an HTTP request
   *
   * @param string $method The method to use for the request
   * @param string $url The API url to make the request to
   * @param array $params The parameters to use for the request
   *
   * @return string The response text
   */
  protected static function call($method, $url, $params = array()) {

    // Initialize curl
    $ch = curl_init();

    // Default curl options, including library & version number
    $curl_options = array(
      CURLOPT_CONNECTTIMEOUT  => 10,
      CURLOPT_RETURNTRANSFER  => true,
      CURLOPT_TIMEOUT         => 60,
      CURLOPT_USERAGENT       => 'gocardless-php/v' . GoCardless::VERSION,
    );

    // Set application specific user agent suffix if found
    if (isset($params['ua_tag'])) {

      // Set the user agent header
      $curl_options[CURLOPT_USERAGENT] .= ' ' . $params['ua_tag'];

      // Remove ua_tag from $params as $params are used later
      unset($params['ua_tag']);

    }

    // Request format
    $curl_options[CURLOPT_HTTPHEADER][] = 'Accept: application/json';

    // Enable SSL certificate validation.
    // This is true by default since libcurl 7.1.
    $curl_options[CURLOPT_SSL_VERIFYPEER] = true;

    // Debug - DO NOT USE THIS IN PRODUCTION FOR SECURITY REASONS
    //
    // This fixes a problem in some environments with connecting to HTTPS-enabled servers.
    // Sometimes, Curl has no list of valid CAs, and so won't connect. With this fix, it
    // doesn't verify and just connects anyway, instead of throwing an exception.
    //
    //$curl_options[CURLOPT_SSL_VERIFYPEER] = false;

    // HTTP Authentication (for confirming new payments)
    if (isset($params['http_authorization'])) {

      // Set HTTP Basic Authorization header
      $curl_options[CURLOPT_USERPWD] = $params['http_authorization'];

      // Unset http basic param as params are used later
      unset($params['http_authorization']);

    } else {

      // Throw an exception if access token is missing
      if ( ! isset($params['http_bearer'])) {
        throw new GoCardless_ClientException('Access token missing');
      }

      // Set the authorization header
      $curl_options[CURLOPT_HTTPHEADER][] = 'Authorization: Bearer ' .
        $params['http_bearer'];

      // Unset http_bearer param as params are used later
      unset($params['http_bearer']);

    }

    if ($method == 'post') {
      // Curl options for POSt

      $curl_options[CURLOPT_POST] = 1;

      if ( ! empty($params)) {
        $curl_options[CURLOPT_POSTFIELDS] = http_build_query($params, null,
          '&');
      }

    } elseif ($method == 'get') {
      // Curl options for GET

      $curl_options[CURLOPT_HTTPGET] = 1;

      if ( ! empty($params)) {
        $url .= '?' . http_build_query($params, null, '&');
      }

    } elseif ($method == 'put') {
      // Curl options for PUT

      $curl_options[CURLOPT_PUT] = 1;

      // Receiving the following Curl error?:
      //    "cannot represent a stream of type MEMORY as a STDIO FILE*"
      // Try changing the first parameter of fopen() to `php://temp`
      $fh = fopen('php://memory', 'rw+');

      $curl_options[CURLOPT_INFILE] = $fh;
      $curl_options[CURLOPT_INFILESIZE] = 0;

    }

    // Set the url to query
    curl_setopt($ch, CURLOPT_URL, $url);

    // Debug
    //echo "
\nCurl " . strtoupper($method) . " request to: $url\n";
    //if (isset($curl_options[CURLOPT_POSTFIELDS])) {
    //  echo "Post vars:\n";
    //  echo htmlspecialchars(print_r($curl_options[CURLOPT_POSTFIELDS], true));
    //  echo "\n";
    //}
    //echo "Curl request config:\n";
    //print_r($curl_options);
    //echo '
';

    // Set curl options
    curl_setopt_array($ch, $curl_options);

    // Send the request
    $result = curl_exec($ch);
    $error = curl_errno($ch);

    if ($error == CURLE_SSL_PEER_CERTIFICATE || $error == CURLE_SSL_CACERT ||
        $error == 77) {
      curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/cert-bundle.crt');
      $result = curl_exec($ch);
    }

    // Debug
    //echo "
\nCurl result config:\n";
    //print_r(curl_getinfo($ch));
    //echo "Curl result:\n";
    //echo htmlspecialchars($result) . "\n";
    //echo "
";

    // Grab the response code and throw an exception if it's not good
    $http_response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($http_response_code < 200 || $http_response_code > 300) {

      // Create a string
      $message = print_r(json_decode($result, true), true);

      // Throw an exception with the error message
      throw new GoCardless_ApiException($message, $http_response_code, $result);

    }

    // Close the connection
    curl_close($ch);

    // Close the $fh handle used by PUT
    if (isset($fh)) {
      fclose($fh);
    }

    // Return the response as an array
    return json_decode($result, true);

  }

}


<?php
namespace Mrubiosan\Facade;

class ClassAliaser
{
    /**
     * @var array
     */
    static private $aliases = [];

    /**
     * @var callable
     */
    static private $autoloadFn;

    /**
     * Registers a class autoloader which will create the given class aliases on demand.
     * @param array $aliases Aliased classes. The alias as key, and the facade class as value
     */
    public static function register(array $aliases)
    {
        if (!$aliases) {
            return;
        }

        if (!isset(self::$autoloadFn)) {
            $classAliases = &self::$aliases;
            self::$autoloadFn = function ($className) use (&$classAliases) {
                if (isset($classAliases[$className])) {
                    if (strtolower($classAliases[$className]) === strtolower($className)) {
                        throw new \LogicException("Class alias is referencing the alias itself");
                    }
                    $facadeClass = $classAliases[$className];
                    class_alias($facadeClass, $className);
                }
            };

            spl_autoload_register(self::$autoloadFn);
        }

        self::$aliases = array_merge(self::$aliases, $aliases);
    }

    /**
     * Unregisters the aliases and class autoloading function
     */
    public static function unregister()
    {
        if (isset(self::$autoloadFn)) {
            spl_autoload_unregister(self::$autoloadFn);
            self::$autoloadFn = null;
        }

        self::$aliases = [];
    }
}


<?php

namespace Zalas\Bundle\ParamConverterBundle\Request\ParamConverter;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class ServiceParamConverter implements ParamConverterInterface
{
    /**
     * @var ContainerInterface
     */
    private $container;

    /**
     * @param ContainerInterface $container
     */
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * @param Request        $request
     * @param ParamConverter $configuration
     *
     * @return bool
     */
    public function apply(Request $request, ParamConverter $configuration)
    {
        $object = $this->callService($request, $configuration);

        $class = $configuration->getClass();
        if ($object instanceof $class) {
            $request->attributes->set($configuration->getName(), $object);

            return true;
        }

        if ($configuration->isOptional()) {
            return false;
        }

        throw new NotFoundHttpException(sprintf('Object of class "%s" not found.', $class));
    }

    /**
     * @param ParamConverter $configuration
     *
     * @return bool
     */
    public function supports(ParamConverter $configuration)
    {
        $options = $configuration->getOptions();

        return !empty($options['service']) && !empty($options['method']);
    }

    /**
     * @param Request        $request
     * @param ParamConverter $configuration
     *
     * @return mixed
     */
    private function callService(Request $request, ParamConverter $configuration)
    {
        $options = $configuration->getOptions();
        $callable = $this->getCallable($options);

        return call_user_func_array($callable, $this->extractArguments($request, $options));
    }

    /**
     * @param Request $request
     * @param array   $options
     *
     * @return array
     */
    private function extractArguments(Request $request, array $options)
    {
        $arguments = isset($options['arguments']) && is_array($options['arguments']) ? $options['arguments'] : [];

        return array_map(
            function ($argument) use ($request) {
                return $request->attributes->get($argument);
            },
            $arguments
        );
    }

    /**
     * @param array $options
     *
     * @return array
     */
    private function getCallable(array $options)
    {
        $serviceId = $options['service'];
        $method = $options['method'];
        $service = $this->container->get($serviceId);
        $callable = [$service, $method];

        if (!is_callable($callable)) {
            throw new \InvalidArgumentException(sprintf(
                'The param converter is configured to use a non existent method "%s::%s()" on the "%s" service.',
                get_class($service), $method, $serviceId
            ));
        }

        return $callable;
    }
}


<?php

namespace Phpactor\ClassFileConverter;

use Phpactor\ClassFileConverter\Exception\NoMatchingSourceException;
use Phpactor\ClassFileConverter\PathFinder;
use Webmozart\PathUtil\Path;
use RuntimeException;

class PathFinder
{
    const KERNEL = '<kernel>';

    /**
     * @var array
     */
    private $destinations = [];

    private function __construct(array $destinations)
    {
        $this->validateTargets($destinations);

        foreach ($destinations as $destinationName => $pattern) {
            $this->add($destinationName, $pattern);
        }
    }

    public static function fromDestinations(array $destinations): PathFinder
    {
        return new self($destinations);
    }

    /**
     * Return a hash map of destination names to paths representing
     * paths which relate to the given file path.
     *
     * @throws NoMatchingSourceException
     */
    public function destinationsFor(string $filePath): array
    {
        $filePath = Path::canonicalize($filePath);
        $source = $this->matchingSource($filePath);

        return $this->resolveDestinations($filePath, $source);
    }

    private function matchingSource($filePath)
    {
        foreach ($this->destinations as $targetName => $pattern) {
            if ($this->matches($filePath, $pattern)) {
                return $targetName;
            }
        }

        throw new NoMatchingSourceException(sprintf(
            'Could not find a matching pattern for path "%s", known patterns: "%s"',
            $filePath, implode('", "', $this->destinations)
        ));
    }

    private function matches(string $filePath, $pattern)
    {
        $pattern = $this->pattern($pattern);

        return (bool) $this->matchPattern($filePath, $pattern);
    }

    private function resolveDestinations(string $filePath, $target)
    {
        $resolved = [];

        foreach ($this->destinations as $targetName => $targetPattern) {
            if ($target === $targetName) {
                continue;
            }

            $sourcePattern = $this->pattern($this->destinations[$target]);
            $kernel = $this->matchPattern($filePath, $sourcePattern);

            $resolved[$targetName] = str_replace(self::KERNEL, $kernel, $targetPattern);
        }

        return $resolved;
    }

    private function pattern(string $pattern): string
    {
        $pattern = preg_quote($pattern);

        return str_replace(preg_quote(self::KERNEL), '(.*?)', $pattern);
    }

    private function matchPattern(string $filePath, string $pattern)
    {
        if (preg_match('{' . $pattern . '$}', $filePath, $matches)) {
            return $matches[1];
        }

        return;
    }

    private function validateTargets(array $destinations)
    {
        foreach ($destinations as $destination) {
            if (strpos($destination, self::KERNEL)) {
                continue;
            }

            throw new RuntimeException(sprintf(
                'Destination "%s" contains no <kernel> placeholder',
                $destination
            ));
        }
    }

    private function add(string $destinationName, string $pattern)
    {
        $this->destinations[$destinationName] = $pattern;
    }
}


<?php

namespace PLUS\Composer;

use Composer\Composer;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Installer\PackageEvent;
use Composer\Installer\PackageEvents;
use Composer\IO\IOInterface;
use Composer\Plugin\PluginInterface;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;
use GrumPHP\Event\TaskEvents;

class Plugin implements PluginInterface, EventSubscriberInterface
{
    const PACKAGE_NAME = 'pluswerk/grumphp-config';
    const DEFAULT_CONFIG_PATH = 'vendor/' . self::PACKAGE_NAME . '/grumphp.yml';

    /**
     * @var Composer
     */
    protected $composer;

    /**
     * @var IOInterface
     */
    protected $consoleIo;

    /**
     * @var array
     */
    protected $extra;

    /**
     * @var bool
     */
    protected $shouldSetConfigPath = false;

    public function activate(Composer $composer, IOInterface $consoleIo)
    {
        $this->composer = $composer;
        $this->consoleIo = $consoleIo;
        $this->extra = $this->composer->getPackage()->getExtra();
    }

    public static function getSubscribedEvents()
    {
        return [
            PackageEvents::POST_PACKAGE_UPDATE => 'postPackageUpdate',
            PackageEvents::POST_PACKAGE_INSTALL => 'postPackageInstall',
            PackageEvents::PRE_PACKAGE_UNINSTALL => 'prePackageUninstall',

            ScriptEvents::POST_INSTALL_CMD => 'runScheduledTasks',
            ScriptEvents::POST_UPDATE_CMD => 'runScheduledTasks',
        ];
    }

    public function postPackageUpdate(PackageEvent $event)
    {
        $operation = $event->getOperation();
        if ($operation instanceof UpdateOperation && $operation->getTargetPackage()->getName() === self::PACKAGE_NAME) {
            $this->shouldSetConfigPath = true;
        }
    }

    public function postPackageInstall(PackageEvent $event)
    {
        $operation = $event->getOperation();
        if ($operation instanceof InstallOperation && $operation->getPackage()->getName() === self::PACKAGE_NAME) {
            $this->shouldSetConfigPath = true;
        }
    }

    public function prePackageUninstall(PackageEvent $event)
    {
        $operation = $event->getOperation();
        if ($operation instanceof UninstallOperation && $operation->getPackage()->getName() === self::PACKAGE_NAME) {
            $this->removeConfigPath();
        }
    }

    public function runScheduledTasks()
    {
        if ($this->shouldSetConfigPath) {
            $this->setConfigPath();
        }
    }

    //ACTIONS:

    public function setConfigPath()
    {
        if ($this->getExtra(self::PACKAGE_NAME . '.auto-setting') === false) {
            $this->message('not setting config path, extra.' . self::PACKAGE_NAME . '.auto-setting is false', 'yellow');
            return;
        }
        $this->setExtra(self::PACKAGE_NAME . '.auto-setting', true);
        if ($this->getExtra('grumphp.config-default-path') !== self::DEFAULT_CONFIG_PATH) {
            $this->setExtra('grumphp.config-default-path', self::DEFAULT_CONFIG_PATH);
            $this->message('auto setting grumphp.config-default-path', 'green');
        }
    }

    public function removeConfigPath()
    {
        if ($this->getExtra(self::PACKAGE_NAME . '.auto-setting') === false) {
            $this->message('not removing config path, extra.' . self::PACKAGE_NAME . '.auto-setting is false', 'yellow');
            return;
        }
        unset($this->extra[self::PACKAGE_NAME]);
        $this->removeExtra(self::PACKAGE_NAME);

        $key = null;
        if (count($this->getExtra('grumphp')) > 1) {
            $key  = 'grumphp.config-default-path';
        } elseif (count($this->getExtra()) > 1) {
            $key = 'grumphp';
        }
        $this->removeExtra($key);
        $this->message('auto removed config path and ' . self::PACKAGE_NAME . ' settings', 'green');
    }

    // HELPER:

    public function getExtra($name = null)
    {
        if ($name === null) {
            return $this->extra;
        }
        $arr = $this->extra;
        $bits = explode('.', $name);
        $last = array_pop($bits);
        foreach ($bits as $bit) {
            if (!isset($arr[$bit])) {
                $arr[$bit] = [];
            }
            $arr = &$arr[$bit];
        }
        if (isset($arr[$last])) {
            return $arr[$last];
        }
        return null;
    }

    public function setExtra($name, $value)
    {
        $configSource = $this->composer->getConfig()->getConfigSource();
        return $configSource->addProperty('extra.' . $name, $value);
    }

    public function removeExtra($name = null)
    {
        $key = 'extra';
        if ($name !== null) {
            $key .= '.' . $name;
        }
        $configSource = $this->composer->getConfig()->getConfigSource();
        return $configSource->removeProperty($key);
    }

    public function message($message, $color = null)
    {
        $colorStart = '';
        $colorEnd = '';
        if (is_string($color)) {
            $colorStart = '<fg=' . $color . '>';
            $colorEnd = '</fg=' . $color . '>';
        }
        $this->consoleIo->write(self::PACKAGE_NAME . ': ' . $colorStart . $message . $colorEnd);
    }
}


<?php
/**
 * Soap client driver
 *
 * @category Popov
 * @package Popov_Importer
 * @author Serhii Popov <popow.serhii@gmail.com>
 * @datetime: 13.05.2016 13:35
 * @link https://github.com/iliaal/php_excel
 */
namespace Popov\Importer\Driver;

use Popov\Importer\Driver\Factory\SoapCombinedAdapterFactory;
use Zend\Soap\Client as SoapClient;
use Popov\Importer\Driver\Adapter\SoapCombinedAdapter;

class Soap implements DriverInterface
{
    protected $source;

    protected $config = [];

    protected $sheet = [];

    protected $soapAdapter;

    public function __construct(array $config = [], SoapCombinedAdapter $soapAdapter = null)
    {
        $this->config = $config;
        $this->soapAdapter = $soapAdapter ?: (new SoapCombinedAdapterFactory($config))();
    }

    /**
     * {@inheritDoc}
     */
    public function source($source = null)
    {
        if ($source) {
            $this->source = $source;

            return $this;
        }

        return $this->source;
    }

    protected function columns()
    {
        if (!isset($this->sheet[0])) {
            $sheet = $this->sheet();
            $row = next($sheet);
            foreach ($row as $name => $value) {
                $this->sheet[0][] = $name;
            }
        }

        return $this->sheet[0];
    }

    protected function sheet()
    {
        if (!$this->sheet) {
            //die(__METHOD__);
            //$params = new \stdClass();
            //$params->DateBalance = '2016-08-09';
            $params = json_decode(json_encode($this->config()['params']), false);;

            $soap = $this->soapAdapter;

            // @todo Фіча яка робить масив макимально наближеним до логіки циклу в Importer.
            // Після реалізації Traversable все стане простіше і логічніше
            $sourceMethod = ucfirst($this->source());
            $this->sheet = [0 => []] + $this->deeper($soap->{$sourceMethod}($params));
            //var_dump($soap->createInvoice($invoice)) . "<br />"; // 50
            //\Zend\Debug\Debug::dump($this->sheet); die(__METHOD__);
        }

        return $this->sheet;
    }

    /**
     * Depth iterate over array to find data set and skip web service wrapper
     *
     * @param $array
     * @return array
     */
    protected function deeper($array)
    {
        $i = 0;
        foreach ($array as $sub) {
            if ($i > 1) {
                return $array;
            }
            $i++;
        }

        return $this->deeper(current($array));
    }

    /**
     * {@inheritDoc}
     */
    public function firstColumn()
    {
        static $first;

        if (!$first) {
            $columns = $this->columns();
            reset($columns);
            $first = key($columns);
            //\Zend\Debug\Debug::dump(get_class_methods($sheet)); die(__METHOD__);
        }
        return $first;
    }

    /**
     * {@inheritDoc}
     */
    public function lastColumn()
    {
        static $last;

        if (!$last) {
            $columns = $this->columns();
            end($columns);
            $last = key($columns) + 1;
        }

        return $last;
    }

    /**
     * {@inheritDoc}
     * @link http://www.libxl.com/spreadsheet.html#lastRow
     */
    public function firstRow()
    {
        static $first;

        if (!$first) {
            $sheet = $this->sheet();
            $first = key($sheet);
        }

        return $first;
    }

    /**
     * {@inheritDoc}
     */
    public function lastRow()
    {
        static $last;

        if (!$last) {
            $sheet = $this->sheet();
            end($sheet);
            $last = key($sheet) + 1;
        }

        return $last;
    }

    /**
     * {@inheritDoc}
     */
    public function read($row, $column = null)
    {
        if (!$column) {
            return $this->sheet()[$row];
        }

        // skip header and convert column index to column name
        if ($row !== $this->firstRow()) {
            $column = $this->columns()[$column];
        }

        //\Zend\Debug\Debug::dump([$row, $column, $this->sheet()[$row][$column]]);
        return $this->sheet()[$row][$column];
    }

    /**
     * {@inheritDoc}
     */
    public function &config(array $config = [])
    {
        return $this->config;
    }
}


<?php
/**
 * LibXl driver
 *
 * @category Popov
 * @package Popov_Importer
 * @author Serhii Popov <popow.serhii@gmail.com>
 * @datetime: 13.05.2016 13:35
 * @link https://github.com/iliaal/php_excel
 */
namespace Popov\Importer\Driver;

use ExcelBook;

class LibXl implements DriverInterface
{
    protected $filename;

    protected $config = [
        'username' => '',
        'password' => '',
        'locale' => 'UTF-8',
        'sheet' => 0,
    ];

    public function __construct(array $config = [])
    {
		//\Zend\Debug\Debug::dump($config); die(__METHOD__);
        $this->config = array_merge($this->config, $config);
    }

    /**
     * {@inheritDoc}
     */
    public function source($filename = null)
    {
        if ($filename) {
            $this->filename = $filename;

            return $this;
        }

        return $this->filename;
    }

    /**
     * {@inheritDoc}
     */
    public function firstColumn()
    {
		//\Zend\Debug\Debug::dump($this->filename); die(__METHOD__);
		//$xlBook = $this->xlBook();

		//$xlSheet = $this->config['sheet'] ? $xlBook->getSheet($this->config['sheet']) : $xlBook->getSheet();

		//$xlSheet = $this->xlBook();
		//\Zend\Debug\Debug::dump($xlSheet->name());die(__METHOD__);
		//\Zend\Debug\Debug::dump($xlSheet->name());
		//\Zend\Debug\Debug::dump(get_class_methods($xlSheet)); die(__METHOD__);

        return $this->xlSheet()->firstCol();
    }

    /**
     * {@inheritDoc}
     */
    public function lastColumn()
    {
        return $this->xlSheet()->lastCol();
    }

    /**
     * {@inheritDoc}
     * @link http://www.libxl.com/spreadsheet.html#lastRow
     */
    public function firstRow()
    {
        return $this->xlSheet()->firstRow();
    }

    /**
     * {@inheritDoc}
     */
    public function lastRow()
    {
        return $this->xlSheet()->lastRow();
    }

    /**
     * {@inheritDoc}
     */
    public function read($row, $column = null)
    {
        return $this->xlSheet()->read($row, $column);
    }

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

    protected function xlBook()
    {
        static $xlBook;

        if (!$xlBook) {
            $splFile = new \SplFileInfo($this->filename);

            $useXlsxFormat = false;
            if ($splFile->getExtension() === 'xlsx') {
                $useXlsxFormat = true;
            }

            $xlBook = new \ExcelBook($this->config['username'], $this->config['password'], $useXlsxFormat);
            $xlBook->setLocale($this->config['locale']);
            $xlBook->loadFile($splFile->getPathname());
        }

        return $xlBook;
    }

	protected function xlSheet()
	{
		static $xlSheet;

		if (!$xlSheet) {
			$xlBook = $this->xlBook();
			$xlSheet = $this->config['sheet'] ? $xlBook->getSheet($this->config['sheet']) : $xlBook->getSheet();
		}

		return $xlSheet;
	}
}


<?php
/**
 * The MIT License (MIT)
 * Copyright (c) 2018 Serhii Popov
 * This source file is subject to The MIT License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * https://opensource.org/licenses/MIT
 *
 * @category Popov
 * @package Popov_Importer
 * @author Serhii Popov <popow.serhii@gmail.com>
 * @license https://opensource.org/licenses/MIT The MIT License (MIT)
 */

namespace Popov\Importer;

use Psr\Container\ContainerInterface;
use Zend\Stdlib\Exception;
use Popov\Importer\Driver;

class DriverCreator
{
    /** @var ContainerInterface */
    protected $container;

    /** @var array */
    protected $config = [];

    /** @var array */
    protected $drivers = [
        'Excel' => Driver\Excel::class,
        'Libxl' => Driver\LibXl::class,
        'Csv' => Driver\Csv::class,
        'Soap' => Driver\Soap::class,
    ];

    public function __construct(ContainerInterface $container = null, array $config = null)
    {
        $this->setConfig($config);
        $this->container = $container;
    }

    public function setConfig($config)
    {
        //$config = isset($config['importer']) ? $config['importer'] : $config;

        $config['drivers'] = array_merge($this->drivers, ($config['drivers'] ?? []));
        $config['tasks'] = $config['tasks'] ?? [];
        // standardizes config key
        foreach ($config['tasks'] as $key => $value) {
            unset($config['tasks'][$key]);
            $config['tasks'][$this->getConfigKey($key)] = $value;
        }
        $this->config = $config;

        return $this;
    }

    public function getConfig()
    {
        return $this->config;
    }

    public function create($configType)
    {
        $taskKey = $this->getConfigKey($configType);
        if (!isset($this->config['tasks'][$taskKey])) {
            throw new Exception\RuntimeException(
                sprintf('Import task "%s" [alias:%s] is not registered', $taskKey, $configType)
            );
        }

        $config = $this->config['tasks'][$taskKey];
        if (!isset($config['driver'])) {
            throw new Exception\RuntimeException('Driver key must be set in the configuration array');
        }

        // developer should control driver naming personally
        //$driverKey = strtolower($config['driver']);
        $driverKey = $config['driver'];
        if (isset($this->config['drivers'][$driverKey])) {
            $driverClass = $this->config['drivers'][$driverKey];
        } else {
            throw new Exception\RuntimeException('No driver registered for ' . $driverKey);
        }

        //$config += isset($this->config['driver_options'][$driverKey]) ? $this->config['driver_options'][$driverKey] : [];
        $config += $config['driver_options'] ?? [];
        $driver = $this->createDriver($driverClass)
            ->config($config);

        return $driver;
    }

    /**
     * Create driver using Container or "new" operator
     *
     * @param string $driverClass
     * @param array $config
     * @return Driver\DriverInterface
     */
    protected function createDriver($driverClass): Driver\DriverInterface
    {
        return isset($this->container)
            ? $this->container->get($driverClass)
            : new $driverClass();
    }

    /**
     * Get standardizes config key
     *
     * @param $key
     * @return string
     */
    protected function getConfigKey($key)
    {
        return strtolower(preg_replace("/[^A-Za-z0-9]/", '', $key));
    }
}


<?php
/**
 * The MIT License (MIT)
 * Copyright (c) 2018 Serhii Popov
 * This source file is subject to The MIT License (OSL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * https://opensource.org/licenses/MIT
 *
 * @category Stagem
 * @package Stagem_Product
 * @author Vlad Kozak <vlad.gem.typ@gmail.com>
 * @license https://opensource.org/licenses/MIT The MIT License (MIT)
 */

namespace Popov\Importer\Helper;

use Popov\Importer\Importer;
use Popov\Variably\Helper\HelperAbstract;
use Popov\Variably\Helper\PrepareInterface;

/**
 * Class PrepareCounter
 *
 * This class is used usually in importer config file in order to write counter for every row data in the table.
 * It is universal class and you can use it in any config file,
 * because it get data about table and last counter from importer variable.
 *
 * @package Popov_Importer
 */
class PrepareCounter extends HelperAbstract implements PrepareInterface
{
    protected $counter;

    /**
     * This method check existing counter and if it isn`t exist it calculate a new one.
     *
     * @param $value
     * @return mixed
     */
    public function prepare($value)
    {
        /** @var Importer $importer */
        $importer = $this->getVariably()->get('importer');
        $table = $importer->getCurrentTable();

        if (!isset($this->counter[$table])) {
            $db = $importer->getDb();
            $counter = $db->fetchOne('SELECT MAX(DISTINCT counter) AS counter FROM ' . $table);

            $this->counter[$table] = $counter['counter'] + 1;
        }

        return $this->counter[$table];
    }
}


<?php
/**
 * Excel driver
 *
 * @category Popov
 * @package Popov_Importer
 * @author Serhii Popov <popow.serhii@gmail.com>
 * @datetime: 13.05.2016 13:35
 * @link https://github.com/PHPOffice/PhpSpreadsheet
 */
namespace Popov\Importer\Driver;

use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Cell;

class Excel implements DriverInterface
{
    protected $source;

    protected $config = [
        //'sheet' => 0,
    ];

    public function __construct(array $config = [])
    {
        $this->config = array_merge($this->config, $config);
    }

    /**
     * {@inheritDoc}
     */
    public function source($filename = null)
    {
        if ($filename) {
            $this->source = $filename;

            return $this;
        }

        return $this->source ?? $this->config['source'];
    }

    /**
     * {@inheritDoc}
     */
    public function firstColumn()
    {
        return 1;
    }

    /**
     * Default value is A1 these means that first row is 1
     *
     * @see \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::$activeCell
     * {@inheritDoc}
     */
    public function firstRow()
    {
        $index = isset($this->config['sheet']['skip']) ? $this->config['sheet']['skip'] + 1 : 1;

        return $index;
    }

    /**
     * {@inheritDoc}
     * @link https://phpspreadsheet.readthedocs.io/en/develop/topics/accessing-cells/#looping-through-cells-using-indexes
     */
    public function lastColumn()
    {
        $xlSheet = $this->xlSheet();
        $highestColumn = $xlSheet->getHighestColumn(); // e.g 'F'
        $highestColumnIndex = Cell\Coordinate::columnIndexFromString($highestColumn); // e.g. 5

        return $highestColumnIndex;
    }

    /**
     * {@inheritDoc}
     * @link https://phpspreadsheet.readthedocs.io/en/develop/topics/accessing-cells/#looping-through-cells-using-indexes
     */
    public function lastRow()
    {
        $xlSheet = $this->xlSheet();
        $highestRow = $xlSheet->getHighestRow();

        return $highestRow;
    }

    /**
     * {@inheritDoc}
     */
    public function read($row, $column = null)
    {
        $xlSheet = $this->xlSheet();

        if (is_null($column)) {
            $rowNames = [];
            $highestColumn = $xlSheet->getHighestColumn(); // e.g 'F'
            $highestColumnIndex = Cell\Coordinate::columnIndexFromString($highestColumn); // e.g. 5
            $value = [];
            for ($col = 1; $col <= $highestColumnIndex; ++$col) {
                if (!isset($rowNames[$col])) {
                    $rowName = $xlSheet->getCellByColumnAndRow($col, $this->firstRow())->getCalculatedValue();
                    $rowNames[$col] = $rowName;
                }

                $rowName = $rowNames[$col];
                if ($rowName) {
                    $value[$rowName] = $xlSheet->getCellByColumnAndRow($col, $row)->getCalculatedValue();
                } else {
                    $value[$col] = $xlSheet->getCellByColumnAndRow($col, $row)->getCalculatedValue();
                }
            }
        } else {
            $value = $xlSheet->getCellByColumnAndRow($column, $row)->getCalculatedValue();
        }

        return $value;
    }

    /**
     * {@inheritDoc}
     */
    public function &config(array $config = [])
    {
        if ($config) {
            $this->config = $config;

            return $this;
        }

        return $this->config;
    }

    protected function xlBook()
    {
        static $spreadsheet;

        if (!$spreadsheet) {
            //$splFile = new \SplFileInfo($this->source);

            $xlBook = IOFactory::createReaderForFile($this->source);
            $xlBook->setReadDataOnly(true);
            $spreadsheet = $xlBook->load($this->source);

            //$reader = IOFactory::createReader($inputFileType);

            //$xlBook->setLocale($this->config['locale']);
            //$xlBook->loadFile($splFile->getPathname());
        }

        return $spreadsheet;
    }

	protected function xlSheet()
	{
		static $xlSheet;

		if (!$xlSheet) {
			$xlBook = $this->xlBook();

			$xlSheet = isset($this->config['sheet']['name'])
                ? $xlBook->setLoadSheetsOnly($this->config['sheet']['name'])
                : $xlBook->getActiveSheet();
		}

		return $xlSheet;
	}
}