<?php
/**
 * Copyright (C) 2012 Igor Fedoronchuk
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @category DIDWW
 * @package API2
 * @author Fedoronchuk Igor <fedoronchuk@gmail.com>
 * @copyright Copyright (C) 2012 by Igor Fedoronchuk
 * @license MIT
 */

namespace Didww\API2;

/**
 * Balance
 * Class that wraps customer's balance operations
 * @category DIDWW
 * @package API2
 * @copyright Copyright (C) 2012 by Igor Fedoronchuk
 * @license MIT
 */
class Balance extends ServerObject
{
    /**
     *operation to remove funds
     */
    const OP_REMOVE = 2;

    /**
     *operation to add funds
     */
    const OP_ADD = 1;

    /**
     * customer id
     * @var int
     */
    protected $customerId = null;

    /**
     * current customer balance value
     * @var float
     */
    protected $prepaidBalance = null;

    /**
     * Unique string to avoid duplicated transaction
     * must be 32 symbols length (use md5)
     * @var string
     */
    protected $_transactionId = null;

    /**
     * value of funds for operation
     * @var float
     */
    protected $_funds = null;

    /**
     * method to return list of all balances from API
     * @return array
     */
    protected static function _getAllBalances()
    {
        $result = self::getClientInstance()->call('getprepaidbalancelist', array(
            "customer_id" => '0'
        ));
        $resellerSubAccount = (object)array('customer_id' => '0', 'prepaid_balance_amount' => '0.0000'); // 0 - is reseller's sub account
        array_unshift($result, $resellerSubAccount);
        return $result;
    }

    /**
     * method to return list of all balances indexed by customer id
     * @link http://open.didww.com/index.php/16._Get_Prepaid_Balance_List
     * @todo change name to getArray
     * @return array
     */
    public static function getBalanceArray()
    {
        $balances = array();
        foreach (self::_getAllBalances() as $value) {
            $balances[$value->customer_id] = $value->prepaid_balance_amount;
        }
        return $balances;
    }

    /**
     * method to return list of DidwwBalance objects from API
     * @link http://open.didww.com/index.php/16._Get_Prepaid_Balance_List
     * @todo change name to getList
     * @return array
     */
    public static function getBalanceList()
    {
        $balances = array();
        foreach (self::_getAllBalances() as $value) {
            $value = (array)$value;
            unset($value['result']);
            unset($value['error']);
            $balance = new Balance(array("prepaid_balance" => $value['prepaid_balance_amount'], "customer_id" => $value["customer_id"]));
            $balances[$value['customer_id']] = $balance;
        }
        return $balances;
    }

    /**
     * set prepaidBalance property
     * @param float $value
     */
    public function setPrepaidBalance($value)
    {
        $this->prepaidBalance = $value;
    }

    /**
     * get customerId property
     * @return int
     */
    public function getCustomerId()
    {
        return $this->customerId;
    }

    /**
     * set customerId property
     * @param int $customerId
     */
    public function setCustomerId($customerId)
    {
        $this->customerId = $customerId;
    }

    /**
     * get prepaidBalance property
     * @return int
     */
    public function getPrepaidBalance()
    {
        return $this->prepaidBalance;
    }

    /**
     * method to remove funds from customer balance
     * @param float $funds
     * @param string $transactionId md5 hash of transaction to avoid duplications
     * @return Balance
     */
    function removeFunds($funds, $transactionId = NULL)
    {
        return $this->changeBalance(abs($funds) * -1, $transactionId);
    }

    /**
     * method to add funds to customer balance
     * @param float $funds
     * @param string $transactionId md5 hash of transaction to avoid duplications
     * @return Balance
     */
    function addFunds($funds, $transactionId = NULL)
    {
        return $this->changeBalance(abs($funds), $transactionId);
    }

    /**
     * method to change customer balance with funds value
     * @link http://open.didww.com/index.php/10._Update_Prepaid_Balance
     * @param float $funds
     * @param string $transactionId d5 hash of transaction to avoid duplications
     * @return Balance
     * @throws BalanceException
     */
    function changeBalance($funds, $transactionId = NULL)
    {
        if (!$this->getCustomerId() > 0) {
            throw new BalanceException("Customer is invalid");
        }

        if (!(is_numeric($funds))) {
            throw new BalanceException("Funds value is invalid");
        }

        $this->_funds = $funds;
        $this->_transactionId = ($transactionId ? $transactionId : $this->generateTransactionId());

        if (!is_string($this->_transactionId) || strlen($this->_transactionId) != 32) {
            throw new BalanceException(" TransactionId value is invalid");
        }

        $response = $this->call('updateprepaidbalance', array(
            "customer_id" => $this->getCustomerId(),
            "prepaid_funds" => $this->_funds,
            "operation_type" => ($this->_funds > 0) ? self::OP_ADD : self::OP_REMOVE,
            "uniq_hash" => $this->_transactionId
        ));
        //set prepaid balance value

        $this->setPrepaidBalance($response['prepaid_balance']);
        return $this;
    }

    function getBilledServices()
    {
        $customer_id = $this->getCustomerId();
        if (!isset($customer_id)) {
            throw new BalanceException("Customer Id is undefined");
        }
        $response = $this->call('getservicelist', array(
            "customer_id" => $this->getCustomerId()
        ));
        $orders = array();
        if (count($response)) {
            foreach ($response as $serviceData) {
                $order = new \Didww\API2\Order();
                $order->setCustomerId($this->getCustomerId());
                $order->fromServiceData($serviceData);
                $orders[] = $order;
            }

        }
        return $orders;
    }

    /**
     * method to set current value of customer balance from API
     * @link http://open.didww.com/index.php/9._Get_Prepaid_Balance
     * @return Balance
     */
    function synchronizePrepaidBalance()
    {
        if (!$this->getCustomerId()) {
            throw new BalanceException("Customer Id is undefined");
        }

        $response = $this->call('getprepaidbalance', array(
            "customer_id" => $this->getCustomerId()
        ));

        $this->setPrepaidBalance($response['prepaid_balance']);
        return $this;
    }

    /**
     * generate unique md5 transaction string
     * @return string
     */
    protected function generateTransactionId()
    {
        return md5($this->customerId . "." . $this->_funds . "." . gmdate("YmdHis"));
    }

}

/**
 * BalanceException
 * Exception for class Balance
 * @category DIDWW
 * @package API2
 * @copyright Copyright (C) 2012 by Igor Fedoronchuk
 * @license MIT
 */
class BalanceException extends BaseException
{
}


<?php

/**
 * Copyright (C) 2012 Igor Fedoronchuk
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @category DIDWW
 * @package API2
 * @author Igor Fedoronchuk <fedoronchuk@gmail.com>
 * @copyright Copyright (C) 2012 by Igor Fedoronchuk
 * @license MIT
 */
namespace Didww\API2;

/**
 * CDRCollection
 * wraps collection of cdrs from API2 for per-page navigation
 * @category DIDWW
 * @package API2
 * @copyright Copyright (C) 2012 by Igor Fedoronchuk
 * @license MIT
 */
class CDRCollection extends \Didww\API2\ServerPaginatedCollection
{
    /**
     * filter by didNumber
     * @var string
     */
    protected $didNumber = null;

    /**
     * get didNumber property
     * @return string
     */
    public function getDidNumber()
    {
        return $this->didNumber;
    }

    /**
     * set didNumber property
     * @param string $didNumber
     */
    public function setDidNumber($didNumber)
    {
        $this->didNumber = $didNumber;
    }

    /**
     * get method for cdrlog
     * @return string
     */
    protected function getMethod()
    {
        return "getcdrlog";
    }

    /**
     *get arguments for smslog method
     * @return array
     */
    protected function getArgs()
    {
        return array(
            'customer_id' => $this->getCustomerId(),
            'did_number' => $this->getDidNumber(),
            'from_date' => $this->getFromDate(),
            'to_date' => $this->getToDate(),
            'limit' => $this->getLimit(),
            'offset' => $this->getOffset(),
            'order' => $this->getOrderBy(),
            'order_Dir' => $this->getOrderDir(),
        );
    }

    /**
     * get type of collection
     * @return string
     */

    protected function getCollectionType()
    {
        return 'Didww\API2\CDR';
    }

    /**
     *get key of response
     * @return string
     */
    protected function getResponseKey()
    {
        return "cdrs";
    }

    /**
     * add element to list
     * @param stdObject $value
     */
    protected function appendToList($value)
    {
        $this->_list[] = new CDR((array)$value);
    }

}

/**
 * CDRCollectionException
 * Exception for class CDRCollection
 * @category DIDWW
 * @package API2
 * @copyright Copyright (C) 2012 by Igor Fedoronchuk
 * @license MIT
 */
class CDRCollectionException extends BaseException
{
}


<?php

namespace Intervention\Gif;

class Encoder
{
    /**
     * Canvas width
     *
     * @var integer
     */
    public $canvasWidth;

    /**
     * Canvas height
     *
     * @var integer
     */
    public $canvasHeight;

    /**
     * Global color table
     *
     * @var string
     */
    public $globalColorTable;

    /**
     * Index of background color
     *
     * @var int
     */
    public $backgroundColorIndex = 0;

    /**
     * Number of sequence loops
     *
     * @var integer
     */
    public $loops;

