<?php

namespace com\realexpayments\hpp\sdk\domain;

use com\realexpayments\hpp\sdk\utils\GenerationUtils;
use Exception;

/**
 * Class representing the HPP response.
 * @package com\realexpayments\hpp\sdk\domain
 * @author vicpada
 */
class HppResponse {

	/**
	 * @var string This is the merchant id that Realex Payments assign to you.
	 */
	private $merchantId;

	/**
	 * @var string The sub-account used in the transaction.
	 */
	private $account;

	/**
	 * @var string The unique order id that you sent to us.
	 */
	private $orderId;

	/**
	 * @var string The amount that was authorised. Returned in the lowest unit of the currency.
	 */
	private $amount;

	/**
	 * @var string Will contain a valid authcode if the transaction was successful. Will be empty otherwise.
	 */
	private $authCode;

	/**
	 * @var string The date and time of the transaction.
	 */
	private $timeStamp;

	/**
	 * @var string A SHA-1 digital signature created using the HPP response fields and your shared secret.
	 */
	private $hash;

	/**
	 * @var string The outcome of the transaction. Will contain "00" if the transaction was a success
	 * or another value (depending on the error) if not.
	 */
	private $result;

	/**
	 * @var string Will contain a text message that describes the result code.
	 */
	private $message;

	/**
	 *

	 * The result of the Card Verification check (if enabled):
	 *

	 *
M: CVV Matched.

	 *
N: CVV Not Matched.

	 *
I: CVV Not checked due to circumstances.

	 *
U: CVV Not checked - issuer not certified.

	 *
P: CVV Not Processed.

	 *

	 *

	 *
	 * @var string cvn result
	 */
	private $cvnResult;

	/**
	 * @var string A unique reference that Realex Payments assign to your transaction.
	 */
	private $pasRef;

	/**
	 * @var string This is the Realex Payments batch that this transaction will be in.
	 * (This is equal to "-1" if the transaction was sent in with the autosettle flag off.
	 * After you settle it (either manually or programmatically) the response to that transaction
	 * will contain the batch id.)
	 */
	private $batchId;

	/**
	 * @var string This is the ecommerce indicator (this will only be returned for 3DSecure transactions).
	 */
	private $eci;

	/**
	 * @var string Cardholder Authentication Verification Value (this will only be returned for 3DSecure transactions).
	 */
	private $cavv;

	/**
	 * @var string Exchange Identifier (this will only be returned for 3DSecure transactions).
	 */
	private $xid;

	/**
	 * @var string Whatever data you have sent in the request will be returned to you.
	 */
	private $commentOne;

	/**
	 * @var string Whatever data you have sent in the request will be returned to you.
	 */
	private $commentTwo;

	/**
	 * The Transaction Suitability Score for the transaction. The RealScore is comprised of various distinct tests.
	 * Using the RealControl application you can request that Realex Payments return certain individual scores to you.
	 * These are identified by numbers - thus TSS_1032 would be the result of the check with id 1032.
	 * You can then use these specific checks in conjunction with RealScore score to ascertain whether
	 * or not you wish to continue with the settlement.
	 *
	 * @var array TSS
	 */
	private $tss = array();

	/**
	 *
	 * Anything else you sent to us in the request will be returned to you in supplementary data.
	 *
	 * @var array Supplementary data
	 */
	private $supplementaryData = array();

	/**
	 * @var string Address Verification Service result for cardholder address
	 */
	private $AVSAddressResult;

	/**
	 * @var string Address Verification Service result for cardholder billing code
	 */
	private $AVSPostCodeResult;

	/**
	 * HppResponse constructor.
	 */
	public function __construct() {
	}

	/**
	 * Getter for merchantId
	 *
	 * @return string
	 */
	public function getMerchantId() {
		return $this->merchantId;
	}

	/**
	 * Setter for merchantId
	 *
	 * @param string $merchantId
	 */
	public function setMerchantId( $merchantId ) {
		$this->merchantId = $merchantId;
	}

	/**
	 * Getter for account
	 *
	 * @return string
	 */
	public function getAccount() {
		return $this->account;
	}

	/**
	 * Setter for account
	 *
	 * @param string $account
	 */
	public function setAccount( $account ) {
		$this->account = $account;
	}

	/**
	 * Getter for orderId
	 *
	 * @return string
	 */
	public function getOrderId() {
		return $this->orderId;
	}

	/**
	 * Setter for orderId
	 *
	 * @param string $orderId
	 */
	public function setOrderId( $orderId ) {
		$this->orderId = $orderId;
	}

	/**
	 * Getter for amount
	 *
	 * @return string
	 */
	public function getAmount() {
		return $this->amount;
	}

	/**
	 * Setter for amount
	 *
	 * @param string $amount
	 */
	public function setAmount( $amount ) {
		$this->amount = $amount;
	}

	/**
	 * Getter for authCode
	 *
	 * @return string
	 */
	public function getAuthCode() {
		return $this->authCode;
	}

	/**
	 * Setter for authCode
	 *
	 * @param string $authCode
	 */
	public function setAuthCode( $authCode ) {
		$this->authCode = $authCode;
	}

	/**
	 * Getter for timeStamp
	 *
	 * @return string
	 */
	public function getTimeStamp() {
		return $this->timeStamp;
	}

	/**
	 * Setter for timeStamp
	 *
	 * @param string $timeStamp
	 */
	public function setTimeStamp( $timeStamp ) {
		$this->timeStamp = $timeStamp;
	}

	/**
	 * Getter for hash
	 *
	 * @return string
	 */
	public function getHash() {
		return $this->hash;
	}

	/**
	 * Setter for hash
	 *
	 * @param string $hash
	 */
	public function setHash( $hash ) {
		$this->hash = $hash;
	}

	/**
	 * Getter for result
	 *
	 * @return string
	 */
	public function getResult() {
		return $this->result;
	}

	/**
	 * Setter for result
	 *
	 * @param string $result
	 */
	public function setResult( $result ) {
		$this->result = $result;
	}

	/**
	 * Getter for message
	 *
	 * @return string
	 */
	public function getMessage() {
		return $this->message;
	}

	/**
	 * Setter for message
	 *
	 * @param string $message
	 */
	public function setMessage( $message ) {
		$this->message = $message;
	}

	/**
	 * Getter for cvnResult
	 *
	 * @return string
	 */
	public function getCvnResult() {
		return $this->cvnResult;
	}

	/**
	 * Setter for cvnResult
	 *
	 * @param string $cvnResult
	 */
	public function setCvnResult( $cvnResult ) {
		$this->cvnResult = $cvnResult;
	}

	/**
	 * Getter for pasRef
	 *
	 * @return string
	 */
	public function getPasRef() {
		return $this->pasRef;
	}

	/**
	 * Setter for pasRef
	 *
	 * @param string $pasRef
	 */
	public function setPasRef( $pasRef ) {
		$this->pasRef = $pasRef;
	}

	/**
	 * Getter for batchId
	 *
	 * @return string
	 */
	public function getBatchId() {
		return $this->batchId;
	}

	/**
	 * Setter for batchId
	 *
	 * @param string $batchId
	 */
	public function setBatchId( $batchId ) {
		$this->batchId = $batchId;
	}

	/**
	 * Getter for eci
	 *
	 * @return string
	 */
	public function getEci() {
		return $this->eci;
	}

	/**
	 * Setter for eci
	 *
	 * @param string $eci
	 */
	public function setEci( $eci ) {
		$this->eci = $eci;
	}

	/**
	 * Getter for cavv
	 *
	 * @return string
	 */
	public function getCavv() {
		return $this->cavv;
	}

	/**
	 * Setter for cavv
	 *
	 * @param string $cavv
	 */
	public function setCavv( $cavv ) {
		$this->cavv = $cavv;
	}

	/**
	 * Getter for xid
	 *
	 * @return string
	 */
	public function getXid() {
		return $this->xid;
	}

	/**
	 * Setter for xid
	 *
	 * @param string $xid
	 */
	public function setXid( $xid ) {
		$this->xid = $xid;
	}

	/**
	 * Getter for commentOne
	 *
	 * @return string
	 */
	public function getCommentOne() {
		return $this->commentOne;
	}

	/**
	 * Setter for commentOne
	 *
	 * @param string $commentOne
	 */
	public function setCommentOne( $commentOne ) {
		$this->commentOne = $commentOne;
	}

	/**
	 * Getter for commentTwo
	 *
	 * @return string
	 */
	public function getCommentTwo() {
		return $this->commentTwo;
	}

	/**
	 * Setter for commentTwo
	 *
	 * @param string $commentTwo
	 */
	public function setCommentTwo( $commentTwo ) {
		$this->commentTwo = $commentTwo;
	}

	/**
	 * Getter for tss
	 *
	 * @return array
	 */
	public function getTss() {
		return $this->tss;
	}

	/**
	 * Setter for tss
	 *
	 * @param array $tss
	 */
	public function setTss( $tss ) {
		$this->tss = $tss;
	}

	/**
	 * Getter for supplementaryData
	 *
	 * @return array
	 */
	public function getSupplementaryData() {
		return $this->supplementaryData;
	}

	/**
	 * Setter for supplementaryData
	 *
	 * @param string $name
	 * @param string $value
	 */
	public function setSupplementaryDataValue( $name, $value ) {
		$this->supplementaryData[ $name ] = $value;
	}

	/**
	 * Getter for AVSAddressResult
	 *
	 * @return string
	 */
	public function getAVSAddressResult() {
		return $this->AVSAddressResult;
	}

	/**
	 * Setter for AVSAddressResult
	 *
	 * @param string $AVSAddressResult
	 */
	public function setAVSAddressResult( $AVSAddressResult ) {
		$this->AVSAddressResult = $AVSAddressResult;
	}

	/**
	 * Getter for AVSPostCodeResult
	 *
	 * @return string
	 */
	public function getAVSPostCodeResult() {
		return $this->AVSPostCodeResult;
	}

	/**
	 * Setter for AVSPostCodeResult
	 *
	 * @param string $AVSPostCodeResult
	 */
	public function setAVSPostCodeResult( $AVSPostCodeResult ) {
		$this->AVSPostCodeResult = $AVSPostCodeResult;
	}

	/**
	 * Helper method for adding a merchantId
	 *
	 * @param string $merchantId
	 *
	 * @return HppResponse
	 */
	public function addMerchantId( $merchantId ) {
		$this->merchantId = $merchantId;

		return $this;
	}

	/**
	 * Helper method for adding a account
	 *
	 * @param string $account
	 *
	 * @return HppResponse
	 */
	public function addAccount( $account ) {
		$this->account = $account;

		return $this;
	}

	/**
	 * Helper method for adding a orderId
	 *
	 * @param string $orderId
	 *
	 * @return HppResponse
	 */
	public function addOrderId( $orderId ) {
		$this->orderId = $orderId;

		return $this;
	}

	/**
	 * Helper method for adding a amount
	 *
	 * @param string $amount
	 *
	 * @return HppResponse
	 */
	public function addAmount( $amount ) {
		$this->amount = $amount;

		return $this;
	}

	/**
	 * Helper method for adding a authCode
	 *
	 * @param string $authCode
	 *
	 * @return HppResponse
	 */
	public function addAuthCode( $authCode ) {
		$this->authCode = $authCode;

		return $this;
	}

