<?php

namespace QCharts\ApiBundle\Controller;

use QCharts\ApiBundle\Exception\ExceptionMessage;
use QCharts\ApiBundle\Exception\InvalidCredentialsException;
use QCharts\CoreBundle\Exception\DatabaseException;
use QCharts\CoreBundle\Exception\InstanceNotFoundException;
use QCharts\CoreBundle\Exception\OffLimitsException;
use QCharts\CoreBundle\Exception\ParameterNotPassedException;
use QCharts\CoreBundle\Exception\SnapshotException;
use QCharts\CoreBundle\Exception\TypeNotValidException;
use QCharts\CoreBundle\Exception\ValidationFailedException;
use QCharts\CoreBundle\Exception\WriteReadException;
use QCharts\CoreBundle\Form\QueryRequestType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;

/**
 * Class ApiController
 * @package QCharts\ApiBundle\Controller
 */
class ApiController extends Controller
{
    /* Key constants from the user's roles in configuration */
    const USER = "user";
    const ADMIN = "admin";
    const SUPER_ADMIN = "super_admin";

    /**
     * @return array
     */
    static public function getNotValidCredentials()
    {
        return [
            "status"=>402,
            "textStatus"=>"Credentials not valid"
        ];
    }

    /**
     * @param $authService
     * @param array $roles
     * @param $role
     * @param bool $allow_demo_users
     * @return bool
     * @throws InvalidCredentialsException
     */
    public static function checkCredentials($authService, array $roles, $role, $allow_demo_users = false)
    {
        /** @var AuthorizationChecker $authService */
        if ($authService->isGranted($roles[$role]) || $allow_demo_users)
        {
            return true;
        }
        throw new InvalidCredentialsException(ExceptionMessage::CREDENTIALS_NOT_VALID($role), 402);
    }

    /**
     * ApiDoc(
     *      description = "Save a query request",
     *      parameters = {
     *          {
     *              "name" = "query_request[title]",
     *              "description" = "Title of the Query",
     *              "required" = true,
     *              "dataType" = "string"
     *          }, {
     *              "name" = "query_request[description]",
     *              "description" = "Brief or concrete description of the query",
     *              "required" = true,
     *              "dataType" = "string"
     *          }, {
     *              "name" = "query_request[config][databaseConnection]",
     *              "required" = true,
     *              "dataType" = "string",
     *              "description" = "The connection to use"
     *          }, {
     *              "name" = "query_request[config][typeOfChart]",
     *              "description" = "Type of chart to display and/or to format query results,
                        please refer to the documentation for supported chart types",
     *              "required" = true,
     *              "dataType" = "string"
     *          }, {
     *              "name" = "query_request[query][query]",
     *              "description" = "Query to execute",
     *              "required" = true,
     *              "dataType" = "string"
     *          }, {
     *              "name" = "query_request[config][queryLimit]",
     *              "required" = false,
     *              "dataType" = "integer",
     *              "description" = "The limit of rows to fetch from the database,
                        refer to the documentation to see the default limit"
     *          }, {
     *              "name"="query_request[config][offset]",
     *              "required" = true,
     *              "dataType" = "integer",
     *              "description" = "The offset from the limit"
     *          }, {
     *              "name"="query_request[config][executionLimit]",
     *              "required" = true,
     *              "dataType" = "number",
     *              "description" = "The time limit execution"
     *          }, {
     *              "name" = "query_request[config][isCached]",
     *              "required" = true,
     *              "dataType" = "integer",
     *              "description" = "type of cached used",
     *              "format" = "[0-2]"
     *          }, {
     *              "name" = "query_request[cronExpression]",
     *              "required" = true,
     *              "dataType" = "string",
     *              "description" = "Cron expression",
     *          }, {
     *              "name" = "query_request[directory]",
     *              "required" = false,
     *              "dataType" = "integer",
     *              "description" = "The folder id to save the query in"
     *          }
     *      },
     *      statusCodes = {
     *          200 = "Registration was successful",
     *          402 = "Credentials not valid",
     *          500 = "The process encountered an unexpected exception"
     *      }
     * )
     *
     * @param Request $request
     * @return JsonResponse
     */
	public function registerAction(Request $request)
    {
        $authorizationChecker = $this->get('security.authorization_checker');
		$queryService = $this->get('qcharts.query');
        $snapshotService = $this->get("qcharts.core.snapshot_service");
		$formFactory = $this->get('form.factory');

        $roles = $this->getParameter('qcharts.user_roles');

		if (!$authorizationChecker->isGranted($roles['admin']))
        {
			return $this->getCredentialsNotValid();
		}

		try
        {
            $doctrine = $this->get("doctrine");
            $chartTypes = $this->getParameter("qcharts.chart_types");
			/** @var Form $form */
			$form = $formFactory->create(new QueryRequestType($doctrine, $chartTypes));
			$form->submit($request->request->get($form->getName()));

			if ($form->isValid())
            {
				$user = $this->getUser();

                $results = $queryService->add($form, $user);

                $snapshotService->writeSnapshot($results["query"], $results["results"]);

                $opts = [
						"status"=>201,
						"textStatus"=>"Query created"
				];
				return new JsonResponse($opts);
			}
		}
        catch (OffLimitsException $e)
        {
            $snapshotService->writeSnapshot($e->getData()["query"], $e->getData()["results"]);

            $options = [
                "status"=>201,
                "textStatus"=>"{$e->getMessage()}. {$e->getPrevious()->getMessage()}"
            ];

            return new JsonResponse($options);
        }
        catch(ParameterNotPassedException $e)
        {
			return $this->getTheFormNotvalid();
		}
        catch (ValidationFailedException $e)
        {
            $options = [
                "status"=>$e->getCode(),
                "textStatus"=>$e->getMessage()
            ];
            return new JsonResponse($options);
        }
        catch (TypeNotValidException $e)
        {
            $options = [
                "status"=>$e->getCode(),
                "textStatus"=>$e->getMessage()
            ];
            return new JsonResponse($options);
        }
        catch (DatabaseException $e)
        {
            $options = [
                "status"=>$e->getCode(),
                "textStatus"=>$e->getMessage()
            ];
            return new JsonResponse($options);
        }

		return $this->getTheFormNotValid();
	}