    /**
     * Image frames
     *
     * @var array
     */
    public $frames = array();

    /**
     * Set canvas dimensions
     *
     * @param  int $width
     * @param  int $height
     * @return \Intervention\Image\Gd\Gif\Encoder
     */
    public function setCanvas($width, $height)
    {
        $this->canvasWidth = $width;
        $this->canvasHeight = $height;

        return $this;
    }

    /**
     * Set number of loops
     *
     * @param  int $value
     * @return \Intervention\Image\Gd\Gif\Encoder
     */
    public function setLoops($value)
    {
        $this->loops = $value;

        return $this;
    }

    /**
     * Set global color table
     *
     * @param  int $value
     * @return \Intervention\Image\Gd\Gif\Encoder
     */
    public function setGlobalColorTable($value)
    {
        $this->globalColorTable = $value;

        return $this;
    }

    /**
     * Return global color table of encoder
     *
     * @return string
     */
    public function getGlobalColorTable()
    {
        return $this->globalColorTable;
    }

    /**
     * Setup frame stack of encoder
     *
     * @param Array $frames
     */
    public function setFrames(Array $frames)
    {
        $this->frames = $frames;

        return $this;
    }

    /**
     * Set background color index for encoder
     *
     * @param string $index
     */
    public function setBackgroundColorIndex($index)
    {
        $this->backgroundColorIndex = $index;

        return $this;
    }

    /**
     * Setup encoder from Decoded object
     *
     * @param Decoded  $decoded
     * @param int|null $frameIndex
     * @return \Intervention\Image\Gd\Gif\Encoder
     */
    public function setFromDecoded(Decoded $decoded, $frameIndex = null)
    {
        if (is_null($frameIndex)) {
            // setup from all decoded data
            $width = $decoded->getCanvasWidth();
            $height = $decoded->getCanvasHeight();
            $colorTable = $decoded->getGlobalColorTable();
            $loops = $decoded->getLoops();
            $frames = $decoded->getFrames();
        } else {
            // setup only one specific frame
            $frame = $decoded->getFrame($frameIndex);
            $frame->setOffset(0, 0);
            $width = $frame->decodeWidth();
            $height = $frame->decodeHeight();
            $colorTable = $frame->hasLocalColorTable() ? $frame->getLocalColorTable() : $decoded->getGlobalColorTable();
            $loops = 0;
            $frames = array($frame);
        }

        // setup
        $this->setCanvas($width, $height);
        $this->setGlobalColorTable($colorTable);
        $this->setBackgroundColorIndex($decoded->getBackgroundColorIndex());
        $this->setLoops($loops);
        $this->setFrames($frames);

        return $this;
    }

    /**
     * Add one frame to stack
     *
     * @param Frame $frame
     */
    public function addFrame(Frame $frame)
    {
        $this->frames[] = $frame;
    }

    /**
     * Create and add new frame from GD resource
     *
     * @param  resource $resource
     * @param  integer  $delay
     * @return void
     */
    public function createFrameFromGdResource($resource, $delay = null)
    {
        // get imagedata from resource
        $gifdata = $this->encodeGdResource($resource);
        $decoder = new Decoder;
        $gif = $decoder->initFromData($gifdata)->decode();

        $frame = $gif->getFrames()[0];
        $frame->setLocalColorTable($gif->getGlobalColorTable());
        $frame->setDelay($delay);

        $this->frames[] = $frame;
    }

    /**
     * Encode image data
     *
     * @return string
     */
    public function encode()
    {
        // create gif
        $encoded = $this->buildLogicalScreenDescriptor();

        if ($this->hasGlobalColorTable()) {
            $encoded .= $this->getGlobalColorTable();
        }

        // netscape extension
        if ($this->isAnimated() && $this->doesLoop()) {
            $encoded .= $this->buildNetscapeExtension();
        }

        // add frame(s)
        foreach ($this->frames as $frame) {
            $encoded .= $this->buildFrame($frame);
        }

        // EOF
        $encoded .= "\x3B";

        return $encoded;
    }

    /**
     * Build logical screen descriptor
     *
     * @return string
     */
    private function buildLogicalScreenDescriptor()
    {
        // gif header
        $descriptor = 'GIF89a';

        // canvas width/height
        $descriptor .= pack('v*', $this->canvasWidth);
        $descriptor .= pack('v*', $this->canvasHeight);

        // packed field
        $colorResolution = 111;
        $sortFlag = 0;

        if ($this->hasGlobalColorTable()) {

            $globalColorTableFlag = 1;
            $globalColorTableSize = log(strlen($this->getGlobalColorTable()) / 3, 2) - 1;
            $globalColorTableSize = decbin($globalColorTableSize);
            $globalColorTableSize = str_pad($globalColorTableSize, 3, 0, STR_PAD_LEFT);

        } else {
            $globalColorTableFlag = 0;
            $globalColorTableSize = 0;
        }

        $packed = $globalColorTableFlag.$colorResolution.$sortFlag.$globalColorTableSize;

        $descriptor .= pack('C', bindec($packed));

        // background color index
        $descriptor .= pack('C', $this->backgroundColorIndex);

        // pixel aspect ratio
        $descriptor .= "\x00";

        return $descriptor;
    }

    /**
     * Build Netscape extension
     *
     * @return string
     */
    private function buildNetscapeExtension()
    {
        $extension = "\x21";
        $extension .= "\xFF";
        $extension .= "\x0B";
        $extension .= 'NETSCAPE2.0';
        $extension .= "\x03";
        $extension .= "\x01";
        $extension .= pack('v', $this->loops);
        $extension .= "\x00";

        return $extension;
    }

    /**
     * Build encoded Frame
     *
     * @param  Frame  $frame
     * @return string
     */
    private function buildFrame(Frame $frame)
    {
        // graphics control extensions
        $encoded = $this->buildGraphicsControlExtension($frame);

        // image descriptor
        $encoded .= $this->buildImageDescriptor($frame);

        // add image data
        $encoded .= $frame->getImageData();

        return $encoded;
    }

    /**
     * Build encoded graphics control extension for frame
     *
     * @param  Frame  $frame
     * @return string
     */
    private function buildGraphicsControlExtension(Frame $frame)
    {
        // start
        $extension = "\x21\xF9";

        // byte size
        $extension .= "\x04";

        // packed field
        $disposalMethod = decbin($frame->getDisposalMethod());
        $disposalMethod = str_pad($disposalMethod, 3, 0, STR_PAD_LEFT);
        $userInputFlat = 0;
        $transparentColorFlag = $frame->hasTransparentColor() ? 1 : 0;
        $packed = $disposalMethod.$userInputFlat.$transparentColorFlag;
        $packed = str_pad($packed, 3, 0, STR_PAD_LEFT);
        $extension .= pack('C', bindec($packed));

        // delay
        $extension .= pack('v*', $frame->getDelay());

        // transparent color index
        if ($frame->hasTransparentColor()) {
            $extension .= $frame->getTransparentColorIndex();
        } else {
            $extension .= "\x00";
        }

        // block terminator
        $extension .= "\x00";

        return $extension;
    }

    /**
     * Build encoded image descriptor for frame
     *
     * @param  Frame  $frame
     * @return string
     */
    private function buildImageDescriptor(Frame $frame)
    {
        // seperator
        $descriptor = "\x2C";

        // image left/top
        $descriptor .= pack('v*',
            $frame->offset->left,
            $frame->offset->top
        );

        // image width/height
        $descriptor .= pack('v*',
            $frame->size->width,
            $frame->size->height
        );

        $interlacedFlag = $frame->isInterlaced() ? 1 : 0;
        $reserved1 = 0;
        $reserved2 = 0;

        if ($frame->hasLocalColorTable()) {
            $colorTableFlag = 1;
            $colorTableSize = log(strlen($frame->getLocalColorTable()) / 3, 2) - 1;
            $colorTableSize = decbin($colorTableSize);
            $sortFlag = 0;
        } else {
            $colorTableFlag = 0;
            $colorTableSize = 0;
            $sortFlag = 0;
        }

        $colorTableSize = str_pad($colorTableSize, 3, 0, STR_PAD_LEFT);

        // packed field
        $packed = $colorTableFlag.$interlacedFlag.$sortFlag.$reserved1.$reserved2.$colorTableSize;
        $descriptor .= pack('C', bindec($packed));

        if ($frame->hasLocalColorTable()) {
            // add local color table
            $descriptor .= $frame->getLocalColorTable();
        }

        return $descriptor;
    }

    /**
     * Determines if encoder has global color table
     *
     * @return boolean
     */
    private function hasGlobalColorTable()
    {
        return isset($this->globalColorTable);
    }

    /**
     * Encode GD resource to GIF string
     *
     * @param  resource $resource
     * @return string
     */
    private function encodeGdResource($resource)
    {
        ob_start();
        imagegif($resource);
        $buffer = ob_get_contents();
        ob_end_clean();

        return $buffer;
    }

    /**
     * Determines if current encoder is set up animated
     *
     * @return boolean
     */
    public function isAnimated()
    {
        return count($this->frames) > 1;
    }

    /**
     * Determines if current encoder is set up to loop animation
     *
     * @return boolean
     */
    public function doesLoop()
    {
        return is_integer($this->loops);
    }
}


<?php