	/**
	 * Helper method for adding a timeStamp
	 *
	 * @param string $timeStamp
	 *
	 * @return HppResponse
	 */
	public function addTimeStamp( $timeStamp ) {
		$this->timeStamp = $timeStamp;

		return $this;
	}

	/**
	 * Helper method for adding a hash
	 *
	 * @param string $hash
	 *
	 * @return HppResponse
	 */
	public function addHash( $hash ) {
		$this->hash = $hash;

		return $this;
	}

	/**
	 * Helper method for adding a result
	 *
	 * @param string $result
	 *
	 * @return HppResponse
	 */
	public function addResult( $result ) {
		$this->result = $result;

		return $this;
	}

	/**
	 * Helper method for adding a message
	 *
	 * @param string $message
	 *
	 * @return HppResponse
	 */
	public function addMessage( $message ) {
		$this->message = $message;

		return $this;
	}

	/**
	 * Helper method for adding a cvnResult
	 *
	 * @param string $cvnResult
	 *
	 * @return HppResponse
	 */
	public function addCvnResult( $cvnResult ) {
		$this->cvnResult = $cvnResult;

		return $this;
	}

	/**
	 * Helper method for adding a pasRef
	 *
	 * @param string $pasRef
	 *
	 * @return HppResponse
	 */
	public function addPasRef( $pasRef ) {
		$this->pasRef = $pasRef;

		return $this;
	}

	/**
	 * Helper method for adding a batchId
	 *
	 * @param string $batchId
	 *
	 * @return HppResponse
	 */
	public function addBatchId( $batchId ) {
		$this->batchId = $batchId;

		return $this;
	}

	/**
	 * Helper method for adding a eci
	 *
	 * @param string $eci
	 *
	 * @return HppResponse
	 */
	public function addEci( $eci ) {
		$this->eci = $eci;

		return $this;
	}

	/**
	 * Helper method for adding a cavv
	 *
	 * @param string $cavv
	 *
	 * @return HppResponse
	 */
	public function addCavv( $cavv ) {
		$this->cavv = $cavv;

		return $this;
	}

	/**
	 * Helper method for adding a xid
	 *
	 * @param string $xid
	 *
	 * @return HppResponse
	 */
	public function addXid( $xid ) {
		$this->xid = $xid;

		return $this;
	}

	/**
	 * Helper method for adding a commentOne
	 *
	 * @param string $commentOne
	 *
	 * @return HppResponse
	 */
	public function addCommentOne( $commentOne ) {
		$this->commentOne = $commentOne;

		return $this;
	}

	/**
	 * Helper method for adding a commentTwo
	 *
	 * @param string $commentTwo
	 *
	 * @return HppResponse
	 */
	public function addCommentTwo( $commentTwo ) {
		$this->commentTwo = $commentTwo;

		return $this;
	}

	/**
	 * Helper method for adding a AVSAddressResult
	 *
	 * @param string $AVSAddressResult
	 *
	 * @return HppResponse
	 */
	public function addAVSAddressResult( $AVSAddressResult ) {
		$this->AVSAddressResult = $AVSAddressResult;

		return $this;
	}

	/**
	 * Helper method for adding a AVSPostCodeResult
	 *
	 * @param string $AVSPostCodeResult
	 *
	 * @return HppResponse
	 */
	public function addAVSPostCodeResult( $AVSPostCodeResult ) {
		$this->AVSPostCodeResult = $AVSPostCodeResult;

		return $this;
	}

	/**
	 * Helper method for adding a tss
	 *
	 * @param array $tss
	 *
	 * @return HppResponse
	 */
	public function addTss( $tss ) {
		$this->tss = $tss;

		return $this;
	}

	/**
	 * @return string The class name
	 */
	public static function GetClassName() {
		return __CLASS__;
	}

	public function hash( $secret ) {
		$this->hash = $this->generateHash( $secret );

		return $this;
	}

	/**
	 * Creates the security hash from a number of fields and the shared secret.
	 *
	 * @param string $secret
	 *
	 * @return String
	 */
	public function generateHash( $secret ) {
		//check for any null values and set them to empty string for hashing
		$timeStamp  = null == $this->timeStamp ? "" : $this->timeStamp;
		$merchantId = null == $this->merchantId ? "" : $this->merchantId;
		$orderId    = null == $this->orderId ? "" : $this->orderId;
		$result     = null == $this->result ? "" : $this->result;
		$message    = null == $this->message ? "" : $this->message;
		$pasRef     = null == $this->pasRef ? "" : $this->pasRef;
		$authCode   = null == $this->authCode ? "" : $this->authCode;

		//create $to hash
		$toHash = $timeStamp
		          . "."
		          . $merchantId
		          . "."
		          . $orderId
		          . "."
		          . $result
		          . "."
		          . $message
		          . "."
		          . $pasRef
		          . "."
		          . $authCode;

		return GenerationUtils::generateHash( $toHash, $secret );
	}

	/**
	 * Helper method to determine if the HPP response security hash is valid.
	 *
	 * @param string $secret
	 *
	 * @return bool
	 */
	public function isHashValid( $secret ) {
		$generatedHash = $this->generateHash( $secret );

		return $generatedHash == $this->hash;
	}

	/**
	 * Base64 encodes the HPP response values.
	 *
	 * @param string $charset
	 *
	 * @return HppResponse
	 * @throws Exception
	 */
	public function encode( $charset ) {
		$this->merchantId         = base64_encode( $this->merchantId );
		$this->account            = base64_encode( $this->account );
		$this->amount             = base64_encode( $this->amount );
		$this->authCode           = base64_encode( $this->authCode );
		$this->batchId            = base64_encode( $this->batchId );
		$this->cavv               = base64_encode( $this->cavv );
		$this->cvnResult          = base64_encode( $this->cvnResult );
		$this->eci                = base64_encode( $this->eci );
		$this->commentOne         = base64_encode( $this->commentOne );
		$this->commentTwo         = base64_encode( $this->commentTwo );
		$this->message            = base64_encode( $this->message );
		$this->pasRef             = base64_encode( $this->pasRef );
		$this->hash               = base64_encode( $this->hash );
		$this->result             = base64_encode( $this->result );
		$this->xid                = base64_encode( $this->xid );
		$this->orderId            = base64_encode( $this->orderId );
		$this->timeStamp          = base64_encode( $this->timeStamp );
		$this->AVSAddressResult   = base64_encode( $this->AVSAddressResult );
		$this->AVSPostCodeResult = base64_encode( $this->AVSPostCodeResult );

		if (is_array($this->tss)) {
			foreach ( $this->tss as $key => $value ) {
				$this->tss[ $key ] = base64_encode( $value );
			}
		}

		if (is_array($this->supplementaryData)) {
			foreach ( $this->supplementaryData as $key => $value ) {
				$this->supplementaryData[ $key ] = base64_encode( $value );
			}
		}

		return $this;
	}

	/**
	 * Base64 decodes the HPP response values.
	 *
	 * @param string $charset
	 *
	 * @return HppResponse
	 * @throws Exception
	 */
	public function decode( $charset ) {
		$this->merchantId         = base64_decode( $this->merchantId );
		$this->account            = base64_decode( $this->account );
		$this->amount             = base64_decode( $this->amount );
		$this->authCode           = base64_decode( $this->authCode );
		$this->batchId            = base64_decode( $this->batchId );
		$this->cavv               = base64_decode( $this->cavv );
		$this->cvnResult          = base64_decode( $this->cvnResult );
		$this->eci                = base64_decode( $this->eci );
		$this->commentOne         = base64_decode( $this->commentOne );
		$this->commentTwo         = base64_decode( $this->commentTwo );
		$this->message            = base64_decode( $this->message );
		$this->pasRef             = base64_decode( $this->pasRef );
		$this->hash               = base64_decode( $this->hash );
		$this->result             = base64_decode( $this->result );
		$this->xid                = base64_decode( $this->xid );
		$this->orderId            = base64_decode( $this->orderId );
		$this->timeStamp          = base64_decode( $this->timeStamp );
		$this->AVSAddressResult   = base64_decode( $this->AVSAddressResult );
		$this->AVSPostCodeResult = base64_decode( $this->AVSPostCodeResult );

		if (is_array($this->tss)) {
			foreach ( $this->tss as $key => $value ) {
				$this->tss[ $key ] = base64_decode( $value );
			}
		}

		if (is_array($this->supplementaryData)) {
			foreach ( $this->supplementaryData as $key => $value ) {
				$this->supplementaryData[ $key ] = base64_decode( $value );
			}
		}

		return $this;
	}

}


<?php
/**
 * Date: 07.12.15
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Type\Enum;

use Youshido\GraphQL\Config\Object\EnumTypeConfig;
use Youshido\GraphQL\Config\Traits\ConfigAwareTrait;
use Youshido\GraphQL\Type\AbstractType;
use Youshido\GraphQL\Type\Traits\AutoNameTrait;
use Youshido\GraphQL\Type\TypeMap;

abstract class AbstractEnumType extends AbstractType
{

    use AutoNameTrait, ConfigAwareTrait;

    /**
     * ObjectType constructor.
     * @param $config
     */
    public function __construct($config = [])
    {
        if (empty($config)) {
            $config['name']   = $this->getName();
            $config['values'] = $this->getValues();
        }

        $this->config = new EnumTypeConfig($config, $this);
    }

    /**
     * @return String predefined type kind
     */
    public function getKind()
    {
        return TypeMap::KIND_ENUM;
    }

    /**
     * @param $value mixed
     *
     * @return bool
     */
    public function isValidValue($value)
    {
        if (is_null($value)) return true;
        foreach ($this->getConfig()->get('values') as $item) {
            if ($value === $item['name'] || $value === $item['value']) {
                return true;
            }
        }

        return false;
    }

    public function getValidationError($value = null)
    {
        $allowedValues             = array_map(function (array $value) {
            return sprintf('%s (%s)', $value['name'], $value['value']);
        }, $this->getConfig()->get('values'));
        return sprintf('Value must be one of the allowed ones: %s', implode(', ', $allowedValues));
    }

    /**
     * @return array
     */
    abstract public function getValues();

    public function serialize($value)
    {
        foreach ($this->getConfig()->get('values') as $valueItem) {
            if ($value === $valueItem['value']) {
                return $valueItem['name'];
            }
        }

        return null;
    }

    public function parseValue($value)
    {
        foreach ($this->getConfig()->get('values') as $valueItem) {
            if ($value === $valueItem['name']) {
                return $valueItem['value'];
            }
        }

        return null;
    }

    public function parseInputValue($value)
    {
        foreach ($this->getConfig()->get('values') as $valueItem) {
            if ($value === $valueItem['value']) {
                return $valueItem['name'];
            }
        }

        return null;
    }

}


<?php
/**
 * Date: 3/24/17
 *
 * @author Volodymyr Rashchepkin <rashepkin@gmail.com>
 */

namespace Youshido\GraphQL\Type;

use Youshido\GraphQL\Directive\DirectiveInterface;

class SchemaDirectivesList
{

    private $directivesList = [];

    /**
     * @param array $directives
     *
     * @throws
     * @return $this
     */
    public function addDirectives($directives)
    {
        if (!is_array($directives)) {
            throw new \Exception('addDirectives accept only array of directives');
        }
        foreach ($directives as $directive) {
            $this->addDirective($directive);
        }

        return $this;
    }