    /**
     *
     * ApiDoc(
     *      description = "Edit the given query",
     *      parameters = {
     *          {
     *              "name" = "id",
     *              "description" = "Query Request Id to edit",
     *              "dataType" = "integer",
     *              "required" = "true"
     *          }, {
     *              "name" = "query_request[title]",
     *              "description" = "Title of the Query",
     *              "required" = true,
     *              "dataType" = "string"
     *          }, {
     *              "name" = "query_request[description]",
     *              "description" = "Brief or concrete description of the query",
     *              "required" = true,
     *              "dataType" = "string"
     *          }, {
     *              "name" = "query_request[config][databaseConnection]",
     *              "required" = true,
     *              "dataType" = "string",
     *              "description" = "The connection to use"
     *          }, {
     *              "name" = "query_request[config][typeOfChart]",
     *              "description" = "Type of chart to display and/or to format query results,
    please refer to the documentation for supported chart types",
     *              "required" = true,
     *              "dataType" = "string"
     *          }, {
     *              "name" = "query_request[query][query]",
     *              "description" = "Query to execute",
     *              "required" = true,
     *              "dataType" = "string"
     *          }, {
     *              "name" = "query_request[config][queryLimit]",
     *              "required" = false,
     *              "dataType" = "integer",
     *              "description" = "The limit of rows to fetch from the database,
     * refer to the documentation to see the default limit"
     *          }, {
     *              "name"="query_request[config][offset]",
     *              "required" = true,
     *              "dataType" = "integer",
     *              "description" = "The offset from the limit"
     *          }, {
     *              "name"="query_request[config][executionLimit]",
     *              "required" = true,
     *              "dataType" = "number",
     *              "description" = "The time limit execution"
     *          }, {
     *              "name" = "query_request[config][isCached]",
     *              "required" = true,
     *              "dataType" = "integer",
     *              "description" = "type of cached used",
     *              "format" = "[0-2]"
     *          }, {
     *              "name" = "query_request[cronExpression]",
     *              "required" = true,
     *              "dataType" = "string",
     *              "description" = "Cron expression",
     *          }, {
     *              "name" = "query_request[directory]",
     *              "required" = false,
     *              "dataType" = "integer",
     *              "description" = "The folder id to save the query in"
     *          }
     *      }, statusCodes = {
     *          200 = "Requested Query was saved with new data",
     *          402 = "Credentials not valid",
     *          500 = "Form not valid"
     *      }
     * )
     *
     * @param Request $request
     * @param null $queryId
     * @return JsonResponse
     */
	public function editAction(Request $request, $queryId = null)
    {
		$authChecker = $this->get('security.authorization_checker');
		$queryService = $this->get('qcharts.query');
        $snapshotService = $this->get("qcharts.core.snapshot_service");

        $roles = $this->getParameter('qcharts.user_roles');

		if (!$authChecker->isGranted($roles["admin"]))
        {
			return $this->getCredentialsNotValid();
		}

        $opts = [];
        try
        {
            $queryId = $request->request->get('id', null);
            $queryRequest = $queryService->getQueryRequestById($queryId);
            /** @var Form $form */
            $formFactory = $this->get("form.factory");
            $chartTypes = $this->getParameter("qcharts.chart_types");
            $form = $formFactory->create(new QueryRequestType($this->get("doctrine"), $chartTypes), $queryRequest);
            $form->submit($request->request->get($form->getName()));

            if (!$form->isValid() || !$form->isSubmitted())
            {
                throw new ValidationFailedException("Data passed in the form was not valid", 500, null);
            }

            $user = $this->getUser();
            $results = $queryService->edit($form, $user, $queryId);

            $snapshotService->writeSnapshot($results["query"], $results["results"]);

            $opts = [
                "status" =>200,
                "textStatus" => "Query was saved",
                "queryId" => $queryId
            ];

        }
        catch (InstanceNotFoundException $e)
        {
            $opts = [
                "status"=>$e->getCode(),
                "textStatus"=>$e->getMessage(),
                "queryId"=>$queryId
            ];
        }
        catch(ValidationFailedException $e)
        {
            $opts = [
                    "status"=>$e->getCode(),
                    "textStatus" => $e->getMessage(),
                    "queryId" => $queryId
            ];

        }
        catch (ParameterNotPassedException $e)
        {
            $opts = [
                "status"=>$e->getCode(),
                "textStatus" => $e->getMessage(),
                "queryId" => $queryId
            ];
        }
        catch (OffLimitsException $e)
        {
            $opts = [
                "status"=>$e->getCode(),
                "textStatus"=>$e->getMessage(),
                "queryId"=>$queryId
            ];
        }
        catch (DatabaseException $e)
        {
            $opts = [
                "status"=>$e->getCode(),
                "textStatus"=>$e->getMessage(),
                "queryId"=>$queryId
            ];
        }
        catch (TypeNotValidException $e)
        {
            $opts = [
                "status"=>$e->getCode(),
                "textStatus"=>$e->getMessage(),
                "queryId"=>$queryId
            ];
        }
        catch (SnapshotException $e)
        {
            $opts = [
                "status"=>$e->getCode(),
                "textStatus"=>$e->getMessage(),
                "queryId"=>$queryId
            ];
        }
        catch (WriteReadException $e)
        {
            $opts = [
                "status"=>$e->getCode(),
                "textStatus"=>$e->getMessage(),
                "queryId"=>$queryId
            ];
        }
        finally
        {
            return new JsonResponse($opts);
        }

	}

    /**
     *
     * ApiDoc(
     *      description = "Deletes the passed Query",
     *      parameters = {
     *          {
     *              "name" = "id",
     *              "dataType" = "integer",
     *              "required" = true,
     *              "description" = "The Id from the requested Query to delete"
     *          },
     *      },
     *      statusCodes = {
     *          202 = "Requested Query was deleted",
     *          402 = "Credentials are not valid",
     *          404 = "The given Id was not found",
     *          500 = "Error in the deletion"
     *      }
     * )
     *
     * @param Request $request
     * @param null $queryId
     * @return JsonResponse
     */
	public function deleteAction(Request $request, $queryId = null)
    {
		$authChecker = $this->get('security.authorization_checker');
		$queryService = $this->get('qcharts.query');

        $roles = $this->getParameter('qcharts.user_roles');

		if (!$authChecker->isGranted($roles["admin"]))
        {
			return $this->getCredentialsNotValid();
		}

        $opts = [];

        try
        {
			($queryId == null) ? $queryId = $request->request->get('id', null) : false;
			$queryService->delete($queryId);
            //TODO: delete the snapshots! call the service and clean the snapshots!
			$opts = [
					"status"=>202,
					"textStatus" => "Query was deleted"
			];
		}
        catch (ParameterNotPassedException $e)
        {
            $opts = [
                "status"=> $e->getCode(),
                "textStatus" => $e->getMessage()
            ];
        }
        catch (InstanceNotFoundException $e)
        {
			$opts = [
					"status"=> $e->getCode(),
					"textStatus" => $e->getMessage()
			];
		}
        finally
        {
            return new JsonResponse($opts);
        }
	}

    /**
     *
     * ApiDoc(
     *      description = "Get Action, returns the information of the requested query",
     *      parameters = {
     *          {
     *              "name" = "q",
     *              "dataType" = "integer",
     *              "required" = false,
     *              "description" = "The requested query to fetch the details from, by default all Queries returned"
     *          },
     *          {
     *              "name" = "_format",
     *              "dataType" = "string",
     *              "required" = false,
     *              "description" = "The desired encoding to apply. By default json is applied. Options: json or xml"
     *          },
     *          {
     *              "name" = "tm",
     *              "dataType" = "boolean",
     *              "required" = false,
     *              "description" = "Flag to return only the Time Machined ones"
     *          }
     *      },
     *      statusCodes = {
     *          200 = "Queries returned",
     *          402 = "Credentials were not valid",
     *          404 = "Returns when the given requested query Id was not found",
     *      }
     * )
     * @param Request $request
     * @return JsonResponse|Response
     */
	public function getAction(Request $request)
    {
		$authChecker = $this->get('security.authorization_checker');
		$queryService = $this->get('qcharts.query');
        $roles = $this->getParameter('qcharts.user_roles');
        $allow_demo_users = $this->getParameter('qcharts.allow_demo_users');

		if (!$authChecker->isGranted($roles["user"]) && !$allow_demo_users)
        {
			return $this->getCredentialsNotValid($roles["user"]);
		}

		$queryId = $request->query->get('q', null);
        $encodingType = $request->query->get('_format', 'json');
        $cachedFlag = $request->query->get("tm", false);

        try
		{
            $serializer = $this->get("qcharts.serializer");

            if ($cachedFlag)
            {
                $queries = $queryService->getTimeMachinedQueries();
            }
            else
            {
                $queries = $queryService->getQueries($queryId);
            }

            $toSend = [
                "status" => 200,
                "textStatus" => "Queries returned",
                "queries" => $queries,
                "count" => count($queries)
            ];

            $toSend = $serializer->serialize($toSend, $encodingType);

            $response = new Response($toSend);
			$response->headers->set('Content-Type', "application/{$encodingType}");
			return $response;
		}
		catch(InstanceNotFoundException $e)
		{
			$options = [
				"status" => 404,
				"textStatus" => $e->getMessage(),
				"queryId" => $queryId
			];
			return new JsonResponse($options);
		}
	}