/**
 * Copyright (C) 2012 Igor Fedoronchuk
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @category DIDWW
 * @package API2
 * @copyright Copyright (C) 2012 by Igor Fedoronchuk
 * @license MIT
 */
namespace Didww\API2;
/**
 * DIDNumber
 * Class to wrap number object
 * @category DIDWW
 * @package API2
 * @copyright Copyright (C) 2012 by Igor Fedoronchuk
 * @license MIT
 */
class DIDNumber extends ServerObject
{
    /**
     * @const Didww Order completed status
     */
    const ORDER_STATUS_COMPLETED = 'Completed';
    /**
     * @const Didww Order pending status
     */
    const ORDER_STATUS_PENDING = 'Pending';
    /**
     * @const Didww Order cenceled status
     */
    const ORDER_STATUS_CANCELED = 'Canceled';
    /**
     * @const Didww number blocked status
     */
    const DID_STATUS_BLOCKED = -1;
    /**
     * @const Didww number removed status
     */
    const DID_STATUS_REMOVED = -2;
    /**
     * @const Didww number active status
     */
    const DID_STATUS_ACTIVE = 1;
    /**
     * @const Didww number unknown status
     */
    const DID_STATUS_UNKNOWN = 0;

    /**
     * country name of didnumber
     * @var string
     */
    protected $countryName = null;

    /**
     * city name of didnumber
     * @var string
     */
    protected $cityName = null;

    /**
     * didnumber assigned
     * @var string
     */
    protected $didNumber = null;

    /**
     * status of did
     * @var string 1|0|-1|-2
     */
    protected $didStatus = null;

    /**
     * timeleft of did number
     * @var string
     */
    protected $didTimeleft = null;

    /**
     * expired date of didnumber
     * @var string
     */
    protected $didExpireDateGmt = null;

    /**
     * didww order id
     * @var string
     */
    protected $orderId = null;

    /**
     * status of didww order Completed | Canceled | Pending
     * @var string
     */
    protected $orderStatus = null;

    /**
     * specific did mapping string
     * @var string
     */
    protected $didMappingFormat = null;

    /**
     * setup price paid for did
     * @var string
     */
    protected $didSetup = null;

    /**
     * monthly  price paid for did
     * @var string
     */
    protected $didMonthly = null;

    /**
     * paid period
     * @var string
     */
    protected $didPeriod = null;

    /**
     * prepaid amount of customer that was paid
     * @var string
     */
    protected $prepaidBalance = null;

    /**
     * Order object related to current DIDnumber object
     * @var Order
     */
    protected $_order = null;

    /**
     * Construct new didnumber object
     * @param Order $order
     */
    function __construct(Order $order = NULL)
    {
        $this->setOrder($order);
        parent::__construct();
    }

    /**
     * get _order property
     * @return Order
     */
    function getOrder()
    {
        return $this->_order;
    }

    /**
     *set _order property
     * @param Order $order
     */
    function setOrder(Order $order = NULL)
    {
        $this->_order = $order;
    }

    /**
     * get countryName property
     * @return string
     */
    function getCountryName()
    {
        return $this->countryName;
    }

    /**
     * get cityName property
     * @return string
     */
    function getCityName()
    {
        return $this->cityName;
    }

    /**
     * get didNumber property
     * @return string
     */
    function getDidNumber()
    {
        return $this->didNumber;
    }

    /**
     * get didStatus property
     * @return string
     */
    function getDidStatus()
    {
        return (int)$this->didStatus;
    }

    /**
     *get didTimeleft property
     * @return string
     */
    function getDidTimeLeft()
    {
        return $this->didTimeleft;
    }

    /**
     * get didExpireDateGmt property
     * @return string
     */
    function getDidExpireDateGmt()
    {
        return $this->didExpireDateGmt;
    }

    /**
     * get didMappingFormat property
     * @return string
     */
    function getDidMappingFormat()
    {
        return $this->didMappingFormat;
    }

    /**
     * get orderId property
     * @return string
     */
    function getOrderId()
    {
        return $this->orderId;
    }

    /**
     * get orderStatus property
     * @return string
     */
    function getOrderStatus()
    {
        return $this->orderStatus;
    }

    /**
     *get prepaidBalance property
     * @return string
     */
    function getPrepaidBalance()
    {
        return $this->prepaidBalance;
    }

    /**
     * get didMonthly property
     * @return string
     */
    function getDidMonthly()
    {
        return $this->didMonthly;
    }

    /**
     * get didSetup property
     * @return string
     */
    function getDidSetup()
    {
        return $this->didSetup;
    }

    /**
     * get didPeriod property
     * @return string
     */
    function getDidPeriod()
    {
        return $this->didPeriod;
    }

    /**
     * set countryName property
     * @param string $countryName
     */
    public function setCountryName($countryName)
    {
        $this->countryName = $countryName;
    }

    /**
     *set cityName property
     * @param string $cityName
     */
    public function setCityName($cityName)
    {
        $this->cityName = $cityName;
    }

    /**
     * set didNumber property
     * @param string $didNumber
     */
    public function setDidNumber($didNumber)
    {
        $this->didNumber = $didNumber;
    }

    /**
     *set didStatus property
     * @param int $didStatus
     */
    public function setDidStatus($didStatus)
    {
        $this->didStatus = (int)$didStatus;
    }

    /**
     *set didTimeleft property
     * @param string $didTimeleft
     */
    public function setDidTimeleft($didTimeleft)
    {
        $this->didTimeleft = (int)$didTimeleft;
    }

    /**
     *set didExpireDateGmt property
     * @param string $didExpireDateGmt
     */
    public function setDidExpireDateGmt($didExpireDateGmt)
    {
        $this->didExpireDateGmt = $didExpireDateGmt;
    }

    /**
     * set orderId property
     * @param int $orderId
     */
    public function setOrderId($orderId)
    {
        $this->orderId = (int)$orderId;
    }

    /**
     * set orderStatus property
     * @param string $orderStatus
     */
    public function setOrderStatus($orderStatus)
    {
        $this->orderStatus = $orderStatus;
    }

    /**
     *set didMappingFormat property
     * @param string $didMappingFormat
     */
    public function setDidMappingFormat($didMappingFormat)
    {
        $this->didMappingFormat = $didMappingFormat;
    }

    /**
     * set didSetup property
     * @param float $didSetup
     */
    public function setDidSetup($didSetup)
    {
        $this->didSetup = (float)$didSetup;
    }

    /**
     * set didMonthly property
     * @param float $didMonthly
     */
    public function setDidMonthly($didMonthly)
    {
        $this->didMonthly = (float)$didMonthly;
    }

    /**
     * set didPeriod property
     * @param int $didPeriod
     */
    public function setDidPeriod($didPeriod)
    {
        $this->didPeriod = (int)$didPeriod;
    }

    /**
     * set prepaidBalance property
     * @param float $prepaidBalance
     */
    public function setPrepaidBalance($prepaidBalance)
    {
        $this->prepaidBalance = (float)$prepaidBalance;
    }

    /**
     * magic __toString
     * @return string
     */
    public function __toString()
    {
        return $this->getDIDNumber();
    }

    /**
     * create did number for order
     * @link http://open.didww.com/index.php/5._Order_Create
     * @param Order $order
     * @return DIDNumber
     */
    public static function create(Order $order)
    {
        $properties = self::getClientInstance()->call('ordercreate', $order->toArray(array('includeNumber' => false)));
        $did = new DIDNumber($order);
        $did->getOrder()->setCityId($properties['city_id']);
        unset($properties['result'], $properties['autorenew_enable'], $properties['city_prefix'], $properties['country_iso'], $properties['country_iso'], $properties['city_id']);
        $did->fromArray($properties);
        return $did;
    }

    /**
     * change API2 order autorenew enable
     * @param bool $flag
     */
    function changeAutorenew($flag)
    {
        $this->call('order_autorenew_status', array(
            "customer_id" => $this->_order->getCustomerId(),
            "did_number" => $this->getDIDNumber(),
            "status" => (int)$flag
        ));
        $this->_order->setAutorenewEnable($flag);
    }

    /**
     * renew order for current period
     * @link http://open.didww.com/index.php/7._Order_Autorenew
     * @return DIDNumber
     */
    public function renew()
    {
        $properties = $this->call('orderautorenew', array(
            "customer_id" => $this->_order->getCustomerId(),
            "did_number" => $this->getDIDNumber(),
            "period" => $this->_order->getPeriod(),
            "uniq_hash" => $this->generateUniqueHash()
        ));

        $this->getOrder()->setCityId($properties['city_id']);
        unset($properties['result'], $properties['autorenew_enable'], $properties['city_prefix'], $properties['country_iso'], $properties['city_id']);

        unset($properties['result'], $properties['autorenew_enable']);
        $this->fromArray($properties);
        return $this;
    }

    /**
     * cancel order and deactivate did number
     * http://open.didww.com/index.php/6._Order_Cancel
     * @return DIDNumber
     */
    public function cancel()
    {
        $this->call('ordercancel', array(
            "customer_id" => $this->_order->getCustomerId(),
            "did_number" => $this->getDIDNumber()
        ));
        $this->orderStatus = self::ORDER_STATUS_CANCELED;
        $this->didStatus = self::DID_STATUS_BLOCKED;
        return $this;
    }