    /**
     * @param DirectiveInterface $directive
     *
     * @return $this
     */
    public function addDirective(DirectiveInterface $directive)
    {
        $directiveName = $this->getDirectiveName($directive);
        if ($this->isDirectiveNameRegistered($directiveName)) return $this;

        $this->directivesList[$directiveName] = $directive;

        return $this;
    }

    private function getDirectiveName($directive)
    {
        if (is_string($directive)) return $directive;
        if (is_object($directive) && $directive instanceof DirectiveInterface) {
            return $directive->getName();
        }
        throw new \Exception('Invalid directive passed to Schema');
    }

    public function isDirectiveNameRegistered($directiveName)
    {
        return (isset($this->directivesList[$directiveName]));
    }

    public function getDirectives()
    {
        return $this->directivesList;
    }

}


<?php
/*
* This file is a part of GraphQL project.
*
* @author Alexandr Viniychuk <a@viniychuk.com>
* created: 5/19/16 9:00 AM
*/

namespace Youshido\GraphQL\Execution\Context;

use Youshido\GraphQL\Execution\Container\ContainerInterface;
use Youshido\GraphQL\Execution\Request;
use Youshido\GraphQL\Field\Field;
use Youshido\GraphQL\Introspection\Field\SchemaField;
use Youshido\GraphQL\Introspection\Field\TypeDefinitionField;
use Youshido\GraphQL\Schema\AbstractSchema;
use Youshido\GraphQL\Type\Object\AbstractObjectType;
use Youshido\GraphQL\Validator\ErrorContainer\ErrorContainerTrait;
use Youshido\GraphQL\Validator\SchemaValidator\SchemaValidator;

class ExecutionContext implements ExecutionContextInterface
{

    use ErrorContainerTrait;

    /** @var AbstractSchema */
    private $schema;

    /** @var Request */
    private $request;

    /** @var ContainerInterface */
    private $container;

    /** @var array */
    private $typeFieldLookupTable;

    /**
     * ExecutionContext constructor.
     *
     * @param AbstractSchema $schema
     */
    public function __construct(AbstractSchema $schema)
    {
        $this->schema = $schema;
        $this->validateSchema();

        $this->introduceIntrospectionFields();

        $this->typeFieldLookupTable = [];
    }

    /**
     * @param AbstractObjectType $type
     * @param string             $fieldName
     *
     * @return Field
     */
    public function getField(AbstractObjectType $type, $fieldName)
    {
        $typeName = $type->getName();

        if (!array_key_exists($typeName, $this->typeFieldLookupTable)) {
            $this->typeFieldLookupTable[$typeName] = [];
        }

        if (!array_key_exists($fieldName, $this->typeFieldLookupTable[$typeName])) {
            $this->typeFieldLookupTable[$typeName][$fieldName] = $type->getField($fieldName);
        }

        return $this->typeFieldLookupTable[$typeName][$fieldName];
    }

    protected function validateSchema()
    {
        try {
            (new SchemaValidator())->validate($this->schema);
        } catch (\Exception $e) {
            $this->addError($e);
        };
    }

    protected function introduceIntrospectionFields()
    {
        $schemaField = new SchemaField();
        $this->schema->addQueryField($schemaField);
        $this->schema->addQueryField(new TypeDefinitionField());
    }

    /**
     * @return AbstractSchema
     */
    public function getSchema()
    {
        return $this->schema;
    }

    /**
     * @param AbstractSchema $schema
     *
     * @return $this
     */
    public function setSchema(AbstractSchema $schema)
    {
        $this->schema = $schema;

        return $this;
    }

    /**
     * @return Request
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * @param Request $request
     *
     * @return $this
     */
    public function setRequest(Request $request)
    {
        $this->request = $request;

        return $this;
    }

    public function get($id)
    {
        return $this->container->get($id);
    }

    /**
     * @return ContainerInterface
     */
    public function getContainer()
    {
        return $this->container;
    }

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

        return $this;
    }
}


<?php
/**
 * Date: 03.12.15
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Type\ListType;

use Youshido\GraphQL\Config\Object\ListTypeConfig;
use Youshido\GraphQL\Type\CompositeTypeInterface;
use Youshido\GraphQL\Type\Object\AbstractObjectType;
use Youshido\GraphQL\Type\TypeMap;

abstract class AbstractListType extends AbstractObjectType implements CompositeTypeInterface
{
    /**
     * @var ListTypeConfig
     */
    protected $config;

    public function __construct()
    {
        $this->config = new ListTypeConfig(['itemType' => $this->getItemType()], $this);
    }

    /**
     * @return AbstractObjectType
     */
    abstract public function getItemType();

    /**
     * @param mixed $value
     *
     * @return bool
     */
    public function isValidValue($value)
    {
        if (!$this->isIterable($value)) {
            return false;
        }

        return $this->validList($value);
    }

    /**
     * @param $value
     * @param bool $returnValue
     *
     * @return bool
     */
    protected function validList($value, $returnValue = false)
    {
        $itemType = $this->config->get('itemType');

        if ($value && $itemType->isInputType()) {
            foreach ($value as $item) {
                if (!$itemType->isValidValue($item)) {
                    return $returnValue ? $item : false;
                }
            }
        }

        return true;
    }

    /**
     * @inheritdoc
     */
    public function build($config)
    {
    }

    public function isCompositeType()
    {
        return true;
    }

    public function getNamedType()
    {
        return $this->getItemType();
    }

    final public function getKind()
    {
        return TypeMap::KIND_LIST;
    }

    public function getTypeOf()
    {
        return $this->getNamedType();
    }

    public function parseValue($value)
    {
        foreach ((array) $value as $keyValue => $valueItem) {
            $value[$keyValue] = $this->getItemType()->parseValue($valueItem);
        }

        return $value;
    }

    public function getValidationError($value = null)
    {
        if (!$this->isIterable($value)) {
            return 'The value is not an iterable.';
        }
        return $this->config->get('itemType')->getValidationError($this->validList($value, true));
    }

    /**
     * @param $value
     *
     * @return bool
     */
    protected function isIterable($value)
    {
        return null === $value || is_array($value) || ($value instanceof \Traversable);
    }
}


<?php
/**
 * Date: 07.11.16
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Execution;

use Youshido\GraphQL\Execution\Context\ExecutionContextInterface;
use Youshido\GraphQL\Execution\Visitor\AbstractQueryVisitor;
use Youshido\GraphQL\Field\Field;
use Youshido\GraphQL\Field\FieldInterface;
use Youshido\GraphQL\Parser\Ast\Field as FieldAst;
use Youshido\GraphQL\Parser\Ast\FragmentReference;
use Youshido\GraphQL\Parser\Ast\Interfaces\FragmentInterface;
use Youshido\GraphQL\Parser\Ast\Mutation;
use Youshido\GraphQL\Parser\Ast\Query;
use Youshido\GraphQL\Type\AbstractType;
use Youshido\GraphQL\Type\Object\AbstractObjectType;
use Youshido\GraphQL\Type\Union\AbstractUnionType;

class Reducer
{

    /** @var  ExecutionContextInterface */
    private $executionContext;

    /**
     * Apply all of $reducers to this query.  Example reducer operations: checking for maximum query complexity,
     * performing look-ahead query planning, etc.
     *
     * @param ExecutionContextInterface $executionContext
     * @param AbstractQueryVisitor[]    $reducers
     */
    public function reduceQuery(ExecutionContextInterface $executionContext, array $reducers)
    {
        $this->executionContext = $executionContext;
        $schema                 = $executionContext->getSchema();

        foreach ($reducers as $reducer) {
            foreach ($executionContext->getRequest()->getAllOperations() as $operation) {
                $this->doVisit($operation, $operation instanceof Mutation ? $schema->getMutationType() : $schema->getQueryType(), $reducer);
            }
        }
    }

    /**
     * Entry point for the `walkQuery` routine.  Execution bounces between here, where the reducer's ->visit() method
     * is invoked, and `walkQuery` where we send in the scores from the `visit` call.
     *
     * @param Query                $query
     * @param AbstractType         $currentLevelSchema
     * @param AbstractQueryVisitor $reducer
     */
    protected function doVisit(Query $query, $currentLevelSchema, $reducer)
    {
        if (!($currentLevelSchema instanceof AbstractObjectType) || !$currentLevelSchema->hasField($query->getName())) {
            return;
        }

        if ($operationField = $currentLevelSchema->getField($query->getName())) {

            $coroutine = $this->walkQuery($query, $operationField);

            if ($results = $coroutine->current()) {
                $queryCost = 0;
                while ($results) {
                    // initial values come from advancing the generator via ->current, subsequent values come from ->send()
                    list($queryField, $astField, $childCost) = $results;

                    /**
                     * @var Query|FieldAst $queryField
                     * @var Field          $astField
                     */
                    $cost = $reducer->visit($queryField->getKeyValueArguments(), $astField->getConfig(), $childCost);
                    $queryCost += $cost;
                    $results = $coroutine->send($cost);
                }
            }
        }
    }

    /**
     * Coroutine to walk the query and schema in DFS manner (see AbstractQueryVisitor docs for more info) and yield a
     * tuple of (queryNode, schemaNode, childScore)
     *
     * childScore costs are accumulated via values sent into the coroutine.
     *
     * Most of the branching in this function is just to handle the different types in a query: Queries, Unions,
     * Fragments (anonymous and named), and Fields.  The core of the function is simple: recurse until we hit the base
     * case of a Field and yield that back up to the visitor up in `doVisit`.
     *
     * @param Query|Field|\Youshido\GraphQL\Parser\Ast\Interfaces\FragmentInterface $queryNode
     * @param FieldInterface                                                        $currentLevelAST
     *
     * @return \Generator
     */
    protected function walkQuery($queryNode, FieldInterface $currentLevelAST)
    {
        $childrenScore = 0;
        if (!($queryNode instanceof FieldAst)) {
            foreach ($queryNode->getFields() as $queryField) {
                if ($queryField instanceof FragmentInterface) {
                    if ($queryField instanceof FragmentReference) {
                        $queryField = $this->executionContext->getRequest()->getFragment($queryField->getName());
                    }
                    // the next 7 lines are essentially equivalent to `yield from $this->walkQuery(...)` in PHP7.
                    // for backwards compatibility this is equivalent.
                    // This pattern is repeated multiple times in this function, and unfortunately cannot be extracted or
                    // made less verbose.
                    $gen  = $this->walkQuery($queryField, $currentLevelAST);
                    $next = $gen->current();
                    while ($next) {
                        $received = (yield $next);
                        $childrenScore += (int)$received;
                        $next = $gen->send($received);
                    }
                } else {
                    $fieldType = $currentLevelAST->getType()->getNamedType();
                    if ($fieldType instanceof AbstractUnionType) {
                        foreach ($fieldType->getTypes() as $unionFieldType) {
                            if ($fieldAst = $unionFieldType->getField($queryField->getName())) {
                                $gen  = $this->walkQuery($queryField, $fieldAst);
                                $next = $gen->current();
                                while ($next) {
                                    $received = (yield $next);
                                    $childrenScore += (int)$received;
                                    $next = $gen->send($received);
                                }
                            }
                        }
                    } elseif ($fieldType instanceof AbstractObjectType && $fieldAst = $fieldType->getField($queryField->getName())) {
                        $gen  = $this->walkQuery($queryField, $fieldAst);
                        $next = $gen->current();
                        while ($next) {
                            $received = (yield $next);
                            $childrenScore += (int)$received;
                            $next = $gen->send($received);
                        }
                    }
                }
            }
        }
        // sanity check.  don't yield fragments; they don't contribute to cost
        if ($queryNode instanceof Query || $queryNode instanceof FieldAst) {
            // BASE CASE.  If we're here we're done recursing -
            // this node is either a field, or a query that we've finished recursing into.
            yield [$queryNode, $currentLevelAST, $childrenScore];
        }
    }

}