    /**
     * @param null $queryId
     * @return JsonResponse
     */
    protected function getTheFormNotValid($queryId = null)
    {
        $opts = [
            "status"=>500,
            "textStatus" => "Data passed was not valid",
            "queryId"=>$queryId
        ];
        return new JsonResponse($opts);
    }

    /**
     * @param null $role
     * @return JsonResponse
     */
    protected function getCredentialsNotValid($role = null)
    {
        return new JsonResponse([
            "status" => 402,
            "textStatus" => "Credentials are not valid {$role}"
        ]);
    }

}


<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Filter
 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: Filter.php 24593 2012-01-05 20:35:02Z matthew $
 */

/**
 * @see Zend_Filter_Interface
 */

/**
 * @category   Zend
 * @package    Zend_Filter
 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Filter implements Zend_Filter_Interface
{

    const CHAIN_APPEND  = 'append';
    const CHAIN_PREPEND = 'prepend';

    /**
     * Filter chain
     *
     * @var array
     */
    protected $_filters = array();

    /**
     * Default Namespaces
     *
     * @var array
     */
    protected static $_defaultNamespaces = array();

    /**
     * Adds a filter to the chain
     *
     * @param  Zend_Filter_Interface $filter
     * @param  string $placement
     * @return Zend_Filter Provides a fluent interface
     */
    public function addFilter(Zend_Filter_Interface $filter, $placement = self::CHAIN_APPEND)
    {
        if ($placement == self::CHAIN_PREPEND) {
            array_unshift($this->_filters, $filter);
        } else {
            $this->_filters[] = $filter;
        }
        return $this;
    }

    /**
     * Add a filter to the end of the chain
     *
     * @param  Zend_Filter_Interface $filter
     * @return Zend_Filter Provides a fluent interface
     */
    public function appendFilter(Zend_Filter_Interface $filter)
    {
        return $this->addFilter($filter, self::CHAIN_APPEND);
    }

    /**
     * Add a filter to the start of the chain
     *
     * @param  Zend_Filter_Interface $filter
     * @return Zend_Filter Provides a fluent interface
     */
    public function prependFilter(Zend_Filter_Interface $filter)
    {
        return $this->addFilter($filter, self::CHAIN_PREPEND);
    }

    /**
     * Get all the filters
     *
     * @return array
     */
    public function getFilters()
    {
        return $this->_filters;
    }

    /**
     * Returns $value filtered through each filter in the chain
     *
     * Filters are run in the order in which they were added to the chain (FIFO)
     *
     * @param  mixed $value
     * @return mixed
     */
    public function filter($value)
    {
        $valueFiltered = $value;
        foreach ($this->_filters as $filter) {
            $valueFiltered = $filter->filter($valueFiltered);
        }
        return $valueFiltered;
    }

    /**
     * Returns the set default namespaces
     *
     * @return array
     */
    public static function getDefaultNamespaces()
    {
        return self::$_defaultNamespaces;
    }

    /**
     * Sets new default namespaces
     *
     * @param array|string $namespace
     * @return null
     */
    public static function setDefaultNamespaces($namespace)
    {
        if (!is_array($namespace)) {
            $namespace = array((string) $namespace);
        }

        self::$_defaultNamespaces = $namespace;
    }

    /**
     * Adds a new default namespace
     *
     * @param array|string $namespace
     * @return null
     */
    public static function addDefaultNamespaces($namespace)
    {
        if (!is_array($namespace)) {
            $namespace = array((string) $namespace);
        }

        self::$_defaultNamespaces = array_unique(array_merge(self::$_defaultNamespaces, $namespace));
    }

    /**
     * Returns true when defaultNamespaces are set
     *
     * @return boolean
     */
    public static function hasDefaultNamespaces()
    {
        return (!empty(self::$_defaultNamespaces));
    }

    /**
     * @deprecated
     * @see Zend_Filter::filterStatic()
     *
     * @param  mixed        $value
     * @param  string       $classBaseName
     * @param  array        $args          OPTIONAL
     * @param  array|string $namespaces    OPTIONAL
     * @return mixed
     * @throws Zend_Filter_Exception
     */
    public static function get($value, $classBaseName, array $args = array(), $namespaces = array())
    {
        trigger_error(
            'Zend_Filter::get() is deprecated as of 1.9.0; please update your code to utilize Zend_Filter::filterStatic()',
            E_USER_NOTICE
        );

        return self::filterStatic($value, $classBaseName, $args, $namespaces);
    }

    /**
     * Returns a value filtered through a specified filter class, without requiring separate
     * instantiation of the filter object.
     *
     * The first argument of this method is a data input value, that you would have filtered.
     * The second argument is a string, which corresponds to the basename of the filter class,
     * relative to the Zend_Filter namespace. This method automatically loads the class,
     * creates an instance, and applies the filter() method to the data input. You can also pass
     * an array of constructor arguments, if they are needed for the filter class.
     *
     * @param  mixed        $value
     * @param  string       $classBaseName
     * @param  array        $args          OPTIONAL
     * @param  array|string $namespaces    OPTIONAL
     * @return mixed
     * @throws Zend_Filter_Exception
     */
    public static function filterStatic($value, $classBaseName, array $args = array(), $namespaces = array())
    {

        $namespaces = array_merge((array) $namespaces, self::$_defaultNamespaces, array('Zend_Filter'));
        foreach ($namespaces as $namespace) {
            $className = $namespace . '_' . ucfirst($classBaseName);
            if (!class_exists($className, false)) {
                try {
                    $file = str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
                    if (Zend_Loader::isReadable($file)) {
                        Zend_Loader::loadClass($className);
                    } else {
                        continue;
                    }
                } catch (Zend_Exception $ze) {
                    continue;
                }
            }

            $class = new ReflectionClass($className);
            if ($class->implementsInterface('Zend_Filter_Interface')) {
                if ($class->hasMethod('__construct')) {
                    $object = $class->newInstanceArgs($args);
                } else {
                    $object = $class->newInstance();
                }
                return $object->filter($value);
            }
        }

        throw new Zend_Filter_Exception("Filter class not found from basename '$classBaseName'");
    }
}


<?php

namespace Reaction\Db\Orm;

use Reaction\Db\Query;
use Reaction\Exceptions\InvalidArgumentException;
use Reaction\Exceptions\InvalidConfigException;
use Reaction\Promise\ExtendedPromiseInterface;
use Reaction\Promise\LazyPromiseInterface;
use function Reaction\Promise\resolve;

/**
 * ActiveRelationTrait implements the common methods and properties for active record relational queries.
 *
 * @method ExtendedPromiseInterface one()
 * @method ExtendedPromiseInterface all()
 * @property ActiveRecordInterface $modelClass
 */