    /**
     * restore order
     * @return DIDNumber
     */
    public function restore()
    {
        $this->call('didrestore', array(
            "customer_id" => $this->_order->getCustomerId(),
            "did_number" => $this->getDIDNumber(),
            "period" => $this->_order->getPeriod(),
            "uniq_hash" => $this->generateUniqueHash(),
            "isrenew" => $this->_order->getAutorenewEnable()
        ));
        $this->didStatus = self::DID_STATUS_ACTIVE;
        return $this;
    }

    /**
     * reload dudnumber data by did
     * http://open.didww.com/index.php/13._Get_Service_Details
     * @return DIDNumber
     */
    public function reloadByNumber()
    {
        $this->_reload($this->getDIDNumber());
        return $this;
    }

    /**
     * reload dudnumber data by didww order id
     * http://open.didww.com/index.php/13._Get_Service_Details
     * @return DIDNumber
     */
    public function reloadByOrderId()
    {
        $this->_reload(NULL, $this->getOrderId());
        return $this;
    }

    public function  fromServiceData($properties)
    {
        $properties = (array)$properties;
        if ($this->_order) {
            $this->_order->setCountryIso($properties['country_iso']);
            $this->_order->setCityPrefix($properties['city_prefix']);
            $this->_order->setAutorenewEnable($properties['autorenew_enable']);

            // only URI supporting
            if (preg_match("/^URI:(\w+)\/(\S+)$/", $properties['did_mapping_format'], $matches)) {

                if (count($matches) == 3) {
                    $mapping = Mapping::create(
                        array(
                            "map_type" => 'URI',
                            "map_proto" => $matches[1],
                            "map_detail" => $matches[2]
                        )
                    );
                    $this->_order->setMapData($mapping);
                }
            }
        }
        unset($properties['city_prefix'], $properties['country_iso'], $properties['autorenew_enable']);
        $assignType = $this->getAssignType();

        $this->setAssignType(\Didww\API2\Object::ASSIGN_IGNORE);
        $this->fromArray($properties);
        $this->setAssignType($assignType);
    }

    /**
     * reload didnumber by number or order id
     * @param string $didNumber
     * @param string $orderId
     */
    protected function _reload($didNumber = NULL, $orderId = NULL)
    {
        $properties = $this->call('getservicedetails', array(
            "customer_id" => $this->_order->getCustomerId(),
            "order_id" => $orderId,
            "did_number" => $didNumber
        ));
        unset($properties['result']);
        $this->fromServiceData($properties);
    }

    /**
     * update mapping of did number
     * @link http://open.didww.com/index.php/8._Update_Mapping
     * @param Mapping $newMapping
     */
    public function updateMapping(Mapping $newMapping = NULL)
    {
        $mapping = empty($newMapping) ? $this->_order->getMapData() : $newMapping;
        $this->call('updatemapping', array(
            "customer_id" => $this->_order->getCustomerId(),
            "did_number" => $this->getDIDNumber(),
            "map_data" => $mapping->toArray()
        ));
        $this->_order->setMapData($newMapping);
        return true;
    }

    /**
     * check if didnumer is active
     * @return bool
     */
    public function isActive()
    {
        return $this->getDidStatus() == self::DID_STATUS_ACTIVE;
    }

    /**
     * check if didnumer is blocked
     * @return bool
     */
    public function isBlocked()
    {
        return $this->getDidStatus() == self::DID_STATUS_BLOCKED;
    }

    /**
     * check if didnumer is removed
     * @return bool
     */
    public function isRemoved()
    {
        return $this->getDidStatus() == self::DID_STATUS_REMOVED;
    }

    /**
     * check if order is completed
     * @return bool
     */
    public function isOrderCompleted()
    {
        return $this->getOrderStatus() == self::ORDER_STATUS_COMPLETED;
    }

    /**
     * check if order is canceled
     * @return bool
     */
    public function isOrderCanceled()
    {
        return $this->getOrderStatus() == self::ORDER_STATUS_CANCELED;
    }

    /**
     * check if order is pending
     * @return bool
     */
    public function isOrderPending()
    {
        return $this->getOrderStatus() == self::ORDER_STATUS_PENDING;
    }

    /**
     * generate unique hash for did renew operation
     * @return string
     */
    protected function generateUniqueHash()
    {
        return $this->_order->getUniqHash() ?:
            md5(sprintf("%s.%s.%s.%s.%s.%s",
                $this->getDIDExpireDateGmt(),
                $this->getOrderId(),
                $this->getDIDMappingFormat(),
                $this->getDIDPeriod(),
                $this->getDIDTimeLeft(),
                $this->getDIDNumber()
            ));
    }

}


<?php

namespace Morphatic\AutoDeploy\Commands;

use Illuminate\Support\Str;
use Illuminate\Console\Command;

class DeployInitCommand extends Command
{
    /**
     * The options available with this command.
     *
     * @var string
     */
    protected $signature = 'deploy:init
                            {--show : Only display the current values (do NOT use with --force)}
                            {--force : Force current values to be overwritten}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Generate a secret key and route name for automated deployments';

    /**
     * Execute the console command.
     */
    public function handle()
    {
        // Set necessary variables
        $path = base_path('.env');
        $url = parse_url(config('app.url'), PHP_URL_HOST);
        $ssl = config('auto-deploy.require-ssl') ? 'https' : 'http';
        $msg = "Here is the information you'll need to set up your webhooks:\n\n".
               "  Payload URL: <comment>$ssl://$url/%s</comment>\n".
               "  Secret Key:  <comment>%s</comment>\n\n".
               "You can display this information again by running `php artisan deploy:info`\n";
        $conf = 'Are you sure you want to overwrite the existing keys?';
        $show = $this->option('show');
        $over = $this->option('force');
        $file = file_exists($path) ? file_get_contents($path) : '';
        $set = preg_match('/^(?=.*\bAUTODEPLOY_SECRET=(\S+)\b)(?=.*\bAUTODEPLOY_ROUTE=(\S+)\b).*$/s', $file, $keys);

        // Step 0: Handle edge cases
        if ($show and $over) {
            return $this->error("You can't use --force and --show together!");
        }

        if ($show and !$set) {
            if ($this->confirm("You don't have any values to show yet. Would you like to create them now?", true)) {
                $show = false;
            } else {
                return;
            }
        }

        // Step 1: Retrieve or Generate?
        if (($set and $show) || ($set and !$show and !$over and !$this->confirm($conf))) {
            // Retrieve
            $secret = $keys[1];
            $route = $keys[2];
        } else {
            // Generate
            $secret = $this->getRandomKey($this->laravel['config']['app.cipher']);
            $route = $this->getRandomKey($this->laravel['config']['app.cipher']);
            if (!$show) {
                if ($file) {
                    if ($set) {
                        // Replace existing keys
                        file_put_contents(
                            $path,
                            preg_replace(
                                '/AUTODEPLOY_SECRET=\S+\n/',
                                "AUTODEPLOY_SECRET=$secret\n",
                                $file
                            )
                        );
                        file_put_contents(
                            $path,
                            preg_replace(
                                '/AUTODEPLOY_ROUTE=\S+\n/',
                                "AUTODEPLOY_ROUTE=$route\n",
                                file_get_contents($path)
                            )
                        );
                    } else {
                        // Append to existing environment variables
                        file_put_contents($path, $file."\n\nAUTODEPLOY_SECRET=$secret\nAUTODEPLOY_ROUTE=$route\n");
                    }
                } else {
                    // Create new .env file
                    file_put_contents($path, "AUTODEPLOY_SECRET=$secret\nAUTODEPLOY_ROUTE=$route\n");
                }
            }
        }

        // Step 2: Set the values in the global config
        $this->laravel['config']['auto-deploy.secret'] = $secret;
        $this->laravel['config']['auto-deploy.route'] = $route;

        // Step 3: Display the keys
        return $this->line(sprintf($msg, $route, $secret));
    }

    /**
     * Generate random keys for the auto deploy package.
     *
     * @param string $cipher
     *
     * @return string
     */
    protected function getRandomKey($cipher)
    {
        if ($cipher === 'AES-128-CBC') {
            return Str::random(16);
        }

        return Str::random(32);
    }
}


<?php

namespace Morphatic\AutoDeploy\Origins;

use Illuminate\Http\Request;

abstract class AbstractOrigin implements OriginInterface
{
    /**
     * The name of the origin.
     *
     * @var string
     */
    public $name;

    /**
     * The Request object associated with this webhook.
     *
     * @var Illuminate\Http\Request
     */
    public $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    /**
     * Determines whether or not the Request originated from the webhook origin.
     *
     * @return bool Returns true if the request originated from this origin. False otherwise.
     */
    abstract public function isOrigin();

    /**
     * Verifies the authenticity of a webhook request from the origin.
     *
     * @return bool Returns true if the request is authentic. False otherwise.
     */
    abstract public function isAuthentic();

    /**
     * Gets the event the triggered the webhook request.
     *
     * @return string The name of the event, e.g. push, release, create, etc.
     */
    abstract public function event();

    /**
     * Gets the URL to be cloned from.
     *
     * @return string The URL of the repo.
     */
    abstract public function getRepoUrl();

    /**
     * Gets the ID of the commit that is to be cloned.
     *
     * @return string The commit ID.
     */
    abstract public function getCommitId();