<?php
/*
* This file is a part of GraphQL project.
*
* @author Alexandr Viniychuk <a@viniychuk.com>
* created: 5/11/16 9:19 PM
*/

namespace Youshido\GraphQL\Type;

use Youshido\GraphQL\Exception\ConfigurationException;
use Youshido\GraphQL\Type\Scalar\AbstractScalarType;

class TypeFactory
{
    private static $objectsHash = [];

    /**
     * @param string $type
     *
     * @throws ConfigurationException
     * @return AbstractScalarType
     */
    public static function getScalarType($type)
    {
        if (TypeService::isScalarType($type)) {
            if (is_object($type)) {
                return $type;
            }
            if (empty(self::$objectsHash[$type])) {
                $name = ucfirst($type);

                $name = $name == 'Datetime' ? 'DateTime' : $name;
                $name = $name == 'Datetimetz' ? 'DateTimeTz' : $name;

                $className                = 'Youshido\GraphQL\Type\Scalar\\' . $name . 'Type';
                self::$objectsHash[$type] = new $className();
            }

            return self::$objectsHash[$type];
        } else {
            throw new ConfigurationException('Configuration problem with type ' . $type);
        }
    }

    /**
     * @return string[]
     */
    public static function getScalarTypesNames()
    {
        return [
            TypeMap::TYPE_INT,
            TypeMap::TYPE_FLOAT,
            TypeMap::TYPE_STRING,
            TypeMap::TYPE_BOOLEAN,
            TypeMap::TYPE_ID,
            TypeMap::TYPE_DATETIME,
            TypeMap::TYPE_DATE,
            TypeMap::TYPE_TIMESTAMP,
            TypeMap::TYPE_DATETIMETZ,
        ];
    }
}


<?php
/**
 * Date: 23.11.15
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Parser;

use Youshido\GraphQL\Exception\Parser\SyntaxErrorException;

class Tokenizer
{
    protected $source;
    protected $pos = 0;
    protected $line = 1;
    protected $lineStart = 0;

    /** @var  Token */
    protected $lookAhead;

    protected function initTokenizer($source)
    {
        $this->source    = $source;
        $this->lookAhead = $this->next();
    }

    /**
     * @return Token
     */
    protected function next()
    {
        $this->skipWhitespace();

        return $this->scan();
    }

    protected function skipWhitespace()
    {
        while ($this->pos < strlen($this->source)) {
            $ch = $this->source[$this->pos];
            if ($ch === ' ' || $ch === "\t" || $ch === ',') {
                $this->pos++;
            } elseif ($ch === '#') {
                $this->pos++;
                while (
                    $this->pos < strlen($this->source) &&
                    ($code = ord($this->source[$this->pos])) &&
                    $code !== 10 && $code !== 13 && $code !== 0x2028 && $code !== 0x2029
                ) {
                    $this->pos++;
                }
            } elseif ($ch === "\r") {
                $this->pos++;
                if ($this->source[$this->pos] === "\n") {
                    $this->pos++;
                }
                $this->line++;
                $this->lineStart = $this->pos;
            } elseif ($ch === "\n") {
                $this->pos++;
                $this->line++;
                $this->lineStart = $this->pos;
            } else {
                break;
            }
        }
    }

    /**
     * @return Token
     *
     * @throws SyntaxErrorException
     */
    protected function scan()
    {
        if ($this->pos >= strlen($this->source)) {
            return new Token(Token::TYPE_END, $this->getLine(), $this->getColumn());
        }

        $ch = $this->source[$this->pos];
        switch ($ch) {
            case Token::TYPE_LPAREN:
                ++$this->pos;

                return new Token(Token::TYPE_LPAREN, $this->getLine(), $this->getColumn());
            case Token::TYPE_RPAREN:
                ++$this->pos;

                return new Token(Token::TYPE_RPAREN, $this->getLine(), $this->getColumn());
            case Token::TYPE_LBRACE:
                ++$this->pos;

                return new Token(Token::TYPE_LBRACE, $this->getLine(), $this->getColumn());
            case Token::TYPE_RBRACE:
                ++$this->pos;

                return new Token(Token::TYPE_RBRACE, $this->getLine(), $this->getColumn());
            case Token::TYPE_COMMA:
                ++$this->pos;

                return new Token(Token::TYPE_COMMA, $this->getLine(), $this->getColumn());
            case Token::TYPE_LSQUARE_BRACE:
                ++$this->pos;

                return new Token(Token::TYPE_LSQUARE_BRACE, $this->getLine(), $this->getColumn());
            case Token::TYPE_RSQUARE_BRACE:
                ++$this->pos;

                return new Token(Token::TYPE_RSQUARE_BRACE, $this->getLine(), $this->getColumn());
            case Token::TYPE_REQUIRED:
                ++$this->pos;

                return new Token(Token::TYPE_REQUIRED, $this->getLine(), $this->getColumn());
            case Token::TYPE_AT:
                ++$this->pos;

                return new Token(Token::TYPE_AT, $this->getLine(), $this->getColumn());
            case Token::TYPE_COLON:
                ++$this->pos;

                return new Token(Token::TYPE_COLON, $this->getLine(), $this->getColumn());

            case Token::TYPE_EQUAL:
                ++$this->pos;

                return new Token(Token::TYPE_EQUAL, $this->getLine(), $this->getColumn());

            case Token::TYPE_POINT:
                if ($this->checkFragment()) {
                    return new Token(Token::TYPE_FRAGMENT_REFERENCE, $this->getLine(), $this->getColumn());
                }

                return new Token(Token::TYPE_POINT, $this->getLine(), $this->getColumn());

            case Token::TYPE_VARIABLE:
                ++$this->pos;

                return new Token(Token::TYPE_VARIABLE, $this->getLine(), $this->getColumn());
        }

        if ($ch === '_' || ('a' <= $ch && $ch <= 'z') || ('A' <= $ch && $ch <= 'Z')) {
            return $this->scanWord();
        }

        if ($ch === '-' || ('0' <= $ch && $ch <= '9')) {
            return $this->scanNumber();
        }

        if ($ch === '"') {
            return $this->scanString();
        }

        throw $this->createException('Can\t recognize token type');
    }

    protected function checkFragment()
    {
        $this->pos++;
        $ch = $this->source[$this->pos];

        $this->pos++;
        $nextCh = $this->source[$this->pos];

        $isset = $ch == Token::TYPE_POINT && $nextCh == Token::TYPE_POINT;

        if ($isset) {
            $this->pos++;

            return true;
        }

        return false;
    }

    protected function scanWord()
    {
        $start = $this->pos;
        $this->pos++;

        while ($this->pos < strlen($this->source)) {
            $ch = $this->source[$this->pos];

            if ($ch === '_' || $ch === '$' || ('a' <= $ch && $ch <= 'z') || ('A' <= $ch && $ch <= 'Z') || ('0' <= $ch && $ch <= '9')) {
                $this->pos++;
            } else {
                break;
            }
        }

        $value = substr($this->source, $start, $this->pos - $start);

        return new Token($this->getKeyword($value), $this->getLine(), $this->getColumn(), $value);
    }

    protected function getKeyword($name)
    {
        switch ($name) {
            case 'null':
                return Token::TYPE_NULL;

            case 'true':
                return Token::TYPE_TRUE;

            case 'false':
                return Token::TYPE_FALSE;

            case 'query':
                return Token::TYPE_QUERY;

            case 'fragment':
                return Token::TYPE_FRAGMENT;

            case 'mutation':
                return Token::TYPE_MUTATION;

            case 'on':
                return Token::TYPE_ON;
        }

        return Token::TYPE_IDENTIFIER;
    }

    protected function expect($type)
    {
        if ($this->match($type)) {
            return $this->lex();
        }

        throw $this->createUnexpectedException($this->peek());
    }

    protected function match($type)
    {
        return $this->peek()->getType() === $type;
    }

    protected function scanNumber()
    {
        $start = $this->pos;
        if ($this->source[$this->pos] === '-') {
            ++$this->pos;
        }

        $this->skipInteger();

        if (isset($this->source[$this->pos]) && $this->source[$this->pos] === '.') {
            $this->pos++;
            $this->skipInteger();
        }

        $value = substr($this->source, $start, $this->pos - $start);

        if (strpos($value, '.') === false) {
            $value = (int) $value;
        } else {
            $value = (float) $value;
        }

        return new Token(Token::TYPE_NUMBER, $this->getLine(), $this->getColumn(), $value);
    }

    protected function skipInteger()
    {
        while ($this->pos < strlen($this->source)) {
            $ch = $this->source[$this->pos];
            if ('0' <= $ch && $ch <= '9') {
                $this->pos++;
            } else {
                break;
            }
        }
    }

    protected function createException($message)
    {
        return new SyntaxErrorException(sprintf('%s', $message), $this->getLocation());
    }

    protected function getLocation()
    {
        return new Location($this->getLine(), $this->getColumn());
    }

    protected function getColumn()
    {
        return $this->pos - $this->lineStart;
    }

    protected function getLine()
    {
        return $this->line;
    }

    /*
  		http://facebook.github.io/graphql/October2016/#sec-String-Value
    */
    protected function scanString()
    {
        $len = strlen($this->source);
        $this->pos++;

        $value = '';
        while ($this->pos < $len) {
            $ch = $this->source[$this->pos];
            if ($ch === '"') {
                $token = new Token(Token::TYPE_STRING, $this->getLine(), $this->getColumn(), $value);
                $this->pos++;

                return $token;
            }

            if($ch === '\\' && ($this->pos < ($len - 1))) {
                $this->pos++;
                $ch = $this->source[$this->pos];
                switch($ch) {
                    case '"':
                    case '\\':
                    case '/':
                        break;
                    case 'b':
                        $ch = sprintf("%c", 8);
                        break;
                    case 'f':
                        $ch = "\f";
                        break;
                    case 'n':
                        $ch = "\n";
                        break;
                    case 'r':
                        $ch = "\r";
                        break;
                    case 'u':
                        $codepoint = substr($this->source, $this->pos + 1, 4);
                        if( !preg_match('/[0-9A-Fa-f]{4}/', $codepoint)) {
                            throw $this->createException(sprintf('Invalid string unicode escape sequece "%s"', $codepoint));
                        }
                        $ch = html_entity_decode("&#x{$codepoint};", ENT_QUOTES, 'UTF-8');
                        $this->pos += 4;
                        break;
                    default:
                        throw $this->createException(sprintf('Unexpected string escaped character "%s"', $ch));
                        break;

                }
            }

            $value .= $ch;
            $this->pos++;
        }

        throw $this->createUnexpectedTokenTypeException(Token::TYPE_END);
    }

    protected function end()
    {
        return $this->lookAhead->getType() === Token::TYPE_END;
    }

    protected function peek()
    {
        return $this->lookAhead;
    }

    protected function lex()
    {
        $prev            = $this->lookAhead;
        $this->lookAhead = $this->next();

        return $prev;
    }

    protected function createUnexpectedException(Token $token)
    {
        return $this->createUnexpectedTokenTypeException($token->getType());
    }

    protected function createUnexpectedTokenTypeException($tokenType)
    {
        return $this->createException(sprintf('Unexpected token "%s"', Token::tokenName($tokenType)));
    }
}