trait ActiveRelationTrait
{
    /**
     * @var bool whether this query represents a relation to more than one record.
     * This property is only used in relational context. If true, this relation will
     * populate all query results into AR instances using [[Query::all()|all()]].
     * If false, only the first row of the results will be retrieved using [[Query::one()|one()]].
     */
    public $multiple;
    /**
     * @var ActiveRecordInterface the primary model of a relational query.
     * This is used only in lazy loading with dynamic query options.
     */
    public $primaryModel;
    /**
     * @var array the columns of the primary and foreign tables that establish a relation.
     * The array keys must be columns of the table for this relation, and the array values
     * must be the corresponding columns from the primary table.
     * Do not prefix or quote the column names as this will be done automatically by Yii.
     * This property is only used in relational context.
     */
    public $link;
    /**
     * @var array|object the query associated with the junction table. Please call [[via()]]
     * to set this property instead of directly setting it.
     * This property is only used in relational context.
     * @see via()
     */
    public $via;
    /**
     * @var string the name of the relation that is the inverse of this relation.
     * For example, an order has a customer, which means the inverse of the "customer" relation
     * is the "orders", and the inverse of the "orders" relation is the "customer".
     * If this property is set, the primary record(s) will be referenced through the specified relation.
     * For example, `$customer->orders[0]->customer` and `$customer` will be the same object,
     * and accessing the customer of an order will not trigger new DB query.
     * This property is only used in relational context.
     * @see inverseOf()
     */
    public $inverseOf;

    /**
     * Clones internal objects.
     */
    public function __clone()
    {
        parent::__clone();
        // make a clone of "via" object so that the same query object can be reused multiple times
        if (is_object($this->via)) {
            $this->via = clone $this->via;
        } elseif (is_array($this->via)) {
            $this->via = [$this->via[0], clone $this->via[1]];
        }
    }

    /**
     * Specifies the relation associated with the junction table.
     *
     * Use this method to specify a pivot record/table when declaring a relation in the [[ActiveRecord]] class:
     *
     * ```php
     * class Order extends ActiveRecord
     * {
     *    public function getOrderItems() {
     *        return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
     *    }
     *
     *    public function getItems() {
     *        return $this->hasMany(Item::className(), ['id' => 'item_id'])
     *                    ->via('orderItems');
     *    }
     * }
     * ```
     *
     * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
     * @param callable $callable a PHP callback for customizing the relation associated with the junction table.
     * Its signature should be `function($query)`, where `$query` is the query to be customized.
     * @return $this the relation object itself.
     */
    public function via($relationName, callable $callable = null)
    {
        $relation = $this->primaryModel->getRelation($relationName);
        $this->via = [$relationName, $relation];
        if ($callable !== null) {
            call_user_func($callable, $relation);
        }

        return $this;
    }

    /**
     * Sets the name of the relation that is the inverse of this relation.
     * For example, a customer has orders, which means the inverse of the "orders" relation is the "customer".
     * If this property is set, the primary record(s) will be referenced through the specified relation.
     * For example, `$customer->orders[0]->customer` and `$customer` will be the same object,
     * and accessing the customer of an order will not trigger a new DB query.
     *
     * Use this method when declaring a relation in the [[ActiveRecord]] class, e.g. in Customer model:
     *
     * ```php
     * public function getOrders()
     * {
     *     return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
     * }
     * ```
     *
     * This also may be used for Order model, but with caution:
     *
     * ```php
     * public function getCustomer()
     * {
     *     return $this->hasOne(Customer::className(), ['id' => 'customer_id'])->inverseOf('orders');
     * }
     * ```
     *
     * in this case result will depend on how order(s) was loaded.
     * Let's suppose customer has several orders. If only one order was loaded:
     *
     * ```php
     * $orders = Order::find()->where(['id' => 1])->all();
     * $customerOrders = $orders[0]->customer->orders;
     * ```
     *
     * variable `$customerOrders` will contain only one order. If orders was loaded like this:
     *
     * ```php
     * $orders = Order::find()->with('customer')->where(['customer_id' => 1])->all();
     * $customerOrders = $orders[0]->customer->orders;
     * ```
     *
     * variable `$customerOrders` will contain all orders of the customer.
     *
     * @param string $relationName the name of the relation that is the inverse of this relation.
     * @return $this the relation object itself.
     */
    public function inverseOf($relationName)
    {
        $this->inverseOf = $relationName;
        return $this;
    }

    /**
     * Finds the related records for the specified primary record.
     * This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion.
     * @param string                                 $name the relation name
     * @param ActiveRecordInterface|BaseActiveRecord $model the primary model
     * @return ExtendedPromiseInterface the related record(s)
     * @throws InvalidArgumentException if the relation is invalid
     */
    public function findFor($name, $model)
    {
        if (method_exists($model, 'get' . $name)) {
            $method = new \ReflectionMethod($model, 'get' . $name);
            $realName = lcfirst(substr($method->getName(), 3));
            if ($realName !== $name) {
                throw new InvalidArgumentException('Relation names are case sensitive. ' . get_class($model) . " has a relation named \"$realName\" instead of \"$name\".");
            }
        }

        return $this->multiple ? $this->all() : $this->one();
    }