    /**
     * Determines if a target IP address is within a particular IP address range.
     *
     * @param string $targetIp The IP address to check
     * @param string $rangeIp  The IP address defining the range
     * @param int    $cidrMask The CIDR notation net mask defining the range
     *
     * @return bool True if the target IP falls within the specified range. Otherwise false.
     */
    protected function isIpInRange($targetIp, $rangeIp, $cidrMask)
    {
        $target = sprintf('%032b', ip2long($targetIp));
        $range = sprintf('%032b', ip2long($rangeIp));

        return 0 === substr_compare($target, $range, 0, $cidrMask);
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container;

use Closure;
use ClanCats\Container\{

    // exceptions
    Exceptions\UnknownServiceException,
    Exceptions\InvalidServiceException,
    Exceptions\ContainerException
};

class Container
{
    /**
     * The parameters
     *
     * @var array
     */
    protected $parameters = [];

    /**
     * A mapping array to know how a service must be resolved.
     *
     * @var array
     */
    const RESOLVE_METHOD = 0;
    const RESOLVE_PROVIDER = 1;
    const RESOLVE_FACTORY = 2;
    const RESOLVE_SHARED = 3;
    const RESOLVE_SETTER = 4;
    protected $serviceResolverType = [];

    /**
     * An array of services and their provider
     *
     * @var array[string => ServiceProviderInterface]
     */
    protected $serviceProviders = [];

    /**
     * An array of methods that resolve a service inside the current container.
     * This is mostly for the cached / dumped container which creates custom methods
     * for each service to (try to) improve performance of the container.
     *
     * @var array
     */
    protected $resolverMethods = [];

    /**
     * An array of factories ojects.
     *
     * @var array[ServiceFactoryInterface|Closure]
     */
    private $resolverFactories = [];

    /**
     * Array of already resolved shared factories
     *
     * @var array
     */
    protected $resolvedSharedServices = [];

    /**
     * The actual metadata by key & service name
     *
     *    [metakey][service] = [[meta array], [meta array]]
     *
     * @var array[string => array]
     */
    protected $metadata = [];

    /**
     * List of metadata keys by service name
     *
     *    [service] = [metakey array]
     *
     * @var string[array]
     */
    protected $metadataService = [];

    /**
     * Construct a new container instance with inital parameters.
     *
     * @param array                 $initalParameters Array of inital parameters.
     * @return void
     */
    public function __construct(array $initalParameters = [])
    {
        $this->parameters = array_merge($this->parameters, $initalParameters);
    }

    /**
     * Returns all container parameters
     *
     * @return array
     */
    public function allParameters() : array
    {
        return $this->parameters;
    }

    /**
     * Does the container contain the parameter with the given name?
     *
     * @param string            $name The parameter name.
     * @return bool
     */
    public function hasParameter(string $name) : bool
    {
        return array_key_exists($name, $this->parameters);
    }

    /**
     * Get the parameter with the the given name, or return the default
     * value if the parameter is not set.
     *
     * @param string            $name The parameter name.
     * @param mixed             $default The returned default value if the parameter is not set.
     * @return mixed
     */
    public function getParameter(string $name, $default = null)
    {
        return $this->hasParameter($name) ? $this->parameters[$name] : $default;
    }

    /**
     * Set the given parameter with value
     *
     * @param string            $name The parameter name.
     * @param mixed             $value The parameter value.
     * @return void
     */
    public function setParameter(string $name, $value)
    {
        $this->parameters[$name] = $value;
    }

    /**
     * Get the metadata of a specific service
     *
     * @param string            $serviceName
     */
    public function getMetaData(string $serviceName, string $key) : array
    {
        return $this->metadata[$key][$serviceName] ?? [];
    }

    /**
     * Get the metadata keys of the given service
     *
     * @param string            $serviceName
     */
    public function getMetaDataKeys(string $serviceName) : array
    {
        return $this->metadataService[$serviceName];
    }

    /**
     * Make sure the metadata and key are linked both ways.
     *
     * @param string            $serviceName
     * @param string            $key
     * @return void
     */
    private function linkMetaService(string $serviceName, string $key)
    {
        // make sure the service link is there
        if (!isset($this->metadataService[$serviceName])) {
            $this->metadataService[$serviceName] = [];
        }

        if (!in_array($key, $this->metadataService[$serviceName])) {
            $this->metadataService[$serviceName][] = $key;
        }
    }

    /**
     * Set metadata for a specific service
     * This will override all meta data matching service and key.
     *
     * @param string            $serviceName
     * @param string            $key
     * @param array             $values multidimensional array
     *
     * @return void
     */
    public function setMetaData(string $serviceName, string $key, array $values)
    {
        // make sure the service exists
        if (!$this->has($serviceName)) {
            throw new UnknownServiceException('There is no service named "' . $serviceName . '" specified.');
        }

        // make sure all elements are arrays
        foreach($values as $value)
        {
            if (!is_array($value)) {
                throw new ContainerException('Every meta data value must be an array. "' . gettype($value) . '" given.');
            }
        }

        // make sure the metadata key is allocated
        if (!isset($this->metadata[$key])) {
            $this->metadata[$key] = [];
        }

        // write
        $this->metadata[$key][$serviceName] = $values;
        $this->linkMetaService($serviceName, $key);
    }

    /**
     * Same as `setMetaData` but will append the data instead of overriding.
     *
     * @param string            $serviceName
     * @param string            $key
     * @param array             $values
     *
     * @return void
     */
    public function addMetaData(string $serviceName, string $key, array $values)
    {
        // make sure the service exists
        if (!$this->has($serviceName)) {
            throw new UnknownServiceException('There is no service named "' . $serviceName . '" specified.');
        }

        // make sure the metadata key is allocated
        if (!isset($this->metadata[$key])) {
            $this->metadata[$key] = [];
        }

        // make sure the service key is allocated
        if (!isset($this->metadata[$key][$serviceName])) {
            $this->metadata[$key][$serviceName] = [];
        }

        // append
        $this->metadata[$key][$serviceName][] = $values;
        $this->linkMetaService($serviceName, $key);
    }

    /**
     * Get an array of service names that have metadata with the given key
     *
     * @param string            $key The metadata key
     * @return array
     */
    public function serviceNamesWithMetaData(string $key) : array
    {
        return $this->metadata[$key] ?? [];
    }

    /**
     * Returns an array of all available service keys.
     *
     * @return array[string]
     */
    public function available() : array
    {
        $available = array_keys($this->serviceResolverType);
        $available[] = 'container';

        return $available;
    }

    /**
     * Does the container have the given service?
     *
     * @param string            $serviceName The name / Identifier of the service to look for.
     * @return bool
     */
    public function has(string $serviceName) : bool
    {
        return $serviceName === 'container' || isset($this->serviceResolverType[$serviceName]);
    }

    /**
     * Sets a value on the container instance.
     * This will overwrite any service stored / shared under the same name.
     *
     * @param string            $serviceName The name / Identifier of the service to look for.
     * @param mixed             $serviceValue
     * @return void
     */
    public function set(string $serviceName, $serviceValue)
    {
        if ($serviceName === 'container')
        {
            throw new ContainerException('Cannot overwrite self container reference!');
        }

        $this->resolvedSharedServices[$serviceName] = $serviceValue;
        $this->setServiceResolverType($serviceName, static::RESOLVE_SETTER);
    }

    /**
     * Removes a service from the container and releases the shared instance
     * if it has been loaded.
     *
     * @param string            $serviceName The name / Identifier of the service to look for.
     *
     * @return bool Returns true if the service has been removed.
     */
    public function remove(string $serviceName) : bool
    {
        if (!$this->has($serviceName)) {
            return false; // can only remove services that exist
        }

        if ($serviceName === 'container') {
            return false; // the container itself cannot be removed
        }

        // remove the already shared instnace if set
        if (isset($this->resolvedSharedServices[$serviceName])) {
            unset($this->resolvedSharedServices[$serviceName]);
        }

        // remove the service type
        if (isset($this->serviceResolverType[$serviceName])) {
            unset($this->serviceResolverType[$serviceName]);
        }

        // remove all possible references
        if (isset($this->serviceProviders[$serviceName])) {
            unset($this->serviceProviders[$serviceName]);
        }
        if (isset($this->resolverMethods[$serviceName])) {
            unset($this->resolverMethods[$serviceName]);
        }
        if (isset($this->resolverFactories[$serviceName])) {
            unset($this->resolverFactories[$serviceName]);
        }

        return true;
    }

    /**
     * Check if the given service has already been resolved / shared / initiated.
     * A factory service will always return false.
     *
     * @param string            $serviceName The name / Identifier of the service to look for.
     * @return bool
     */
    public function isResolved(string $serviceName) : bool
    {
        if ($serviceName === 'container') {
            return true; // the container itself is always resolved
        }

        if (!isset($this->serviceResolverType[$serviceName])) {
            return false;
        }

        if ($this->serviceResolverType[$serviceName] === static::RESOLVE_FACTORY) {
            return false;
        }

        return isset($this->resolvedSharedServices[$serviceName]);
    }

    /**
     * Release a shared resolved service from the container. This
     * will force the service to reload when accessed again.
     *
     * @param string                $serviceName
     *
     * @return bool Return false on failure.
     */
    public function release(string $serviceName) : bool
    {
        if (isset($this->resolvedSharedServices[$serviceName]))
        {
            unset($this->resolvedSharedServices[$serviceName]); return true;
        }

        return false;
    }

    /**
     * Retrieve a service from the container.
     *
     * @throws UnknownServiceException When a service could not be found or is unresolvable.
     *
     * @param string            $serviceName The name / Identifier of the service to look for.
     *
     * @return mixed The requested service.
     */
    public function get(string $serviceName)
    {
        // if the service container itself is requested
        if ($serviceName === 'container') {
            return $this;
        }

        // check if the service name has a registered service type
        if (!isset($this->serviceResolverType[$serviceName]))
        {
            throw new UnknownServiceException('Could not find service named "' . $serviceName . '" registered in the container.');
        }
        $serviceResolverType = $this->serviceResolverType[$serviceName];

        // check if a service instance already exists
        if (
            isset($this->resolvedSharedServices[$serviceName]) &&
            $serviceResolverType !== static::RESOLVE_FACTORY
        ) {
            return $this->resolvedSharedServices[$serviceName];
        }

        switch ($serviceResolverType)
        {
            // Default resolver for all services that are defined
            // directly in the container. We can skip here an unnecessary method call.
            case static::RESOLVE_METHOD:
                return $this->{$this->resolverMethods[$serviceName]}();
            break;

            case static::RESOLVE_PROVIDER:
                return $this->resolveServiceProvider($serviceName);
            break;

            case static::RESOLVE_FACTORY:
                return $this->resolveServiceFactory($serviceName);
            break;

            case static::RESOLVE_SHARED:
                return $this->resolveServiceShared($serviceName);
            break;

            default:
                throw new UnknownServiceException('Could not resolve service named "' . $serviceName . '", the resolver type is unkown.');
            break;
        }
    }

    /**
     * Resolve a service instance from the given factory object.
     *
     * @param ServiceFactoryInterface|Closure           $factory The factory object.
     *
     * @return mixed The instance of created by the factory object.
     */
    private function createInstanceFromFactory($factory)
    {
        if ($factory instanceof ServiceFactoryInterface)
        {
            return $factory->create($this);
        }
        elseif ($factory instanceof Closure)
        {
            return $factory($this);
        }

        // otherwise throw an exception
        throw new InvalidServiceException('Service could not be resolved, the registered factory is invalid.');
    }

    /**
     * Resolves a service instance from a provider and stores the
     * result inside the shared services if needed.
     *
     * @param string            $name The service name.
     *
     * @return mixed The service instance created by the provider.
     */
    private function resolveServiceProvider(string $name)
    {
        list($service, $isShared) = $this->serviceProviders[$name]->resolve($name, $this);

        if ($isShared) {
            $this->resolvedSharedServices[$name] = $service;
        }

        return $service;
    }

    /**
     * Resolver that will always store the returned value inside the service container.
     *
     * @param string            $name The service name.
     *
     * @return mixed The service instance created by the factory or shared in the container.
     */
    private function resolveServiceShared(string $name)
    {
        return $this->resolvedSharedServices[$name] = $this->resolveServiceFactory($name);
    }

    /**
     * Resolver that will simply create an instance from the factory.
     *
     * @param string            $name The service name.
     *
     * @return mixed The service instance created by the factory.
     */
    private function resolveServiceFactory(string $name)
    {
        return $this->createInstanceFromFactory($this->resolverFactories[$name]);
    }

    /**
     * Register a service provider.
     * This will call the `provides` method on the given service provider instance.
     *
     * @param ServiceProviderInterface          $provider The service provider instance.
     * @return void
     */
    public function register(ServiceProviderInterface $provider)
    {
        foreach($provider->provides() as $serviceName)
        {
            $this->setServiceResolverType($serviceName, static::RESOLVE_PROVIDER);
            $this->serviceProviders[$serviceName] = $provider;
        }
    }

    /**
     * Binds a service factory to the container.
     *
     *     $container->bind('session', new SessionFactory);
     *
     *     $container->bind('config', function($c) {
     *          return new Config($c->get('config.loader'));
     *     }, false);
     *
     *     $container->bind('router', '\\Routing\\Router')
     *         ->addDependencyArgument('config');
     *
     * @param string            $name The service name.
     * @param mixed             $factory The service factory instance, the closure or the classname as string
     * @param bool              $shared Should the service be shared inside the container.
     *
     * @return Closure|ServiceFactoryInterface The given or generated service factory.
     */
    public function bind(string $name, $factory, bool $shared = true)
    {
        if (is_string($factory)) {
            return $this->bindClass($name, $factory, [], $shared);
        } elseif ($shared) {
            $this->bindFactoryShared($name, $factory);
        } else {
            $this->bindFactory($name, $factory);
        }
    }

    /**
     * Creates and binds a service factory by class name and arguments.
     *
     * @param string            $name The service name.
     * @param string            $className The service class name.
     * @param array             $arguments An array of arguments.
     * @param bool              $shared Should the service be shared inside the container.
     *
     * @return ServiceFactory The created service factory
     */
    public function bindClass(string $name, string $factory, array $arguments = [], bool $shared = true) : ServiceFactory
    {
        $factory = new ServiceFactory($factory, $arguments);

        if ($shared) {
            $this->bindFactoryShared($name, $factory);
        } else {
            $this->bindFactory($name, $factory);
        }

        return $factory;
    }

    /**
     * Binds an unshared factory instance to the service container.
     *
     * @param string                            $name The service name.
     * @param ServiceFactoryInterface|Closure   $factory The service factory instance or closure.
     * @return void
     */
    public function bindFactory(string $name, $factory)
    {
        $this->setServiceResolverType($name, static::RESOLVE_FACTORY);
        $this->resolverFactories[$name] = $factory;
    }

    /**
     * Binds a shared factory instance to the service container.
     *
     * @param string                            $name The service name.
     * @param ServiceFactoryInterface|Closure   $factory The service factory instance or closure.
     * @return void
     */
    public function bindFactoryShared(string $name, $factory)
    {
        $this->setServiceResolverType($name, static::RESOLVE_SHARED);
        $this->resolverFactories[$name] = $factory;
    }

    /**
     * Set a service resolver type.
     * The service resolver type tells the container where to look for the correct resolver.
     *
     * @param string            $serviceName The name / Identifier of the service to look for.
     * @param int               $serviceType The service type as int represented by the `RESOLVE_` prefixed constants.
     * @return void
     */
    private function setServiceResolverType(string $serviceName, int $serviceType)
    {
        $this->serviceResolverType[$serviceName] = $serviceType;
    }

    /**
     * Get the resolver type of the given service name.
     *
     * @param string                $serviceName
     * @return int
     */
    public function getServiceResolverType(string $serviceName) : int
    {
        if (!isset($this->serviceResolverType[$serviceName]))
        {
            throw new UnknownServiceException('There is no type for the service named "' . $serviceName . '" specified.');
        }

        return $this->serviceResolverType[$serviceName];
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Parser;

use ClanCats\Container\ContainerParser\{
    Nodes\BaseNode as Node,
    ContainerParser,
    Token as T,

    // contextual node
    Nodes\ValueNode,
    Nodes\ServiceMethodCallNode
};

class ServiceMethodCallParser extends ContainerParser
{
    /**
     * Parse the next token
     *
     * @return null|Node
     */
    protected function next()
    {
        if (!$this->currentToken()->isType(T::TOKEN_MINUS))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        $this->skipToken();

        if (!$this->currentToken()->isType(T::TOKEN_IDENTIFIER))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        // create the node
        $call = new ServiceMethodCallNode($this->currentToken()->getValue());
        $this->skipToken();

        // we might already be done..
        if ($this->parserIsDone() || $this->currentToken()->isType(T::TOKEN_LINE))
        {
            return $call;
        }

        // otherwise we expect an opening brace
        if (!$this->currentToken()->isType(T::TOKEN_BRACE_OPEN))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        $arguments = $this->parseChild(ArgumentArrayParser::class, $this->getTokensUntilClosingScope(), false);
        $call->setArguments($arguments);

        return $call;
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container;

class ServiceArguments
{
    /**
     * Static instance constructor from array for eye candy
     *
     *     ServiceArguments::from([
     *         '@session.storage.redis',
     *         ':session_token',
     *         600, // session lifetime
     *     ])
     *
     * @param array         $arguments The arguments as array.
     */
    public static function from(array $arguments) : ServiceArguments
    {
        return new static($arguments);
    }

    /**
     * Available service factory argument types
     * Subclasses would be cleaner, but this could have
     * a real performance impact so lets do it oldschool
     */
    const RAW = 0;
    const PARAMETER = 1;
    const DEPENDENCY = 2;

    /**
     * An array of arguments
     *
     * @var array[[string, int]]
     */
    protected $arguments = [];

    /**
     * Construct new arguments object with array
     *
     * @param array             $arguments
     * @return void
     */
    public function __construct(array $arguments = [])
    {
        $this->addArgumentsFromArray($arguments);
    }

    /**
     * Add a service argument of type
     *
     * @param mixed             $argumentValue
     * @param int               $argumentType
     * @return self
     */
    private function addArgument($argumentValue, int $argumentType) : ServiceArguments
    {
        $this->arguments[] = [$argumentValue, $argumentType]; return $this;
    }

    /**
     * Add a simply raw argument,
     *
     * @param mixed             $argumentValue
     * @return self
     */
    public function addRaw($argumentValue) : ServiceArguments
    {
        return $this->addArgument($argumentValue, static::RAW);
    }

    /**
     * Add a simply raw argument,
     *
     * @param mixed             $argumentValue
     * @return self
     */
    public function addDependency($argumentValue) : ServiceArguments
    {
        return $this->addArgument($argumentValue, static::DEPENDENCY);
    }

    /**
     * Add a simply raw argument,
     *
     * @param mixed             $argumentValue
     * @return self
     */
    public function addParameter($argumentValue) : ServiceArguments
    {
        return $this->addArgument($argumentValue, static::PARAMETER);
    }

    /**
     * Add arguments with a simple array
     *
     *  - @ prefix indicates dependency
     *  - : prefix indicates parameter
     *
     * @param array                 $argumentsArray
     * @return void
     */
    public function addArgumentsFromArray(array $argumentsArray)
    {
        foreach($argumentsArray as $argument)
        {
            if (is_string($argument) && ($argument[0] === '@' || $argument[0] === ':'))
            {
                if ($argument[0] === '@') {
                    $this->addDependency(substr($argument, 1));
                } elseif ($argument[0] === ':') {
                    $this->addParameter(substr($argument, 1));
                }
            } else  {
                $this->addRaw($argument);
            }
        }
    }

    /**
     * Resolve the current arguments from the given container instance and
     * return them as array
     *
     * @param Container             $container
     * @return array
     */
    public function resolve(Container $container) : array
    {
        $resolvedArguments = [];
        foreach($this->arguments as list($argumentValue, $argumentType))
        {
            switch ($argumentType)
            {
                case static::RAW:
                    $resolvedArguments[] = $argumentValue;
                break;
                case static::DEPENDENCY:
                    $resolvedArguments[] = $container->get($argumentValue);
                break;
                case static::PARAMETER:
                    $resolvedArguments[] = $container->getParameter($argumentValue);
                break;
            }
        }

        return $resolvedArguments;
    }

    /**
     * Return all arguments
     *
     * @return array[[string => int]]
     */
    public function getAll() : array
    {
        return $this->arguments;
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container;

class ServiceFactory extends ServiceDefinition implements ServiceFactoryInterface
{
    /**
     * Construct your object, or value based on the given container.
     *
     * @param Container             $container
     * @return mixed
     */
    public function create(Container $container)
    {
        $instance = new $this->className(...$this->constructorArguments->resolve($container));

        foreach($this->methodCallers as list($method, $arguments))
        {
            $instance->{$method}(... $arguments->resolve($container));
        }

        return $instance;
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Parser;

use ClanCats\Container\ContainerParser\{
    Nodes\BaseNode as Node,
    ContainerParser,
    Token as T,

    // contextual node
    Nodes\ValueNode,
    Nodes\ParameterDefinitionNode
};

class ParameterDefinitionParser extends ContainerParser
{
    /**
     * Parse the next token
     *
     * @return null|Node
     */
    protected function next()
    {
        $isOverride = false;
        if ($this->currentToken()->isType(T::TOKEN_OVERRIDE))
        {
            $isOverride = true; $this->skipToken();
        }

        if (!$this->currentToken()->isType(T::TOKEN_PARAMETER))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        // get the paramter name
        $parameterName = $this->currentToken()->getValue();

        // remove the ":" prefix
        $parameterName = substr($parameterName, 1);

        // now a assign ":" character must follow
        if (!$this->nextToken()->isType(T::TOKEN_ASSIGN))
        {
            throw $this->errorUnexpectedToken($this->nextToken());
        }

        // at this point we can skip the name and assign character
        $this->skipToken(2);

        // we do allow skipping linebreaks here
        $this->skipTokenOfType([T::TOKEN_LINE]);

        $parameterValue = null;

        // Parameters can contain an array so we need to check
        // for an open scope here
        if ($this->currentToken()->isType(T::TOKEN_SCOPE_OPEN))
        {
            $parameterValue = $this->parseChild(
                ArrayParser::class,
                $this->getTokensUntilClosingScope(
                    false,
                    T::TOKEN_SCOPE_OPEN,
                    T::TOKEN_SCOPE_CLOSE
                ),
                false
            );
        }
        // otherwise we expect a scalar value
        elseif ($this->currentToken()->isValue())
        {
            $parameterValue = ValueNode::fromToken($this->currentToken());
            $this->skipToken();
        }
        else
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        // create the definition node
        $definition = new ParameterDefinitionNode($parameterName, $parameterValue);
        $definition->setIsOverride($isOverride);

        // return the paramter definition
        return $definition;
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Parser;

use ClanCats\Container\ContainerParser\{
    Nodes\BaseNode as Node,
    ContainerParser,
    Token as T,

    // contextual node
    Nodes\ValueNode,
    Nodes\MetaDataAssignmentNode
};

class ServiceMetaDataParser extends ContainerParser
{
    /**
     * Parse the next token
     *
     * @return null|Node
     */
    protected function next()
    {
        if (!$this->currentToken()->isType(T::TOKEN_EQUAL))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        $this->skipToken();

        if (!$this->currentToken()->isType(T::TOKEN_IDENTIFIER))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        // create the node
        $meta = new MetaDataAssignmentNode($this->currentToken()->getValue());
        $this->skipToken();

        // we might already be done..
        if ($this->parserIsDone() || $this->currentToken()->isType(T::TOKEN_LINE))
        {
            return $meta;
        }

        // otherwise we expect an double point `:`
        if (!$this->currentToken()->isType(T::TOKEN_ASSIGN))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }
        $this->skipToken();

        // parse the data
        $data = $this->parseChild(
            ArrayParser::class,
            $this->getTokensUntil(T::TOKEN_LINE, true),
            false
        );
        $meta->setData($data);

        return $meta;
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser;

use ClanCats\Container\{
    Exceptions\ContainerParserException,

    // Alias the token as T
    ContainerParser\Token as T,

    ContainerParser\Nodes\BaseNode as Node
};

class ContainerParser
{
    /**
     * The tokens in this code segment
     *
     * @var array[Token]
     */
    protected $tokens = array();

    /**
     * The current index while parsing trough the tokens
     *
     * @var int
     */
    protected $index = 0;

    /**
     * The number of tokens to parse
     *
     * @var int
     */
    protected $tokenCount = 0;

    /**
     * The constructor
     * You have to initialize the Parser with an array of lexed tokens.
     *
     * @var array[T]             $tokens
     * @return void
     */
    public function __construct(array $tokens)
    {
        foreach ($tokens as $key => $token)
        {
            // remove all comments and whitespaces
            if ($token->getType() === T::TOKEN_COMMENT || $token->getType() === T::TOKEN_SPACE)
            {
                unset($tokens[$key]);
            }
        }

        // prepare the parser
        $this->prepare();

        // set the initial tokens
        $this->setTokens($tokens);
    }

    /**
     * Set the tokens of the current parser
     *
     * @param array[T]             $tokens
     * @return void
     */
    public function setTokens(array $tokens)
    {
        // reset the keys
        $this->tokens = array_values($tokens);

        // count the real number of tokens
        $this->tokenCount = count($this->tokens);

        // reset the index
        $this->index = 0;
    }

    /**
     * Returns all curent tokens
     *
     * @return array[T]
     */
    public function getTokens() : array
    {
        return $this->tokens;
    }

    /**
     * Get the number of tokens
     *
     * @return int
     */
    public function getTokenCount() : int
    {
        return $this->tokenCount;
    }

    /**
     * Returns the current index
     *
     * @return int
     */
    public function getIndex() : int
    {
        return $this->index;
    }

    /**
     * Should be in the most cases identical with the current index
     * But may variant in a parser
     *
     * @return int
     */
    public function getParsedTokensCount() : int
    {
        return $this->getIndex();
    }

    /**
     * Retrives the current token based on the index
     *
     * @return T
     */
    protected function currentToken() : T
    {
        if (!isset($this->tokens[$this->index]))
        {
            throw new ContainerParserException("The current token is of the available tokens range.");
        }

        return $this->tokens[$this->index];
    }

    /**
     * Get the next token based on the current index
     * If the token does not exist because its off index "false" is returend.
     *
     * @param int             $i
     * @return T|false
     */
    protected function nextToken(int $i = 1)
    {
        if (!isset($this->tokens[$this->index + $i])) {
            return false;
        }

        return $this->tokens[$this->index + $i];
    }

    /**
     * Skip the next parser token by updating the index.
     *
     * @param int            $times
     * @return void
     */
    protected function skipToken(int $times = 1)
    {
        $this->index += $times;
    }

    /**
     * Skip all upcoming tokens of the given type
     *
     * @param array[int]         $types
     * @return void
     */
    protected function skipTokenOfType(array $types = [])
    {
        while ((!$this->parserIsDone()) && in_array($this->currentToken()->getType(), $types))
        {
            $this->skipToken();
        }
    }

    /**
     * Check if all tokens have been parsed trough
     *
     * @return bool
     */
    protected function parserIsDone() : bool
    {
        return $this->index >= $this->tokenCount;
    }

    /**
     * Return all remaining tokens
     *
     * @param string                    $skip
     * @return array[T]
     */
    protected function getRemainingTokens(bool $skip = false) : array
    {
        $tokens = array();

        while (!$this->parserIsDone())
        {
            $tokens[] = $this->currentToken(); $this->skipToken();
        }

        if (!$skip)
        {
            $this->index -= count($tokens);
        }

        return $tokens;
    }

    /**
     * Get all tokens until the next token with given type
     *
     * @param int                $type
     * @param bool                  $ignoreScopes if enabled, if the token is inside a new scope its ignored.
     * @return array[T]
     */
    protected function getTokensUntil(int $type, bool $ignoreScopes = false) : array
    {
        $tokens = array();

        if (!is_array($type)) {
            $type = array($type);
        }

        while (!$this->parserIsDone() && !in_array($this->currentToken()->getType(), $type))
        {
            // if scopes should be ignored, we need to check if the current
            // token opens one an then get all tokens inside the scope
            if ($ignoreScopes && $this->currentToken()->isType(T::TOKEN_SCOPE_OPEN))
            {
                foreach($this->getTokensUntilClosingScope(true, T::TOKEN_SCOPE_OPEN, T::TOKEN_SCOPE_CLOSE) as $token) {
                    $tokens[] = $token;
                }
            } else {
                $tokens[] = $this->currentToken();
                $this->skipToken();
            }
        }

        return $tokens;
    }

    /**
     * Retruns all tokens until the opened scope is closed again
     *
     * @return array[T]
     */
    protected function getTokensUntilClosingScope(bool $includeScope = false, int $openToken = T::TOKEN_BRACE_OPEN, int $closeToken = T::TOKEN_BRACE_CLOSE) : array
    {
        if ($this->currentToken()->getType() !== $openToken)
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        $tokens = array();

        // include the opening scope
        if ($includeScope) {
            $tokens[] = $this->currentToken();
        }

        $this->skipToken();

        $currentLevel = 0;

        while($this->currentToken() && !($this->currentToken()->getType() === $closeToken && $currentLevel === 0))
        {
            if ($this->currentToken()->getType() === $openToken)
            {
                $currentLevel++;
            }

            if ($this->currentToken()->getType() === $closeToken)
            {
                $currentLevel--;
            }

            $tokens[] = $this->currentToken();
            $this->skipToken();
        }

        // include the closing scope
        if ($includeScope) {
            $tokens[] = $this->currentToken();
        }

        // skip the closing scope
        $this->skipToken();

        return $tokens;
    }

    /**
     * Create new unexpected token exception
     *
     * @param T                 $token
     * @return ContainerParserException
     */
    protected function errorUnexpectedToken($token)
    {
        $class = new \ReflectionClass($token);
        $constants = array_flip($class->getConstants());

        return new ContainerParserException('unexpected "' . $constants[$token->getType()] . '" given at line ' . $token->getLine());
    }

    /**
     * Starts a new parser with the remaining or given tokens
     *
     * @param string                $parserClassName
     * @param array                 $tokens
     * @param bool                  $skip
     * @return Node
     */
    protected function parseChild(string $parserClassName, $tokens = null, $skip = true)
    {
        if (is_null($tokens))
        {
            $tokens = $this->getRemainingTokens();
        }

        $parser = new $parserClassName($tokens);
        $node = $parser->parse();

        // Update the current index based on the work of the child parser.
        if ($skip)
        {
            $this->skipToken($parser->getParsedTokensCount());
        }

        // finally return the parsed node
        return $node;
    }

    /**
     * Start the code parser and return the result
     *
     * @return array
     */
    public function parse()
    {
        // reset the result
        $this->result = array();

        // start parsing trought the tokens
        while (!$this->parserIsDone())
        {
            $specialNode = $this->next();

            if ($specialNode instanceof Node)
            {
                return $specialNode;
            }
        }

        // return the result after the loop is done
        return $this->node();
    }

    /**
     * Prepare the current parser
     *
     * @return void
     */
    protected function prepare() {}

    /**
     * Return the current result
     *
     * @return null|Node
     */
    protected function node() : Node
    {
        throw new ContainerParserException("The container parser acts as base and should not be used on its own.");
    }

    /**
     * Parse the next token
     *
     * @return null|Node
     */
    protected function next()
    {
        throw new ContainerParserException("The container parser acts as base and should not be used on its own.");
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Parser;

use ClanCats\Container\ContainerParser\{
    Nodes\BaseNode as Node,
    ContainerParser,
    Token as T,

    // contextual node
    Nodes\ArgumentArrayNode,
    Nodes\ParameterReferenceNode,
    Nodes\ServiceReferenceNode,
    Nodes\ValueNode
};

class ArgumentArrayParser extends ContainerParser
{
    /**
     * The current arguments node
     *
     * @param ArgumentArrayNode
     */
    protected $arguments;

    /**
     * Prepare the current parser
     *
     * @return void
     */
    protected function prepare()
    {
        $this->arguments = new ArgumentArrayNode;
    }

    /**
     * Return the current result
     *
     * @return null|Node
     */
    protected function node() : Node
    {
        return $this->arguments;
    }

    /**
     * Parse the next token
     *
     * @return null|Node
     */
    protected function next()
    {
        $token = $this->currentToken();

        // the argument might be an array
        if ($this->currentToken()->isType(T::TOKEN_SCOPE_OPEN))
        {
            $this->arguments->addArgument($this->parseChild(
                ArrayParser::class,
                $this->getTokensUntilClosingScope(
                    false,
                    T::TOKEN_SCOPE_OPEN,
                    T::TOKEN_SCOPE_CLOSE
                ),
                false
            ));
        }
        // or a simple scalar value
        elseif ($token->isValue())
        {
            $this->arguments->addArgument(ValueNode::fromToken($token));
        }
        // is it a parameter?
        elseif ($token->isType(T::TOKEN_PARAMETER))
        {
            $this->arguments->addArgument($this->parseChild(ReferenceParser::class));
        }
        // is a service reference
        elseif ($token->isType(T::TOKEN_DEPENDENCY))
        {
            $this->arguments->addArgument($this->parseChild(ReferenceParser::class));
        }
        // just a linebreak
        elseif ($token->isType(T::TOKEN_LINE))
        {
            $this->skipToken(); return;
        }

        // anything else?
        else
        {
            throw $this->errorUnexpectedToken($token);
        }

        $this->skipToken();

        // now ther might follow a seperator indicating another argument
        if (!$this->parserIsDone() && $this->currentToken()->isType(T::TOKEN_SEPERATOR))
        {
            $this->skipToken();
        }
    }
}


<?php
/**
 * ClanCats Container
 *
 * @link      https://github.com/ClanCats/Container/
 * @copyright Copyright (c) 2016-2019 Mario Döring
 * @license   https://github.com/ClanCats/Container/blob/master/LICENSE (MIT License)
 */
namespace ClanCats\Container\ContainerParser\Parser;

use ClanCats\Container\ContainerParser\{
    Nodes\BaseNode as Node,
    ContainerParser,
    Token as T,

    // contextual node
    Nodes\ValueNode,
    Nodes\ServiceDefinitionNode
};

class ServiceDefinitionParser extends ContainerParser
{
    /**
     * Parse the next token
     *
     * @return null|Node
     */
    protected function next()
    {
        $definition = new ServiceDefinitionNode();

        if ($this->currentToken()->isType(T::TOKEN_OVERRIDE))
        {
            $definition->setIsOverride(true); $this->skipToken();
        }

        if (!$this->currentToken()->isType(T::TOKEN_DEPENDENCY))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        // get the paramter name
        $serviceName = $this->currentToken()->getValue();

        // remove the "@" prefix
        $serviceName = substr($serviceName, 1);

        // set the name
        $definition->setName($serviceName);

        // now a assign ":" character must follow
        if (!$this->nextToken()->isType(T::TOKEN_ASSIGN))
        {
            throw $this->errorUnexpectedToken($this->nextToken());
        }

        // at this point we can skip the name and assign character
        $this->skipToken(2);

        // the next token must therefor be a identifier
        if (!$this->currentToken()->isType(T::TOKEN_IDENTIFIER))
        {
            throw $this->errorUnexpectedToken($this->currentToken());
        }

        // assign the class name from the identifiers value
        $definition->setClassName($this->currentToken()->getValue());
        $this->skipToken();

        // try to parse service constructor arguments
        if (!$this->parserIsDone() && $this->currentToken()->isType(T::TOKEN_BRACE_OPEN))
        {
            $arguments = $this->parseChild(ArgumentArrayParser::class, $this->getTokensUntilClosingScope(), false);
            $definition->setArguments($arguments);
        }

        // we need at least one linebreak to continue
        while(!$this->parserIsDone() && $this->currentToken()->isType(T::TOKEN_LINE))
        {
            // skip all other linebreak
            $this->skipTokenOfType([T::TOKEN_LINE]);

            // parse servide definiton caller
            if (!$this->parserIsDone() && $this->currentToken()->isType(T::TOKEN_MINUS))
            {
                $definition->addConstructionAction($this->parseChild(ServiceMethodCallParser::class));
            }
            elseif (!$this->parserIsDone() && $this->currentToken()->isType(T::TOKEN_EQUAL))
            {
                $definition->addMetaDataAssignemnt($this->parseChild(ServiceMetaDataParser::class));
            }
        }

        return $definition;
    }
}