<?php
/*
* This file is a part of graphql-youshido project.
*
* @author Alexandr Viniychuk <a@viniychuk.com>
* created: 11/28/15 6:07 PM
*/

namespace Youshido\GraphQL\Validator\ConfigValidator\Rules;

use Youshido\GraphQL\Field\FieldInterface;
use Youshido\GraphQL\Field\InputFieldInterface;
use Youshido\GraphQL\Type\AbstractType;
use Youshido\GraphQL\Type\TypeFactory;
use Youshido\GraphQL\Type\TypeService;
use Youshido\GraphQL\Validator\ConfigValidator\ConfigValidator;

class TypeValidationRule implements ValidationRuleInterface
{

    private $configValidator;

    public function __construct(ConfigValidator $validator)
    {
        $this->configValidator = $validator;
    }

    public function validate($data, $ruleInfo)
    {
        if (!is_string($ruleInfo)) return false;

        switch ($ruleInfo) {
            case TypeService::TYPE_ANY:
                return true;

            case TypeService::TYPE_ANY_OBJECT:
                return is_object($data);

            case TypeService::TYPE_CALLABLE:
                return is_callable($data);

            case TypeService::TYPE_BOOLEAN:
                return is_bool($data);

            case TypeService::TYPE_ARRAY:
                return is_array($data);

            case TypeService::TYPE_STRING:
                return TypeFactory::getScalarType($ruleInfo)->isValidValue($data);

            case TypeService::TYPE_GRAPHQL_TYPE:
                return TypeService::isGraphQLType($data);

            case TypeService::TYPE_OBJECT_TYPE:
                return TypeService::isObjectType($data);

            case TypeService::TYPE_ARRAY_OF_OBJECT_TYPES:
                return $this->isArrayOfObjectTypes($data);

            case TypeService::TYPE_ARRAY_OF_FIELDS_CONFIG:
                return $this->isArrayOfFields($data);

            case TypeService::TYPE_OBJECT_INPUT_TYPE:
                return TypeService::isInputObjectType($data);

            case TypeService::TYPE_ENUM_VALUES:
                return $this->isEnumValues($data);

            case TypeService::TYPE_ARRAY_OF_INPUT_FIELDS:
                return $this->isArrayOfInputFields($data);

            case TypeService::TYPE_ANY_INPUT:
                return TypeService::isInputType($data);

            case TypeService::TYPE_ARRAY_OF_INTERFACES:
                return $this->isArrayOfInterfaces($data);

            default:
                return false;
        }
    }

    private function isArrayOfObjectTypes($data)
    {
        if (!is_array($data) || !count($data)) {
            return false;
        }

        foreach ($data as $item) {
            if (!TypeService::isObjectType($item)) {
                return false;
            }
        }

        return true;
    }

    private function isEnumValues($data)
    {
        if (!is_array($data) || empty($data)) return false;

        foreach ($data as $item) {
            if (!is_array($item) || !array_key_exists('name', $item) || !is_string($item['name']) || !preg_match('/^[_a-zA-Z][_a-zA-Z0-9]*$/', $item['name'])) {
                return false;
            }

            if (!array_key_exists('value', $item)) {
                return false;
            }
        }

        return true;
    }

    private static function isArrayOfInterfaces($data)
    {
        if (!is_array($data)) return false;

        foreach ($data as $item) {
            if (!TypeService::isInterface($item)) {
                return false;
            }
        }

        return true;
    }

    private function isArrayOfFields($data)
    {
        if (!is_array($data) || empty($data)) return false;

        foreach ($data as $name => $item) {
            if (!$this->isField($item, $name)) return false;
        }

        return true;
    }

    private function isField($data, $name = null)
    {
        if (is_object($data)) {
            if (($data instanceof FieldInterface) || ($data instanceof AbstractType)) {
                return !$data->getConfig() || ($data->getConfig() && $this->configValidator->isValidConfig($data->getConfig()));
            } else {
                return false;
            }
        }
        if (!is_array($data)) {
            $data = [
                'type' => $data,
                'name' => $name,
            ];
        } elseif (empty($data['name'])) {
            $data['name'] = $name;
        }
        $this->configValidator->validate($data, $this->getFieldConfigRules());

        return $this->configValidator->isValid();
    }

    private function isArrayOfInputFields($data)
    {
        if (!is_array($data)) return false;

        foreach ($data as $name => $item) {
            if (!$this->isInputField($item)) return false;
        }

        return true;
    }

    private function isInputField($data)
    {
        if (is_object($data)) {
            if ($data instanceof InputFieldInterface) {
                return true;
            } else {
                return TypeService::isInputType($data);
            }
        } else {
            if (!isset($data['type'])) {
                return false;
            }

            return TypeService::isInputType($data['type']);
        }
    }

    /**
     * Exists for the performance
     * @return array
     */
    private function getFieldConfigRules()
    {
        return [
            'name'              => ['type' => TypeService::TYPE_STRING, 'required' => true],
            'type'              => ['type' => TypeService::TYPE_ANY, 'required' => true],
            'args'              => ['type' => TypeService::TYPE_ARRAY],
            'description'       => ['type' => TypeService::TYPE_STRING],
            'resolve'           => ['type' => TypeService::TYPE_CALLABLE],
            'isDeprecated'      => ['type' => TypeService::TYPE_BOOLEAN],
            'deprecationReason' => ['type' => TypeService::TYPE_STRING],
            'cost'              => ['type' => TypeService::TYPE_ANY]
        ];
    }

}


<?php
/*
* This file is a part of GraphQL project.
*
* @author Alexandr Viniychuk <a@viniychuk.com>
* created: 5/19/16 8:58 AM
*/

namespace Youshido\GraphQL\Execution;

use Youshido\GraphQL\Execution\Context\ExecutionContextInterface;
use Youshido\GraphQL\Field\FieldInterface;
use Youshido\GraphQL\Parser\Ast\Field;
use Youshido\GraphQL\Parser\Ast\Query;
use Youshido\GraphQL\Type\AbstractType;

class ResolveInfo
{
    /** @var  FieldInterface */
    protected $field;

    /** @var Field[] */
    protected $fieldASTList;

    /** @var ExecutionContextInterface */
    protected $executionContext;

    /**
     * This property is to be used for DI in various scenario
     * Added to original class to keep backward compatibility
     * because of the way AbstractField::resolve has been declared
     *
     * @var mixed $container
     */
    protected $container;

    public function __construct(FieldInterface $field, array $fieldASTList, ExecutionContextInterface $executionContext)
    {
        $this->field            = $field;
        $this->fieldASTList     = $fieldASTList;
        $this->executionContext = $executionContext;
    }

    /**
     * @return ExecutionContextInterface
     */
    public function getExecutionContext()
    {
        return $this->executionContext;
    }

    /**
     * @return FieldInterface
     */
    public function getField()
    {
        return $this->field;
    }

    /**
     * @param string $fieldName
     *
     * @return null|Query|Field
     */
    public function getFieldAST($fieldName)
    {
        $field = null;
        foreach ($this->getFieldASTList() as $fieldAST) {
            if ($fieldAST->getName() === $fieldName) {
                $field = $fieldAST;
                break;
            }
        }

        return $field;
    }

    /**
     * @return Field[]
     */
    public function getFieldASTList()
    {
        return $this->fieldASTList;
    }

    /**
     * @return AbstractType
     */
    public function getReturnType()
    {
        return $this->field->getType();
    }

    public function getContainer()
    {
        return $this->executionContext->getContainer();
    }

}


<?php
/**
 * Date: 03.12.15
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Introspection;

use Youshido\GraphQL\Field\Field;
use Youshido\GraphQL\Schema\AbstractSchema;
use Youshido\GraphQL\Type\NonNullType;
use Youshido\GraphQL\Type\Object\AbstractObjectType;
use Youshido\GraphQL\Type\TypeInterface;
use Youshido\GraphQL\Type\TypeMap;

class InputValueType extends AbstractObjectType
{
    /**
     * @param AbstractSchema|Field $value
     *
     * @return TypeInterface
     */
    public function resolveType($value)
    {
        return $value->getConfig()->getType();
    }

    /**
     * @param AbstractSchema|Field $value
     *
     * @return string|null
     *
     * //todo implement value printer
     */
    public function resolveDefaultValue($value)
    {
        $resolvedValue = $value->getConfig()->getDefaultValue();
        return $resolvedValue === null ? $resolvedValue : str_replace('"', '', json_encode($resolvedValue));
    }

    public function build($config)
    {
        $config
            ->addField('name', new NonNullType(TypeMap::TYPE_STRING))
            ->addField('description', TypeMap::TYPE_STRING)
            ->addField('isDeprecated', new NonNullType(TypeMap::TYPE_BOOLEAN))
            ->addField('deprecationReason', TypeMap::TYPE_STRING)
            ->addField(new Field([
                'name'    => 'type',
                'type'    => new NonNullType(new QueryType()),
                'resolve' => [$this, 'resolveType']
            ]))
            ->addField('defaultValue', [
                'type' => TypeMap::TYPE_STRING,
                'resolve' => [$this, 'resolveDefaultValue']
            ]);
    }

    /**
     * @return string type name
     */
    public function getName()
    {
        return '__InputValue';
    }
}