    /**
     * If applicable, populate the query's primary model into the related records' inverse relationship.
     * @param array $result the array of related records as generated by [[populate()]]
     */
    protected function addInverseRelations(&$result)
    {
        if ($this->inverseOf === null) {
            return;
        }

        foreach ($result as $i => $relatedModel) {
            if ($relatedModel instanceof ActiveRecordInterface) {
                if (!isset($inverseRelation)) {
                    $inverseRelation = $relatedModel->getRelation($this->inverseOf);
                }
                $relatedModel->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$this->primaryModel] : $this->primaryModel);
            } else {
                if (!isset($inverseRelation)) {
                    /* @var $modelClass ActiveRecordInterface */
                    $modelClass = $this->modelClass;
                    $inverseRelation = $modelClass::instance()->getRelation($this->inverseOf);
                }
                $result[$i][$this->inverseOf] = $inverseRelation->multiple ? [$this->primaryModel] : $this->primaryModel;
            }
        }
    }

    /**
     * Finds the related records and populates them into the primary models.
     * @param string $name the relation name
     * @param array $primaryModels primary models
     * @return ExtendedPromiseInterface with array the related models
     * @throws InvalidConfigException if [[link]] is invalid
     */
    public function populateRelation($name, &$primaryModels)
    {
        if (!is_array($this->link)) {
            throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.');
        }
        $viaQuery = null;
        $viaModels = null;
        $viaModelsUsed = true;
        if ($this->via instanceof self) {
            // via junction table
            /* @var $viaQuery ActiveRelationTrait */
            $viaQuery = $this->via;
            $filterPr = $viaQuery->findJunctionRows($primaryModels)->otherwise(
                function() {
                    return [];
                }
            );
        } elseif (is_array($this->via)) {
            // via relation
            /* @var $viaQuery ActiveRelationTrait|ActiveQueryTrait */
            list($viaName, $viaQuery) = $this->via;
            if ($viaQuery->asArray === null) {
                // inherit asArray from primary query
                $viaQuery->asArray($this->asArray);
            }
            $viaQuery->primaryModel = null;
            $filterPr = $viaQuery->populateRelation($viaName, $primaryModels)->otherwise(
                function() {
                    return [];
                }
            );
        } else {
            $viaModelsUsed = false;
            $filterPr = resolve($primaryModels);
        }
        $filterPr->then(function($filterModels) use (&$viaModelsUsed, &$viaModels) {
            if ($viaModelsUsed) {
                $viaModels = $filterModels;
            }
            $this->filterByModels($filterModels);
            return true;
        })->then(function() use ($name, &$primaryModels, &$viaModels, &$viaQuery) {
            if (!$this->multiple && count($primaryModels) === 1) {
                return $this->populateRelationAsyncSingle($name, $primaryModels);
            } else {
                return $this->populateRelationAsyncMultiple($name, $primaryModels, $viaModels, $viaQuery);
            }
        });
        return $filterPr;
    }

    /**
     * Populate relation for single primary model
     * @param string $name
     * @param ActiveRecordInterface[] $primaryModels
     * @return ExtendedPromiseInterface
     */
    protected function populateRelationAsyncSingle($name, &$primaryModels) {
        return $this->one()->otherwise(function() { return null; })->then(
            function($model) use ($name, &$primaryModels) {
                $primaryModel = reset($primaryModels);
                if ($primaryModel instanceof ActiveRecordInterface) {
                    $primaryModel->populateRelation($name, $model);
                } else {
                    $primaryModels[key($primaryModels)][$name] = $model;
                }
                if ($this->inverseOf !== null) {
                    $this->populateInverseRelation($primaryModels, [$model], $name, $this->inverseOf);
                }

                return [$model];
            }
        );
    }

    /**
     * Populate relation for multiple primary models
     * @param string                       $name
     * @param ActiveRecordInterface[]      $primaryModels
     * @param ActiveRecordInterface[]|null $viaModels
     * @param ActiveQuery|null             $viaQuery
     * @return ExtendedPromiseInterface with ActiveRecordInterface[]
     */
    protected function populateRelationAsyncMultiple($name, &$primaryModels, $viaModels = null, $viaQuery = null) {
        // https://github.com/yiisoft/yii2/issues/3197
        // delay indexing related models after buckets are built
        $indexBy = $this->indexBy;
        $this->indexBy = null;
        return $this->all()->otherwise(function() {
            return [];
        })->then(
            function($models) use (&$primaryModels, $name, $viaModels, $viaQuery, $indexBy) {
                if (isset($viaModels, $viaQuery)) {
                    $buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link);
                } else {
                    $buckets = $this->buildBuckets($models, $this->link);
                }
                $this->indexBy = $indexBy;
                if ($this->indexBy !== null && $this->multiple) {
                    $buckets = $this->indexBuckets($buckets, $this->indexBy);
                }

                $link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link);
                foreach ($primaryModels as $i => $primaryModel) {
                    if ($this->multiple && count($link) === 1 && is_array($keys = $primaryModel[reset($link)])) {
                        $value = [];
                        foreach ($keys as $key) {
                            $key = $this->normalizeModelKey($key);
                            if (isset($buckets[$key])) {
                                if ($this->indexBy !== null) {
                                    // if indexBy is set, array_merge will cause renumbering of numeric array
                                    foreach ($buckets[$key] as $bucketKey => $bucketValue) {
                                        $value[$bucketKey] = $bucketValue;
                                    }
                                } else {
                                    $value = array_merge($value, $buckets[$key]);
                                }
                            }
                        }
                    } else {
                        $key = $this->getModelKey($primaryModel, $link);
                        $value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? [] : null);
                    }
                    if ($primaryModel instanceof ActiveRecordInterface) {
                        $primaryModel->populateRelation($name, $value);
                    } else {
                        $primaryModels[$i][$name] = $value;
                    }
                }
                if ($this->inverseOf !== null) {
                    $this->populateInverseRelation($primaryModels, $models, $name, $this->inverseOf);
                }

                return $models;
            }
        );
    }

    /**
     * @param ActiveRecordInterface[] $primaryModels primary models
     * @param ActiveRecordInterface[] $models models
     * @param string $primaryName the primary relation name
     * @param string $name the relation name
     */
    private function populateInverseRelation(&$primaryModels, $models, $primaryName, $name)
    {
        if (empty($models) || empty($primaryModels)) {
            return;
        }
        $model = reset($models);
        /* @var $relation ActiveQueryInterface|ActiveQuery */
        if ($model instanceof ActiveRecordInterface) {
            $relation = $model->getRelation($name);
        } else {
            /* @var $modelClass ActiveRecordInterface */
            $modelClass = $this->modelClass;
            $relation = $modelClass::instance()->getRelation($name);
        }

        if ($relation->multiple) {
            $buckets = $this->buildBuckets($primaryModels, $relation->link, null, null, false);
            if ($model instanceof ActiveRecordInterface) {
                foreach ($models as $model) {
                    $key = $this->getModelKey($model, $relation->link);
                    $model->populateRelation($name, isset($buckets[$key]) ? $buckets[$key] : []);
                }
            } else {
                foreach ($primaryModels as $i => $primaryModel) {
                    if ($this->multiple) {
                        foreach ($primaryModel as $j => $m) {
                            $key = $this->getModelKey($m, $relation->link);
                            $primaryModels[$i][$j][$name] = isset($buckets[$key]) ? $buckets[$key] : [];
                        }
                    } elseif (!empty($primaryModel[$primaryName])) {
                        $key = $this->getModelKey($primaryModel[$primaryName], $relation->link);
                        $primaryModels[$i][$primaryName][$name] = isset($buckets[$key]) ? $buckets[$key] : [];
                    }
                }
            }
        } else {
            if ($this->multiple) {
                foreach ($primaryModels as $i => $primaryModel) {
                    foreach ($primaryModel[$primaryName] as $j => $m) {
                        if ($m instanceof ActiveRecordInterface) {
                            $m->populateRelation($name, $primaryModel);
                        } else {
                            $primaryModels[$i][$primaryName][$j][$name] = $primaryModel;
                        }
                    }
                }
            } else {
                foreach ($primaryModels as $i => $primaryModel) {
                    if ($primaryModels[$i][$primaryName] instanceof ActiveRecordInterface) {
                        $primaryModels[$i][$primaryName]->populateRelation($name, $primaryModel);
                    } elseif (!empty($primaryModels[$i][$primaryName])) {
                        $primaryModels[$i][$primaryName][$name] = $primaryModel;
                    }
                }
            }
        }
    }

    /**
     * @param array $models
     * @param array $link
     * @param array $viaModels
     * @param array $viaLink
     * @param bool $checkMultiple
     * @return array
     */
    private function buildBuckets($models, $link, $viaModels = null, $viaLink = null, $checkMultiple = true)
    {
        if ($viaModels !== null) {
            $map = [];
            $viaLinkKeys = array_keys($viaLink);
            $linkValues = array_values($link);
            foreach ($viaModels as $viaModel) {
                $key1 = $this->getModelKey($viaModel, $viaLinkKeys);
                $key2 = $this->getModelKey($viaModel, $linkValues);
                $map[$key2][$key1] = true;
            }
        }

        $buckets = [];
        $linkKeys = array_keys($link);

        if (isset($map)) {
            foreach ($models as $model) {
                $key = $this->getModelKey($model, $linkKeys);
                if (isset($map[$key])) {
                    foreach (array_keys($map[$key]) as $key2) {
                        $buckets[$key2][] = $model;
                    }
                }
            }
        } else {
            foreach ($models as $model) {
                $key = $this->getModelKey($model, $linkKeys);
                $buckets[$key][] = $model;
            }
        }

        if ($checkMultiple && !$this->multiple) {
            foreach ($buckets as $i => $bucket) {
                $buckets[$i] = reset($bucket);
            }
        }

        return $buckets;
    }

    /**
     * Indexes buckets by column name.
     *
     * @param array $buckets
     * @param string|callable $indexBy the name of the column by which the query results should be indexed by.
     * This can also be a callable (e.g. anonymous function) that returns the index value based on the given row data.
     * @return array
     */
    private function indexBuckets($buckets, $indexBy)
    {
        $result = [];
        foreach ($buckets as $key => $models) {
            $result[$key] = [];
            foreach ($models as $model) {
                $index = is_string($indexBy) ? $model[$indexBy] : call_user_func($indexBy, $model);
                $result[$key][$index] = $model;
            }
        }

        return $result;
    }

    /**
     * @param array $attributes the attributes to prefix
     * @return array
     */
    private function prefixKeyColumns($attributes)
    {
        if ($this instanceof ActiveQuery && (!empty($this->join) || !empty($this->joinWith))) {
            if (empty($this->from)) {
                /* @var $modelClass ActiveRecordInterface */
                $modelClass = $this->modelClass;
                $alias = $modelClass::tableName();
            } else {
                foreach ($this->from as $alias => $table) {
                    if (!is_string($alias)) {
                        $alias = $table;
                    }
                    break;
                }
            }
            if (isset($alias)) {
                foreach ($attributes as $i => $attribute) {
                    $attributes[$i] = "$alias.$attribute";
                }
            }
        }

        return $attributes;
    }

    /**
     * @param array $models
     */
    private function filterByModels($models)
    {
        $attributes = array_keys($this->link);

        $attributes = $this->prefixKeyColumns($attributes);

        $values = [];
        if (count($attributes) === 1) {
            // single key
            $attribute = reset($this->link);
            foreach ($models as $model) {
                if (($value = $model[$attribute]) !== null) {
                    if (is_array($value)) {
                        $values = array_merge($values, $value);
                    } else {
                        $values[] = $value;
                    }
                }
            }
            if (empty($values)) {
                $this->emulateExecution();
            }
        } else {
            // composite keys

            // ensure keys of $this->link are prefixed the same way as $attributes
            $prefixedLink = array_combine(
                $attributes,
                array_values($this->link)
            );
            foreach ($models as $model) {
                $v = [];
                foreach ($prefixedLink as $attribute => $link) {
                    $v[$attribute] = $model[$link];
                }
                $values[] = $v;
                if (empty($v)) {
                    $this->emulateExecution();
                }
            }
        }
        $this->andWhere(['in', $attributes, array_unique($values, SORT_REGULAR)]);
    }

    /**
     * @param ActiveRecordInterface|array $model
     * @param array $attributes
     * @return string
     */
    private function getModelKey($model, $attributes)
    {
        $key = [];
        foreach ($attributes as $attribute) {
            $key[] = $this->normalizeModelKey($model[$attribute]);
        }
        if (count($key) > 1) {
            return serialize($key);
        }
        $key = reset($key);
        return is_scalar($key) ? $key : serialize($key);
    }

    /**
     * @param mixed $value raw key value.
     * @return string normalized key value.
     */
    private function normalizeModelKey($value)
    {
        if (is_object($value) && method_exists($value, '__toString')) {
            // ensure matching to special objects, which are convertable to string, for cross-DBMS relations, for example: `|MongoId`
            $value = $value->__toString();
        }

        return $value;
    }

    /**
     * @param array $primaryModels either array of AR instances or arrays
     * @return ExtendedPromiseInterface with array
     */
    protected function findJunctionRows($primaryModels)
    {
        if (empty($primaryModels)) {
            return resolve([]);
        }
        $this->filterByModels($primaryModels);
        /* @var $primaryModel ActiveRecordInterface */
        $primaryModel = reset($primaryModels);
        if (!$primaryModel instanceof ActiveRecordInterface) {
            // when primaryModels are array of arrays (asArray case)
            $primaryModel = $this->modelClass;
        }
        /** @var ActiveQueryInterface $that */
        $that = $this->asArray();

        return $that->all($primaryModel::getDb());
    }
}