<?php
/**
 * Date: 23.11.15
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Execution;

use Youshido\GraphQL\Exception\Parser\InvalidRequestException;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Variable;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\VariableReference;
use Youshido\GraphQL\Parser\Ast\Fragment;
use Youshido\GraphQL\Parser\Ast\FragmentReference;
use Youshido\GraphQL\Parser\Ast\Mutation;
use Youshido\GraphQL\Parser\Ast\Query;

class Request
{

    /** @var  Query[] */
    private $queries = [];

    /** @var Fragment[] */
    private $fragments = [];

    /** @var Mutation[] */
    private $mutations = [];

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

    /** @var VariableReference[] */
    private $variableReferences = [];

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

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

    public function __construct($data = [], $variables = [])
    {
        if (array_key_exists('queries', $data)) {
            $this->addQueries($data['queries']);
        }

        if (array_key_exists('mutations', $data)) {
            $this->addMutations($data['mutations']);
        }

        if (array_key_exists('fragments', $data)) {
            $this->addFragments($data['fragments']);
        }

        if (array_key_exists('fragmentReferences', $data)) {
            $this->addFragmentReferences($data['fragmentReferences']);
        }

        if (array_key_exists('variables', $data)) {
            $this->addQueryVariables($data['variables']);
        }

        if (array_key_exists('variableReferences', $data)) {
            foreach ($data['variableReferences'] as $ref) {
                if (!array_key_exists($ref->getName(), $variables)) {
                    /** @var Variable $variable */
                    $variable = $ref->getVariable();
                    if ($variable->hasDefaultValue()) {
                        $variables[$variable->getName()] = $variable->getDefaultValue()->getValue();
                        continue;
                    }
                    throw new InvalidRequestException(sprintf("Variable %s hasn't been submitted", $ref->getName()), $ref->getLocation());
                }
            }

            $this->addVariableReferences($data['variableReferences']);
        }

        $this->setVariables($variables);
    }

    public function addQueries($queries)
    {
        foreach ($queries as $query) {
            $this->queries[] = $query;
        }
    }

    public function addMutations($mutations)
    {
        foreach ($mutations as $mutation) {
            $this->mutations[] = $mutation;
        }
    }

    public function addQueryVariables($queryVariables)
    {
        foreach ($queryVariables as $queryVariable) {
            $this->queryVariables[] = $queryVariable;
        }
    }

    public function addVariableReferences($variableReferences)
    {
        foreach ($variableReferences as $variableReference) {
            $this->variableReferences[] = $variableReference;
        }
    }

    public function addFragmentReferences($fragmentReferences)
    {
        foreach ($fragmentReferences as $fragmentReference) {
            $this->fragmentReferences[] = $fragmentReference;
        }
    }

    public function addFragments($fragments)
    {
        foreach ($fragments as $fragment) {
            $this->addFragment($fragment);
        }
    }

    /**
     * @return Query[]
     */
    public function getAllOperations()
    {
        return array_merge($this->mutations, $this->queries);
    }

    /**
     * @return Query[]
     */
    public function getQueries()
    {
        return $this->queries;
    }

    /**
     * @return Fragment[]
     */
    public function getFragments()
    {
        return $this->fragments;
    }

    public function addFragment(Fragment $fragment)
    {
        $this->fragments[] = $fragment;
    }

    /**
     * @param $name
     *
     * @return null|Fragment
     */
    public function getFragment($name)
    {
        foreach ($this->fragments as $fragment) {
            if ($fragment->getName() == $name) {
                return $fragment;
            }
        }

        return null;
    }

    /**
     * @return Mutation[]
     */
    public function getMutations()
    {
        return $this->mutations;
    }

    /**
     * @return bool
     */
    public function hasQueries()
    {
        return (bool)count($this->queries);
    }

    /**
     * @return bool
     */
    public function hasMutations()
    {
        return (bool)count($this->mutations);
    }

    /**
     * @return bool
     */
    public function hasFragments()
    {
        return (bool)count($this->fragments);
    }

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

    /**
     * @param array|string $variables
     *
     * @return $this
     */
    public function setVariables($variables)
    {
        if (!is_array($variables)) {
            $variables = json_decode($variables, true);
        }

        $this->variables = $variables;
        foreach ($this->variableReferences as $reference) {
            /** invalid request with no variable */
            if (!$reference->getVariable()) continue;
            $variableName = $reference->getVariable()->getName();

            /** no variable was set at the time */
            if (!array_key_exists($variableName, $variables)) continue;

            $reference->getVariable()->setValue($variables[$variableName]);
            $reference->setValue($variables[$variableName]);
        }

        return $this;
    }

    public function getVariable($name)
    {
        return $this->hasVariable($name) ? $this->variables[$name] : null;
    }

    public function hasVariable($name)
    {
        return array_key_exists($name, $this->variables);
    }

    /**
     * @return array|Variable[]
     */
    public function getQueryVariables()
    {
        return $this->queryVariables;
    }

    /**
     * @param array $queryVariables
     */
    public function setQueryVariables($queryVariables)
    {
        $this->queryVariables = $queryVariables;
    }

    /**
     * @return array|FragmentReference[]
     */
    public function getFragmentReferences()
    {
        return $this->fragmentReferences;
    }

    /**
     * @param array $fragmentReferences
     */
    public function setFragmentReferences($fragmentReferences)
    {
        $this->fragmentReferences = $fragmentReferences;
    }

    /**
     * @return array|VariableReference[]
     */
    public function getVariableReferences()
    {
        return $this->variableReferences;
    }
}


<?php
/**
 * Date: 16.12.15
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Introspection;

use Youshido\GraphQL\Config\Directive\DirectiveConfig;
use Youshido\GraphQL\Directive\Directive;
use Youshido\GraphQL\Directive\DirectiveInterface;
use Youshido\GraphQL\Type\ListType\ListType;
use Youshido\GraphQL\Type\NonNullType;
use Youshido\GraphQL\Type\Object\AbstractObjectType;
use Youshido\GraphQL\Type\TypeMap;

class DirectiveType extends AbstractObjectType
{

    /**
     * @return String type name
     */
    public function getName()
    {
        return '__Directive';
    }

    public function resolveArgs(DirectiveInterface $value)
    {
        if ($value->hasArguments()) {
            return $value->getArguments();
        }

        return [];
    }

    /**
     * @param DirectiveInterface|Directive $value
     *
     * @return mixed
     */
    public function resolveLocations(DirectiveInterface $value)
    {
        /** @var DirectiveConfig $directiveConfig */
        $directiveConfig = $value->getConfig();

        $locations = $directiveConfig->getLocations();

        return $locations;
    }

    public function build($config)
    {
        $config
            ->addField('name', new NonNullType(TypeMap::TYPE_STRING))
            ->addField('description', TypeMap::TYPE_STRING)
            ->addField('args', [
                'type'    => new NonNullType(new ListType(new NonNullType(new InputValueType()))),
                'resolve' => [$this, 'resolveArgs'],
            ])
            ->addField('locations',[
                'type'  =>  new NonNullType(new ListType(new NonNullType(new DirectiveLocationType()))),
                'resolve' => [$this, 'resolveLocations'],
            ]);
    }
}


<?php
/**
 * Date: 10.05.16
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Relay\Connection;

use Youshido\GraphQL\Relay\Type\PageInfoType;
use Youshido\GraphQL\Type\AbstractType;
use Youshido\GraphQL\Type\ListType\ListType;
use Youshido\GraphQL\Type\NonNullType;
use Youshido\GraphQL\Type\Object\ObjectType;
use Youshido\GraphQL\Type\Scalar\IntType;
use Youshido\GraphQL\Type\TypeMap;

class Connection
{

    public static function connectionArgs()
    {
        return array_merge(self::forwardArgs(), self::backwardArgs());
    }

    public static function forwardArgs()
    {
        return [
            'after' => ['type' => TypeMap::TYPE_STRING],
            'first' => ['type' => TypeMap::TYPE_INT]
        ];
    }

    public static function backwardArgs()
    {
        return [
            'before' => ['type' => TypeMap::TYPE_STRING],
            'last'   => ['type' => TypeMap::TYPE_INT]
        ];
    }

    /**
     * @param AbstractType $type
     * @param null|string  $name
     * @param array        $config
     * @option string  edgeFields
     *
     * @return ObjectType
     */
    public static function edgeDefinition(AbstractType $type, $name = null, $config = [])
    {
        $name       = $name ?: $type->getName();
        $edgeFields = !empty($config['edgeFields']) ? $config['edgeFields'] : [];

        $edgeType = new ObjectType([
            'name'        => $name . 'Edge',
            'description' => 'An edge in a connection.',
            'fields'      => array_merge([
                'node'   => [
                    'type'        => $type,
                    'description' => 'The item at the end of the edge',
                    'resolve'     => [__CLASS__, 'getNode'],
                ],
                'cursor' => [
                    'type'        => TypeMap::TYPE_STRING,
                    'description' => 'A cursor for use in pagination'
                ]
            ], $edgeFields)
        ]);

        return $edgeType;
    }

    /**
     * @param AbstractType $type
     * @param null|string  $name
     * @param array        $config
     * @option string  connectionFields
     *
     * @return ObjectType
     */
    public static function connectionDefinition(AbstractType $type, $name = null, $config = [])
    {
        $name             = $name ?: $type->getName();
        $connectionFields = !empty($config['connectionFields']) ? $config['connectionFields'] : [];

        $connectionType = new ObjectType([
            'name'        => $name . 'Connection',
            'description' => 'A connection to a list of items.',
            'fields'      => array_merge([
                'totalCount' => [
                    'type'        => new NonNullType(new IntType()),
                    'description' => 'How many nodes.',
                    'resolve'     => [__CLASS__, 'getTotalCount'],
                ],
                'pageInfo' => [
                    'type'        => new NonNullType(new PageInfoType()),
                    'description' => 'Information to aid in pagination.',
                    'resolve'     => [__CLASS__, 'getPageInfo'],
                ],
                'edges'    => [
                    'type'        => new ListType(self::edgeDefinition($type, $name, $config)),
                    'description' => 'A list of edges.',
                    'resolve'     => [__CLASS__, 'getEdges'],
                ]
            ], $connectionFields)
        ]);

        return $connectionType;
    }

    public static function getTotalCount($value)
    {
        return isset($value['totalCount']) ? $value['totalCount'] : -1;
    }

    public static function getEdges($value)
    {
        return isset($value['edges']) ? $value['edges'] : null;
    }

    public static function getPageInfo($value)
    {
        return isset($value['pageInfo']) ? $value['pageInfo'] : null;
    }

    public static function getNode($value)
    {
        return isset($value['node']) ? $value['node'] : null;
    }
}


<?php
/**
 * Date: 03.11.16
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Execution;

use Youshido\GraphQL\Exception\ResolveException;
use Youshido\GraphQL\Execution\Container\Container;
use Youshido\GraphQL\Execution\Context\ExecutionContext;
use Youshido\GraphQL\Execution\Visitor\MaxComplexityQueryVisitor;
use Youshido\GraphQL\Field\Field;
use Youshido\GraphQL\Field\FieldInterface;
use Youshido\GraphQL\Field\InputField;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputList as AstInputList;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputObject as AstInputObject;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Literal as AstLiteral;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\VariableReference;
use Youshido\GraphQL\Parser\Ast\Field as AstField;
use Youshido\GraphQL\Parser\Ast\FragmentReference;
use Youshido\GraphQL\Parser\Ast\Interfaces\FieldInterface as AstFieldInterface;
use Youshido\GraphQL\Parser\Ast\Mutation as AstMutation;
use Youshido\GraphQL\Parser\Ast\Query as AstQuery;
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference;
use Youshido\GraphQL\Parser\Parser;
use Youshido\GraphQL\Schema\AbstractSchema;
use Youshido\GraphQL\Type\AbstractType;
use Youshido\GraphQL\Type\Enum\AbstractEnumType;
use Youshido\GraphQL\Type\InputObject\AbstractInputObjectType;
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
use Youshido\GraphQL\Type\ListType\AbstractListType;
use Youshido\GraphQL\Type\Object\AbstractObjectType;
use Youshido\GraphQL\Type\Scalar\AbstractScalarType;
use Youshido\GraphQL\Type\TypeMap;
use Youshido\GraphQL\Type\Union\AbstractUnionType;
use Youshido\GraphQL\Validator\RequestValidator\RequestValidator;
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidator;
use Youshido\GraphQL\Validator\ResolveValidator\ResolveValidatorInterface;

class Processor
{

    const TYPE_NAME_QUERY = '__typename';

    /** @var ExecutionContext */
    protected $executionContext;

    /** @var ResolveValidatorInterface */
    protected $resolveValidator;

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

    /** @var int */
    protected $maxComplexity;

    /** @var array DeferredResult[] */
    protected $deferredResultsLeaf = [];

    /** @var array DeferredResult[] */
    protected $deferredResultsComplex = [];

    public function __construct(AbstractSchema $schema)
    {
        if (empty($this->executionContext)) {
            $this->executionContext = new ExecutionContext($schema);
            $this->executionContext->setContainer(new Container());
        }

        $this->resolveValidator = new ResolveValidator($this->executionContext);
    }

    public function processPayload($payload, $variables = [], $reducers = [])
    {
        $this->data = [];

        try {
            $this->parseAndCreateRequest($payload, $variables);

            if ($this->maxComplexity) {
                $reducers[] = new MaxComplexityQueryVisitor($this->maxComplexity);
            }

            if ($reducers) {
                $reducer = new Reducer();
                $reducer->reduceQuery($this->executionContext, $reducers);
            }

            foreach ($this->executionContext->getRequest()->getAllOperations() as $query) {
                if ($operationResult = $this->resolveQuery($query)) {
                    $this->data = array_replace_recursive($this->data, $operationResult);
                };
            }

            // If the processor found any deferred results, resolve them now.
            if (!empty($this->data) && (!empty($this->deferredResultsLeaf) || !empty($this->deferredResultsComplex))) {
              try {
                  while ($deferredResolver = array_shift($this->deferredResultsComplex)) {
                      $deferredResolver->resolve();
                  }

                  // Deferred scalar and enum fields should be resolved last to
                  // pick up as many as possible for a single batch.
                  while ($deferredResolver = array_shift($this->deferredResultsLeaf)) {
                      $deferredResolver->resolve();
                  }
              } catch (\Exception $e) {
                  $this->executionContext->addError($e);
              } finally {
                  $this->data = static::unpackDeferredResults($this->data);
              }
            }

        } catch (\Exception $e) {
            $this->executionContext->addError($e);
        }

        return $this;
    }

    /**
     * Unpack results stored inside deferred resolvers.
     *
     * @param mixed $result
     *   The result ree.
     *
     * @return mixed
     *   The unpacked result.
     */
    public static function unpackDeferredResults($result)
    {
        while ($result instanceof DeferredResult) {
            $result = $result->result;
        }

        if (is_array($result)) {
            foreach ($result as $key => $value) {
                $result[$key] = static::unpackDeferredResults($value);
            }
        }

        return $result;
    }

    protected function resolveQuery(AstQuery $query)
    {
        $schema = $this->executionContext->getSchema();
        $type   = $query instanceof AstMutation ? $schema->getMutationType() : $schema->getQueryType();
        $field  = new Field([
            'name' => $query instanceof AstMutation ? 'mutation' : 'query',
            'type' => $type
        ]);

        if (self::TYPE_NAME_QUERY == $query->getName()) {
            return [$this->getAlias($query) => $type->getName()];
        }

        $this->resolveValidator->assetTypeHasField($type, $query);
        $value = $this->resolveField($field, $query);

        return [$this->getAlias($query) => $value];
    }

    protected function resolveField(FieldInterface $field, AstFieldInterface $ast, $parentValue = null, $fromObject = false)
    {
        try {
            /** @var AbstractObjectType $type */
            $type        = $field->getType();
            $nonNullType = $type->getNullableType();

            if (self::TYPE_NAME_QUERY == $ast->getName()) {
                return $nonNullType->getName();
            }

            $this->resolveValidator->assetTypeHasField($nonNullType, $ast);

            $targetField = $this->executionContext->getField($nonNullType, $ast->getName());

            $this->prepareAstArguments($targetField, $ast, $this->executionContext->getRequest());
            $this->resolveValidator->assertValidArguments($targetField, $ast, $this->executionContext->getRequest());

            switch ($kind = $targetField->getType()->getNullableType()->getKind()) {
                case TypeMap::KIND_ENUM:
                case TypeMap::KIND_SCALAR:
                    if ($ast instanceof AstQuery && $ast->hasFields()) {
                        throw new ResolveException(sprintf('You can\'t specify fields for scalar type "%s"', $targetField->getType()->getNullableType()->getName()), $ast->getLocation());
                    }

                    return $this->resolveScalar($targetField, $ast, $parentValue);

                case TypeMap::KIND_OBJECT:
                    /** @var $type AbstractObjectType */
                    if (!$ast instanceof AstQuery) {
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
                    }

                    return $this->resolveObject($targetField, $ast, $parentValue);

                case TypeMap::KIND_LIST:
                    return $this->resolveList($targetField, $ast, $parentValue);

                case TypeMap::KIND_UNION:
                case TypeMap::KIND_INTERFACE:
                    if (!$ast instanceof AstQuery) {
                        throw new ResolveException(sprintf('You have to specify fields for "%s"', $ast->getName()), $ast->getLocation());
                    }

                    return $this->resolveComposite($targetField, $ast, $parentValue);

                default:
                    throw new ResolveException(sprintf('Resolving type with kind "%s" not supported', $kind));
            }
        } catch (\Exception $e) {
            $this->executionContext->addError($e);

            if ($fromObject) {
                throw $e;
            }

            return null;
        }
    }

    private function prepareAstArguments(FieldInterface $field, AstFieldInterface $query, Request $request)
    {
        foreach ($query->getArguments() as $astArgument) {
            if ($field->hasArgument($astArgument->getName())) {
                $argumentType = $field->getArgument($astArgument->getName())->getType()->getNullableType();

                $astArgument->setValue($this->prepareArgumentValue($astArgument->getValue(), $argumentType, $request));
            }
        }
    }

    private function prepareArgumentValue($argumentValue, AbstractType $argumentType, Request $request)
    {
        switch ($argumentType->getKind()) {
            case TypeMap::KIND_LIST:
                /** @var $argumentType AbstractListType */
                $result = [];
                if ($argumentValue instanceof AstInputList || is_array($argumentValue)) {
                    $list = is_array($argumentValue) ? $argumentValue : $argumentValue->getValue();
                    foreach ($list as $item) {
                        $result[] = $this->prepareArgumentValue($item, $argumentType->getItemType()->getNullableType(), $request);
                    }
                } else {
                    if ($argumentValue instanceof VariableReference) {
                        return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
                    }
                }

                return $result;

            case TypeMap::KIND_INPUT_OBJECT:
                /** @var $argumentType AbstractInputObjectType */
                $result = [];
                if ($argumentValue instanceof AstInputObject) {
                    foreach ($argumentType->getFields() as $field) {
                        /** @var $field Field */
                        if ($field->getConfig()->has('defaultValue')) {
                            $result[$field->getName()] = $field->getType()->getNullableType()->parseInputValue($field->getConfig()->get('defaultValue'));
                        }
                    }
                    foreach ($argumentValue->getValue() as $key => $item) {
                        if ($argumentType->hasField($key)) {
                            $result[$key] = $this->prepareArgumentValue($item, $argumentType->getField($key)->getType()->getNullableType(), $request);
                        } else {
                            $result[$key] = $item;
                        }
                    }
                } else {
                    if ($argumentValue instanceof VariableReference) {
                        return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
                    } else {
                        if (is_array($argumentValue)) {
                            return $argumentValue;
                        }
                    }
                }

                return $result;

            case TypeMap::KIND_SCALAR:
            case TypeMap::KIND_ENUM:
                /** @var $argumentValue AstLiteral|VariableReference */
                if ($argumentValue instanceof VariableReference) {
                    return $this->getVariableReferenceArgumentValue($argumentValue, $argumentType, $request);
                } else {
                    if ($argumentValue instanceof AstLiteral) {
                        return $argumentValue->getValue();
                    } else {
                        return $argumentValue;
                    }
                }
        }

        throw new ResolveException('Argument type not supported');
    }

    private function getVariableReferenceArgumentValue(VariableReference $variableReference, AbstractType $argumentType, Request $request)
    {
        $variable = $variableReference->getVariable();
        if ($argumentType->getKind() === TypeMap::KIND_LIST) {
            if (
                (!$variable->isArray() && !is_array($variable->getValue())) ||
                ($variable->getTypeName() !== $argumentType->getNamedType()->getNullableType()->getName()) ||
                ($argumentType->getNamedType()->getKind() === TypeMap::KIND_NON_NULL && $variable->isArrayElementNullable())
            ) {
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getNamedType()->getNullableType()->getName()), $variable->getLocation());
            }
        } else {
            if ($variable->getTypeName() !== $argumentType->getName()) {
                throw new ResolveException(sprintf('Invalid variable "%s" type, allowed type is "%s"', $variable->getName(), $argumentType->getName()), $variable->getLocation());
            }
        }

        $requestValue = $request->getVariable($variable->getName());
        if ((null === $requestValue && $variable->isNullable()) && !$request->hasVariable($variable->getName())) {
            throw new ResolveException(sprintf('Variable "%s" does not exist in request', $variable->getName()), $variable->getLocation());
        }

        return $requestValue;
    }

    /**
     * @param FieldInterface     $field
     * @param AbstractObjectType $type
     * @param AstFieldInterface  $ast
     * @param                    $resolvedValue
     * @return array
     */
    private function collectResult(FieldInterface $field, AbstractObjectType $type, $ast, $resolvedValue)
    {
        $result = [];

        foreach ($ast->getFields() as $astField) {
            switch (true) {
                case $astField instanceof TypedFragmentReference:
                    $astName  = $astField->getTypeName();
                    $typeName = $type->getName();

                    if ($typeName !== $astName) {
                        foreach ($type->getInterfaces() as $interface) {
                            if ($interface->getName() === $astName) {
                                $result = array_replace_recursive($result, $this->collectResult($field, $type, $astField, $resolvedValue));

                                break;
                            }
                        }

                        continue 2;
                    }

                    $result = array_replace_recursive($result, $this->collectResult($field, $type, $astField, $resolvedValue));

                    break;

                case $astField instanceof FragmentReference:
                    $astFragment      = $this->executionContext->getRequest()->getFragment($astField->getName());
                    $astFragmentModel = $astFragment->getModel();
                    $typeName         = $type->getName();

                    if ($typeName !== $astFragmentModel) {
                        foreach ($type->getInterfaces() as $interface) {
                            if ($interface->getName() === $astFragmentModel) {
                                $result = array_replace_recursive($result, $this->collectResult($field, $type, $astFragment, $resolvedValue));

                                break;
                            }
                        }

                        continue 2;
                    }

                    $result = array_replace_recursive($result, $this->collectResult($field, $type, $astFragment, $resolvedValue));

                    break;

                default:
                    $result = array_replace_recursive($result, [$this->getAlias($astField) => $this->resolveField($field, $astField, $resolvedValue, true)]);
            }
        }

        return $result;
    }

    /**
     * Apply post-process callbacks to all deferred resolvers.
     */
    protected function deferredResolve($resolvedValue, FieldInterface $field, callable $callback) {
        if ($resolvedValue instanceof DeferredResolverInterface) {
            $deferredResult = new DeferredResult($resolvedValue, function ($resolvedValue) use ($field, $callback) {
                // Allow nested deferred resolvers.
                return $this->deferredResolve($resolvedValue, $field, $callback);
            });

            // Whenever we stumble upon a deferred resolver, add it to the queue
            // to be resolved later.
            $type = $field->getType()->getNamedType();
            if ($type instanceof AbstractScalarType || $type instanceof AbstractEnumType) {
                array_push($this->deferredResultsLeaf, $deferredResult);
            } else {
                array_push($this->deferredResultsComplex, $deferredResult);
            }

            return $deferredResult;
        }
        // For simple values, invoke the callback immediately.
        return $callback($resolvedValue);
    }

    protected function resolveScalar(FieldInterface $field, AstFieldInterface $ast, $parentValue)
    {
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
        return $this->deferredResolve($resolvedValue, $field, function($resolvedValue) use ($field, $ast, $parentValue) {
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);

            /** @var AbstractScalarType $type */
            $type = $field->getType()->getNullableType();

            return $type->serialize($resolvedValue);
        });
    }

    protected function resolveList(FieldInterface $field, AstFieldInterface $ast, $parentValue)
    {
        /** @var AstQuery $ast */
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);

        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);

            if (null === $resolvedValue) {
                return null;
            }

            /** @var AbstractListType $type */
            $type     = $field->getType()->getNullableType();
            $itemType = $type->getNamedType();

            $fakeAst = clone $ast;
            if ($fakeAst instanceof AstQuery) {
                $fakeAst->setArguments([]);
            }

            $fakeField = new Field([
              'name' => $field->getName(),
              'type' => $itemType,
              'args' => $field->getArguments(),
            ]);

            $result = [];
            foreach ($resolvedValue as $resolvedValueItem) {
                try {
                    $fakeField->getConfig()->set('resolve', function () use ($resolvedValueItem) {
                        return $resolvedValueItem;
                    });

                    switch ($itemType->getNullableType()->getKind()) {
                        case TypeMap::KIND_ENUM:
                        case TypeMap::KIND_SCALAR:
                            $value = $this->resolveScalar($fakeField, $fakeAst, $resolvedValueItem);

                            break;

                        case TypeMap::KIND_OBJECT:
                            $value = $this->resolveObject($fakeField, $fakeAst, $resolvedValueItem);

                            break;

                        case TypeMap::KIND_UNION:
                        case TypeMap::KIND_INTERFACE:
                            $value = $this->resolveComposite($fakeField, $fakeAst, $resolvedValueItem);

                            break;

                        default:
                            $value = null;
                    }
                } catch (\Exception $e) {
                    $this->executionContext->addError($e);

                    $value = null;
                }

                $result[] = $value;
            }

            return $result;
        });
    }

    protected function resolveObject(FieldInterface $field, AstFieldInterface $ast, $parentValue, $fromUnion = false)
    {
        $resolvedValue = $parentValue;
        if (!$fromUnion) {
            $resolvedValue = $this->doResolve($field, $ast, $parentValue);
        }

        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);

            if (null === $resolvedValue) {
                return null;
            }
            /** @var AbstractObjectType $type */
            $type = $field->getType()->getNullableType();

            try {
                return $this->collectResult($field, $type, $ast, $resolvedValue);
            } catch (\Exception $e) {
                return null;
            }
        });
    }

    protected function resolveComposite(FieldInterface $field, AstFieldInterface $ast, $parentValue)
    {
        /** @var AstQuery $ast */
        $resolvedValue = $this->doResolve($field, $ast, $parentValue);
        return $this->deferredResolve($resolvedValue, $field, function ($resolvedValue) use ($field, $ast, $parentValue) {
            $this->resolveValidator->assertValidResolvedValueForField($field, $resolvedValue);

            if (null === $resolvedValue) {
                return null;
            }

            /** @var AbstractUnionType $type */
            $type         = $field->getType()->getNullableType();
            $resolveInfo = new ResolveInfo(
                $field,
                $ast instanceof AstQuery ? $ast->getFields() : [],
                $this->executionContext
            );
            $resolvedType = $type->resolveType($resolvedValue, $resolveInfo);

            if (!$resolvedType) {
                throw new ResolveException('Resolving function must return type');
            }

            if ($type instanceof AbstractInterfaceType) {
                $this->resolveValidator->assertTypeImplementsInterface($resolvedType, $type);
            } else {
                $this->resolveValidator->assertTypeInUnionTypes($resolvedType, $type);
            }

            $fakeField = new Field([
              'name' => $field->getName(),
              'type' => $resolvedType,
              'args' => $field->getArguments(),
            ]);

            return $this->resolveObject($fakeField, $ast, $resolvedValue, true);
        });
    }

    protected function parseAndCreateRequest($payload, $variables = [])
    {
        if (empty($payload)) {
            throw new \InvalidArgumentException('Must provide an operation.');
        }

        $parser  = new Parser();
        $request = new Request($parser->parse($payload), $variables);

        (new RequestValidator())->validate($request);

        $this->executionContext->setRequest($request);
    }

    protected function doResolve(FieldInterface $field, AstFieldInterface $ast, $parentValue = null)
    {
        /** @var AstQuery|AstField $ast */
        $arguments = $this->parseArgumentsValues($field, $ast);
        $astFields = $ast instanceof AstQuery ? $ast->getFields() : [];

        return $field->resolve($parentValue, $arguments, $this->createResolveInfo($field, $astFields));
    }

    protected function parseArgumentsValues(FieldInterface $field, AstFieldInterface $ast)
    {
        $values   = [];
        $defaults = [];

        foreach ($field->getArguments() as $argument) {
            /** @var $argument InputField */
            if ($argument->getConfig()->has('defaultValue')) {
                $defaults[$argument->getName()] = $argument->getConfig()->getDefaultValue();
            }
        }

        foreach ($ast->getArguments() as $astArgument) {
            $argument     = $field->getArgument($astArgument->getName());
            $argumentType = $argument->getType()->getNullableType();

            $values[$argument->getName()] = $argumentType->parseValue($astArgument->getValue());

            if (isset($defaults[$argument->getName()])) {
                unset($defaults[$argument->getName()]);
            }
        }

        return array_merge($values, $defaults);
    }

    private function getAlias(AstFieldInterface $ast)
    {
        return $ast->getAlias() ?: $ast->getName();
    }

    protected function createResolveInfo(FieldInterface $field, array $astFields)
    {
        return new ResolveInfo($field, $astFields, $this->executionContext);
    }

    /**
     * You can access ExecutionContext to check errors and inject dependencies
     *
     * @return ExecutionContext
     */
    public function getExecutionContext()
    {
        return $this->executionContext;
    }

    public function getResponseData()
    {
        $result = [];

        if (!empty($this->data)) {
            $result['data'] = $this->data;
        }

        if ($this->executionContext->hasErrors()) {
            $result['errors'] = $this->executionContext->getErrorsArray();
        }

        return $result;
    }

    /**
     * @return int
     */
    public function getMaxComplexity()
    {
        return $this->maxComplexity;
    }

    /**
     * @param int $maxComplexity
     */
    public function setMaxComplexity($maxComplexity)
    {
        $this->maxComplexity = $maxComplexity;
    }

}