<?php
/**
 * Message
 *
 * PHP version 5
 *
 * @category Class
 * @package  Swagger\Client
 * @author   http://github.com/swagger-api/swagger-codegen
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache License v2
 * @link     https://github.com/swagger-api/swagger-codegen
 */

/**
 * shoutout-sdk
 *
 * shoutout sdk
 *
 * OpenAPI spec version: 7.9.0
 * Contact: info@getshoutout.com
 * Generated by: https://github.com/swagger-api/swagger-codegen.git
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * NOTE: This class is auto generated by the swagger code generator program.
 * https://github.com/swagger-api/swagger-codegen
 * Do not edit the class manually.
 */

namespace Swagger\Client\Model;

use \ArrayAccess;

/**
 * Message Class Doc Comment
 *
 * @category    Class */
/**
 * @package     Swagger\Client
 * @author      http://github.com/swagger-api/swagger-codegen
 * @license     http://www.apache.org/licenses/LICENSE-2.0 Apache License v2
 * @link        https://github.com/swagger-api/swagger-codegen
 */
class Message implements ArrayAccess
{
    /**
      * The original name of the model.
      * @var string
      */
    protected static $swaggerModelName = 'Message';

    /**
      * Array of property to type mappings. Used for (de)serialization
      * @var string[]
      */
    protected static $swaggerTypes = [
        'source' => 'string',
        'destinations' => 'string[]',
        'transports' => 'string[]',
        'content' => '\Swagger\Client\Model\MessageContent'
    ];

    public static function swaggerTypes()
    {
        return self::$swaggerTypes;
    }

    /**
     * Array of attributes where the key is the local name, and the value is the original name
     * @var string[]
     */
    protected static $attributeMap = [
        'source' => 'source',
        'destinations' => 'destinations',
        'transports' => 'transports',
        'content' => 'content'
    ];

    /**
     * Array of attributes to setter functions (for deserialization of responses)
     * @var string[]
     */
    protected static $setters = [
        'source' => 'setSource',
        'destinations' => 'setDestinations',
        'transports' => 'setTransports',
        'content' => 'setContent'
    ];

    /**
     * Array of attributes to getter functions (for serialization of requests)
     * @var string[]
     */
    protected static $getters = [
        'source' => 'getSource',
        'destinations' => 'getDestinations',
        'transports' => 'getTransports',
        'content' => 'getContent'
    ];

    public static function attributeMap()
    {
        return self::$attributeMap;
    }

    public static function setters()
    {
        return self::$setters;
    }

    public static function getters()
    {
        return self::$getters;
    }

    const TRANSPORTS_SMS = 'sms';

    /**
     * Gets allowable values of the enum
     * @return string[]
     */
    public function getTransportsAllowableValues()
    {
        return [
            self::TRANSPORTS_SMS,
        ];
    }

    /**
     * Associative array for storing property values
     * @var mixed[]
     */
    protected $container = [];

    /**
     * Constructor
     * @param mixed[] $data Associated array of property values initializing the model
     */
    public function __construct(array $data = null)
    {
        $this->container['source'] = isset($data['source']) ? $data['source'] : null;
        $this->container['destinations'] = isset($data['destinations']) ? $data['destinations'] : null;
        $this->container['transports'] = isset($data['transports']) ? $data['transports'] : null;
        $this->container['content'] = isset($data['content']) ? $data['content'] : null;
    }

    /**
     * show all the invalid properties with reasons.
     *
     * @return array invalid properties with reasons
     */
    public function listInvalidProperties()
    {
        $invalid_properties = [];
        if ($this->container['source'] === null) {
            $invalid_properties[] = "'source' can't be null";
        }
        if ($this->container['destinations'] === null) {
            $invalid_properties[] = "'destinations' can't be null";
        }
        return $invalid_properties;
    }

    /**
     * validate all the properties in the model
     * return true if all passed
     *
     * @return bool True if all properteis are valid
     */
    public function valid()
    {
        if ($this->container['source'] === null) {
            return false;
        }
        if ($this->container['destinations'] === null) {
            return false;
        }
        return true;
    }