<?php
/**
 * Date: 17.05.16
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Relay\Connection;

class ArrayConnection
{

    const PREFIX = 'arrayconnection:';

    public static function cursorForObjectInConnection($data, $object)
    {
        if (!is_array($data)) return null;

        $index = array_search($object, $data);
        return $index === false ? null : (string) self::keyToCursor($index);
    }

    /**
     * @param $offset int
     * @return string
     * @deprecated
     *   Use keyToCursor instead.
     */
    public static function offsetToCursor($offset)
    {
      return self::keyToCursor($offset);
    }

    public static function keyToCursor($key)
    {
      return base64_encode(self::PREFIX . $key);
    }

    /**
     * @param $cursor string
     *
     * @return int|null
     * @deprecated Use cursorToKey instead.
     */
    public static function cursorToOffset($cursor)
    {
        return self::cursorToKey($cursor);
    }

  /**
   * Converts a cursor to its array key.
   *
   * @param $cursor
   * @return null|string
   */
    public static function cursorToKey($cursor) {
      if ($decoded = base64_decode($cursor)) {
        return substr($decoded, strlen(self::PREFIX));
      }
      return null;
    }

  /**
   * Converts a cursor to an array offset.
   *
   * @param $cursor
   *   The cursor string.
   * @param $default
   *   The default value, in case the cursor is not given.
   * @param array $array
   *   The array to use in counting the offset. If empty, assumed to be an indexed array.
   * @return int|null
   */
    public static function cursorToOffsetWithDefault($cursor, $default, $array = [])
    {
        if (!is_string($cursor)) {
            return $default;
        }

        $key = self::cursorToKey($cursor);
        if (empty($array)) {
          $offset = $key;
        }
        else {
          $offset = array_search($key, array_keys($array));
        }

        return is_null($offset) ? $default : (int) $offset;
    }

    public static function connectionFromArray(array $data, array $args = [])
    {
        return self::connectionFromArraySlice($data, $args, 0, count($data));
    }

    public static function connectionFromArraySlice(array $data, array $args, $sliceStart, $arrayLength)
    {
        $after  = isset($args['after']) ? $args['after'] : null;
        $before = isset($args['before']) ? $args['before'] : null;
        $first  = isset($args['first']) ? $args['first'] : null;
        $last   = isset($args['last']) ? $args['last'] : null;

        $sliceEnd = $sliceStart + count($data);

        $beforeOffset = ArrayConnection::cursorToOffsetWithDefault($before, $arrayLength, $data);
        $afterOffset  = ArrayConnection::cursorToOffsetWithDefault($after, -1, $data);

        $startOffset = max($sliceStart - 1, $afterOffset, -1) + 1;
        $endOffset   = min($sliceEnd, $beforeOffset, $arrayLength);

        if ($first) {
            $endOffset = min($endOffset, $startOffset + $first);
        }

        if ($last) {
            $startOffset = max($startOffset, $endOffset - $last);
        }

        $arraySliceStart    = max($startOffset - $sliceStart, 0);
        $arraySliceEnd      = count($data) - ($sliceEnd - $endOffset) - $arraySliceStart;

        $slice = array_slice($data, $arraySliceStart, $arraySliceEnd, true);
        $edges = array_map(['self', 'edgeForObjectWithIndex'], $slice, array_keys($slice));

        $firstEdge  = array_key_exists(0, $edges) ? $edges[0] : null;
        $lastEdge   = count($edges) > 0 ? $edges[count($edges) - 1] : null;
        $lowerBound = $after ? $afterOffset + 1 : 0;
        $upperBound = $before ? $beforeOffset : $arrayLength;

        return [
            'totalCount' => $arrayLength,
            'edges'      => $edges,
            'pageInfo'   => [
                'startCursor'     => $firstEdge ? $firstEdge['cursor'] : null,
                'endCursor'       => $lastEdge ? $lastEdge['cursor'] : null,
                'hasPreviousPage' => $last ? $startOffset > $lowerBound : false,
                'hasNextPage'     => $first ? $endOffset < $upperBound : false,
            ],
        ];
    }

    public static function edgeForObjectWithIndex($object, $index)
    {
        return [
            'cursor' => ArrayConnection::keyToCursor($index),
            'node'   => $object
        ];
    }

}