    /**
     * Gets source
     * @return string
     */
    public function getSource()
    {
        return $this->container['source'];
    }

    /**
     * Sets source
     * @param string $source
     * @return $this
     */
    public function setSource($source)
    {
        $this->container['source'] = $source;

        return $this;
    }

    /**
     * Gets destinations
     * @return string[]
     */
    public function getDestinations()
    {
        return $this->container['destinations'];
    }

    /**
     * Sets destinations
     * @param string[] $destinations
     * @return $this
     */
    public function setDestinations($destinations)
    {
        $this->container['destinations'] = $destinations;

        return $this;
    }

    /**
     * Gets transports
     * @return string[]
     */
    public function getTransports()
    {
        return $this->container['transports'];
    }

    /**
     * Sets transports
     * @param string[] $transports
     * @return $this
     */
    public function setTransports($transports)
    {
        $allowed_values = array('sms');
        if (!is_null($transports) && (array_diff($transports, $allowed_values))) {
            throw new \InvalidArgumentException("Invalid value for 'transports', must be one of 'sms'");
        }
        $this->container['transports'] = $transports;

        return $this;
    }

    /**
     * Gets content
     * @return \Swagger\Client\Model\MessageContent
     */
    public function getContent()
    {
        return $this->container['content'];
    }

    /**
     * Sets content
     * @param \Swagger\Client\Model\MessageContent $content
     * @return $this
     */
    public function setContent($content)
    {
        $this->container['content'] = $content;

        return $this;
    }
    /**
     * Returns true if offset exists. False otherwise.
     * @param  integer $offset Offset
     * @return boolean
     */
    public function offsetExists($offset)
    {
        return isset($this->container[$offset]);
    }

    /**
     * Gets offset.
     * @param  integer $offset Offset
     * @return mixed
     */
    public function offsetGet($offset)
    {
        return isset($this->container[$offset]) ? $this->container[$offset] : null;
    }

    /**
     * Sets value based on offset.
     * @param  integer $offset Offset
     * @param  mixed   $value  Value to be set
     * @return void
     */
    public function offsetSet($offset, $value)
    {
        if (is_null($offset)) {
            $this->container[] = $value;
        } else {
            $this->container[$offset] = $value;
        }
    }

    /**
     * Unsets offset.
     * @param  integer $offset Offset
     * @return void
     */
    public function offsetUnset($offset)
    {
        unset($this->container[$offset]);
    }

    /**
     * Gets the string presentation of the object
     * @return string
     */
    public function __toString()
    {
        if (defined('JSON_PRETTY_PRINT')) { // use JSON pretty print
            return json_encode(\Swagger\Client\ObjectSerializer::sanitizeForSerialization($this), JSON_PRETTY_PRINT);
        }

        return json_encode(\Swagger\Client\ObjectSerializer::sanitizeForSerialization($this));
    }
}


<?php
namespace Core\Toolbox\Arrays;

/**
 * Flatten.php
 *
 * @author Michael "Tekkla" Zorn <tekkla@tekkla.de>
 * @copyright 2016
 * @license MIT
 */
class Flatten extends AbstractArray
{

    /**
     *
     * @var bool
     */
    private $glue = '.';

    /**
     *
     * @var bool
     */
    private $preserve_flagged_arrays = false;

    /**
     * Sets glue
     *
     * @param string $glue
     */
    public function setGlue(string $glue)
    {
        $allowed_glues = [
            '.',
            '_',
            '-'
        ];

        if (!in_array($glue, $allowed_glues)) {
            Throw new ArraysException('This glues "%s" is not permitted. Allowed glues are "%s"', $glue, implode(', ', $allowed_glues));
        }

        $this->glue = $glue;
    }

    /**
     * Retuns glue
     *
     * @return string
     */
    public function getGlue(): string
    {
        return $this->glue;
    }

    /**
     * Sets preserve flagged arrays Flag
     *
     * @param bool $flag
     */
    public function setPreserveFlaggedArraysFlag(bool $flag)
    {
        $this->preserve_flagged_arrays = $flag;
    }

    /**
     * Returns preserve flagged arrays Flag
     *
     * @return bool
     */
    public function getPreserveFlaggedArraysFlag(): bool
    {
        return $this->preserve_flagged_arrays;
    }

    /**
     * Flattens a multidimensional array by using the set glue
     *
     * @param string $prefix
     *            Optional prefix to prepend to flattened keys
     */
    public function flatten(string $prefix = '')
    {
        return $this->process($this->array, $prefix);
    }

    /**
     * Flattens a multidimensional array by using a glue
     *
     * @param array $array
     *            The array to flatten
     * @param string $glue
     *            Optional glue to get flattened array with this glue as return value
     * @param boolean $preserve_flagged_arrays
     *            With this optional flag and a set __preserve key in the array the array will be still flattended but
     *            also be stored as array with an ending .array key. Those arrays will not be flattened further more.
     *            This means any nesting array will stay arrays in this array.
     *
     * @return string|array
     */
    private function process(array $array, $prefix = '')
    {
        $result = [];

        foreach ($array as $key => $value) {

            // Subarrray handling needed?
            if (is_array($value)) {

                // __preserve key set tha signals us to store the array as it is?
                if ($this->preserve_flagged_arrays && array_key_exists('__preserve', $value)) {
                    $result[$prefix . $key . $this->glue . 'array'] = $value;
                    unset($value['__preserve']);
                }

                // Flatten the array
                $result = $result + $this->process($value, $prefix . $key . $this->glue, $this->glue, $this->preserve_flagged_arrays);
            }
            else {
                $result[$prefix . $key] = $value;
            }
        }
        return $result;
    }
}


<?php

namespace Colorium\Orm\Mapper;

use Colorium\Runtime\Annotation;

class Entity
{

    /** @var string */
    public $name;

    /** @var string */
    public $class;

    /** @var Entity\Field[] */
    public $fields = [];

    /**
     * Define entity
     *
     * @param string $name
     * @param string $class
     */
    public function __construct($name, $class = null)
    {
        $this->name = $name;
        $this->class = $class;
    }

    /**
     * Define field
     *
     * @param string $field
     * @param string $type
     * @param bool $nullable
     * @param mixed $default
     * @param bool $primary
     * @return $this
     */
    public function set($field, $type, $nullable = true, $default = null, $primary = false)
    {
        $this->fields[$field] = new Entity\Field($field, $type, $nullable, $default, $primary);

        return $this;
    }

    /**
     * Generate entity from model class
     *
     * @param string $class
     * @return Entity
     */
    public static function of($class)
    {
        $reflector = new \ReflectionClass($class);

        $name = Annotation::ofClass($class, 'entity') ?: strtolower($reflector->getShortName());
        $entity = new static($name, $class);

        $defaults = $reflector->getDefaultProperties();
        foreach ($defaults as $property => $default) {
            $annotations = Annotation::ofProperty($class, $property);
            $type = !empty($annotations['var']) ? $annotations['var'] : 'string';
            $field = new Entity\Field($property, $type);
            $field->nullable = $default !== null;
            $field->default = $default;
            $field->primary = isset($annotations['id']) ?: ($property === 'id');

            $entity->fields[$property] = $field;
        }

        return $entity;
    }

}


<?php declare(strict_types = 1); namespace BapCat\Values;

use InvalidArgumentException;

use function is_string;
use function strlen;

/**
 * Represents text
 *
 * @property-read  string  $raw
 * @property-read  int     $length;
 * @property-read  bool    $is_empty;
 *
 * @author    Corey Frenette
 * @copyright Copyright (c) 2019, BapCat
 */
class Text extends Value {
  /** @var  string  $raw  The raw string */
  private $raw;

  /**
   * Returns an array of Text objects build from an array of strings
   *
   * @param  string[]  $strings  The strings from which to build Text objects
   *
   * @return  Text[]  The Text objects built from the array of strings
   */
  public static function fromArray(array $strings): array {
    return array_map(function($string) {
      return new static($string);
    }, $strings);
  }

  /**
   * @param  string  $string  The raw text in string form
   */
  public function __construct($string) {
    $this->validate($string);
    $this->raw = $string;
  }

  /**
   * Ensures the string passed in is valid
   *
   * @throws  InvalidArgumentException  If the value is not a valid string
   *
   * @param  string  $string  The value to validate
   */
  private function validate($string): void {
    if(!is_string($string)) {
      throw new InvalidArgumentException('Expected string, but got [' . var_export($string, true) . '] instead');
    }
  }

  /**
   * Converts this object to a string
   *
   * @return  string  A string representation of this object
   */
  public function __toString() {
    return $this->raw;
  }

  /**
   * Converts this object to a json encodable-form
   *
   * @return  string  A representation of this object suitable for encoding
   */
  public function jsonSerialize(): string {
    return $this->raw;
  }

  /**
   * Gets the raw value this object wraps
   *
   * @return  string  The raw value this object wraps
   */
  protected function getRaw(): string {
    return $this->raw;
  }

  /*
   * Properties
   */

  /**
   * Gets the length of this text
   *
   * @return  int  The length of this text
   */
  protected function getLength(): int {
    return strlen($this->raw);
  }

  /**
   * Gets whether or not this text is empty
   *
   * @return  bool  True if length is 0, false otherwise
   */
  protected function getIsEmpty(): bool {
    return $this->length === 0;
  }

  /*
   * Methods
   */

  /**
   * Checks if two Text objects are equal
   *
   * @param  Text|null  $other
   *
   * @return  bool  True if the objects are equal, false otherwise
   */
  public function equals(?Text $other = null): bool {
    if($other === null) {
      return false;
    }

    return $this->raw === (string)$other;
  }

  /**
   * Checks if this Text object starts with another Text object
   *
   * @param  Text  $other  The other text object
   *
   * @return  bool  True if this Text object starts with the other Text object, false otherwise
   */
  public function startsWith(Text $other): bool {
    return strpos($this->raw, (string)$other) === 0;
  }

  /**
   * Checks if this Text object ends with another Text object
   *
   * @param  Text  $other  The other text object
   *
   * @return  bool  True if this Text object ends with the other Text object, false otherwise
   */
  public function endsWith(Text $other): bool {
    return strrpos($this->raw, (string)$other, -$other->length) === ($this->length - $other->length);
  }

  /**
   * Checks if this Text object contains another Text object
   *
   * @param  Text  $other  The other text object
   *
   * @return  bool  True if this Text object contains the other Text object, false otherwise
   */
  public function contains(Text $other): bool {
    return strpos($this->raw, (string)$other) !== false;
  }

  /**
   * Checks if this Text object matches a regular expression
   *
   * @param  Regex  $regex  The regex
   *
   * @return  bool  True if this Text object matches the regular expression, false otherwise
   */
  public function matches(Regex $regex): bool {
    return $regex->check($this);
  }

  /**
   * Get part of this Text object
   *
   * @param  int       $start   Where in the string to start
   * @param  int|null  $length  (optional) The number of characters to get
   *
   * @return  Text  A new Text object containing part of this Text object
   */
  public function substring(int $start, ?int $length = null): Text {
    if($length === null) {
      return new static(substr($this->raw, $start));
    }

    return new static(substr($this->raw, $start, $length));
  }

  /**
   * Concatenate two Text objects together
   *
   * @param  Text  $other  The other text object
   *
   * @return  Text  A new Text object containing both Text objects concatenated together
   */
  public function concat(Text $other): Text {
    return new static($this->raw . $other);
  }

  /**
   * Trims this Text object
   *
   * @return  Text  A new Text object containing a trimmed version of this Text object
   */
  public function trim(): Text {
    return new static(trim($this->raw));
  }

  /**
   * Pads this text object with spaces
   *
   * @param  int  $length  The length to pad this Text object to
   *
   * @return  Text  A new Text object containing this Text object, padded to the correct length
   */
  public function pad(int $length): Text {
    return new static(str_pad($this->raw, $length));
  }

  /**
   * Converts this Text object to upper case
   *
   * @return  Text  A new Text object containing this Text object, converted to upper case
   */
  public function toUpperCase(): Text {
    return new static(strtoupper($this->raw));
  }

  /**
   * Converts this Text object to lower case
   *
   * @return  Text  A new Text object containing this Text object, converted to lower case
   */
  public function toLowerCase(): Text {
    return new static(strtolower($this->raw));
  }

  /**
   * Replaces parts of this Text object based on a search and replace
   *
   * @param  Text  $search   The text to find
   * @param  Text  $replace  The text with which to replace the found text
   *
   * @return  Text  A new Text object containing this Text object, with all search text replaced
   */
  public function replace(Text $search, Text $replace): Text {
    return new static(str_replace((string)$search, (string)$replace, $this->raw));
  }

  /**
   * Replaces parts of this Text object based on a search and replace via a regex
   *
   * @param  Regex  $search   The regex to match
   * @param  Text   $replace  The text with which to replace the found text
   *
   * @return  Text  A new Text object containing this Text object, with all search text replaced
   */
  public function replaceByRegex(Regex $search, Text $replace): Text {
    return $search->replace($this, $replace);
  }

  /**
   * Splits this Text object based on another Text object
   *
   * @param  Text  $delimiter  The Text object to split on
   *
   * @return  Text[]  A new Text object containing this Text object, split by the delimiter
   */
  public function split(Text $delimiter): array {
    return static::fromArray(explode((string)$delimiter, $this->raw));
  }
}


<?php namespace Ordercloud\Entities\Organisations;

use JsonSerializable;
use Ordercloud\Entities\Addresses\NamedCoordinatedAddress;

class OrganisationAddress extends NamedCoordinatedAddress implements JsonSerializable
{
    /**
     * @var int
     */
    private $id;
    /**
     * @var string
     */
    private $province;
    /**
     * @var string
     */
    private $country;

    /**
     * @param int    $id
     * @param string $longitude
     * @param string $latitude
     * @param string $name
     * @param string $streetNumber
     * @param string $streetName
     * @param string $complex
     * @param string $suburb
     * @param string $city
     * @param string $postalCode
     * @param string $province
     * @param string $country
     */
    public function __construct($id, $longitude, $latitude, $name, $streetNumber, $streetName, $complex, $suburb, $city, $postalCode, $province, $country)
    {
        parent::__construct(
            $streetNumber,
            $streetName,
            $complex,
            $suburb,
            $city,
            $postalCode,
            $name,
            $latitude,
            $longitude
        );
        $this->id = $id;
        $this->province = $province;
        $this->country = $country;
    }

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

    /**
     * @return string
     */
    public function getCountry()
    {
        return $this->country;
    }

    /**
     * @return string
     */
    public function getProvince()
    {
        return $this->province;
    }

    /**
     * Specify data which should be serialized to JSON
     */
    public function jsonSerialize()
    {
        $json = parent::jsonSerialize();

        $json['id'] = $this->getId();
        $json['province'] = $this->getProvince();
        $json['country'] = $this->getCountry();

        return $json;
    }
}