<?php

/*
 * TODO: determine suffix priority lexically instead of using a hard-coded $suffixList....
 *   For more information, check out semver.org (Semantic Versioning).
 */

namespace crazedsanity\version;

class Version {

	protected $versionFileLocation=null;
	private $_fullVersionString = null;
	private $_projectName = "unknown";
	public static $suffixList = array(
		'ALPHA', 	//very unstable
		'BETA', 	//kinda unstable, but probably useable
		'RC'		//all known bugs fixed, searching for unknown ones
	);

	protected $_versionData = array();

	//=========================================================================
	function __construct($versionData, $projectName=null) {
		if(is_array($versionData)) {
			$this->_versionData = $versionData;
		}
		elseif(is_string($versionData)) {
			if(file_exists($versionData)) {
//				$this->set_version_file_location($versionData);
				$this->_versionData = $this->parse_version_file($versionData, true);
			}
			else {
				$this->_versionData = $this->parse_version_string($versionData);
			}
		}
		else {
			throw new \InvalidArgumentException("no version data supplied");
		}

		if(!is_null($projectName) && !empty($projectName)) {
			$this->_projectName = $projectName;
		}

		$this->_fullVersionString = self::build_full_version_string($this->_versionData);
	}//end __construct()
	//=========================================================================

	//=========================================================================
	/**
	 * Retrieve our version string from the VERSION file.
	 */
	public function get_version($asArray=false) {
//		trigger_error("Deprecated function called (". __METHOD__ .")", E_USER_NOTICE);
//		return self::parse_version_file($this->versionFileLocation, $asArray);

		if($asArray) {
			$retval = $this->_versionData;
			$retval['version_string'] = self::build_full_version_string($this->_versionData);
		}
		else {
//			$retval = self::build_full_version_string($this->_versionData);
			$retval = $this->_fullVersionString;
		}

		return $retval;
	}//end get_version()
	//=========================================================================

	//=========================================================================
	public function parse_version_file($pathToFile, $asArray=false) {
		$foundIt = 0;
		if(file_exists($pathToFile)) {

			$lines = explode("\n", file_get_contents($pathToFile));

			foreach($lines as $lineData) {
				if(strlen($lineData) && preg_match('/(^[A-Z]{3,}):(.+)/', $lineData)) {
					$theBits = explode(': ', $lineData, 2);

					switch(strtolower($theBits[0])) {
						case 'project':
							$this->_projectName = trim($theBits[1]);
							break;
						case 'version':
							$versionInfo = self::parse_version_string($theBits[1]);
							$fullVersionString = self::build_full_version_string($versionInfo);

							if($asArray) {
								$retval = $versionInfo;
								$retval['version_string'] = $fullVersionString;
							}
							else {
								$retval = $fullVersionString;
							}
							$foundIt++;
							break;
					}
				}
			}

			if($foundIt !== 1) {
				throw new \Exception("no version found");
			}
		}
		else {
			throw new \Exception(__METHOD__ .": failed to retrieve version information, file ({$pathToFile}) does not exist");
		}

		return $retval;
	}
	//=========================================================================

	//=========================================================================
	final public function get_project() {
		return $this->_projectName;
	}//end get_project()
	//=========================================================================

	//=========================================================================
	public function set_version_file_location($location) {
		if(file_exists($location)) {
			$this->versionFileLocation = $location;
		}
		else {
			throw new \Exception(__METHOD__ .": invalid location of VERSION file (". $location .")");
		}
	}//end set_version_file_location()
	//=========================================================================

	//=========================================================================
	/**
	 *
	 * TODO: add logic to split apart the suffix (i.e. "-ALPHA5" broken into "ALPHA" and "5").
	 */
	public static function parse_version_string($version) {
		if(is_string($version) && strlen($version) && preg_match('/\./', $version)) {
			$version = preg_replace('/ /', '', $version);

			$pieces = explode('.', $version);
			$retval = array(
				'version_major'			=> $pieces[0],
				'version_minor'			=> $pieces[1]
			);
			if(isset($pieces[2]) && strlen($pieces[2])) {
				$retval['version_maintenance'] = $pieces[2];
			}
			else {
				$retval['version_maintenance'] = 0;
			}

			if(preg_match('/-/', $retval['version_maintenance'])) {
				$bits = explode('-', $retval['version_maintenance']);
				$retval['version_maintenance'] = $bits[0];
				$suffix = $bits[1];
			}
			elseif(preg_match('/-/', $retval['version_minor'])) {
				$bits = explode('-', $retval['version_minor']);
				$retval['version_minor'] = $bits[0];
				$suffix = $bits[1];
			}
			else {
				$suffix = "";
			}
			$retval['version_suffix'] = $suffix;
		}
		else {
			throw new \InvalidArgumentException(__METHOD__ .": invalid version string passed (". $version .")");
		}

		return($retval);
	}//end parse_version_string()
	//=========================================================================

	//=========================================================================
	public static function build_full_version_string(array $versionInfo) {

		$myVersionString = '0';
		if(isset($versionInfo['version_major'])) {
			$myVersionString = $versionInfo['version_major'];
		}

		if(isset($versionInfo['version_minor'])) {
			$myVersionString .= '.'. $versionInfo['version_minor'];
		}
		else {
			$myVersionString .= '.0';
		}

		if(isset($versionInfo['version_maintenance'])) {
			$myVersionString .= '.'. $versionInfo['version_maintenance'];
		}
		else {
			$myVersionString .= '.0';
		}

		if(isset($versionInfo['version_suffix']) && !empty($versionInfo['version_suffix'])) {
			$myVersionString .= '-'. $versionInfo['version_suffix'];
		}

		return $myVersionString;

	}//end build_full_version_string()
	//=========================================================================

	//=========================================================================
	public static function is_higher_version($version, $checkIfHigher) {
		$retval = FALSE;
		if(!is_string($version) || !is_string($checkIfHigher)) {
			throw new \InvalidArgumentException(__METHOD__ .": no valid version strings, version=(". $version ."), checkIfHigher=(". $checkIfHigher .")");
		}
		elseif($version == $checkIfHigher) {
			$retval = FALSE;
		}
		else {
			$curVersionArr = self::parse_version_string($version);
			$checkVersionArr = self::parse_version_string($checkIfHigher);

			unset($curVersionArr['version_string'], $checkVersionArr['version_string']);

			$curVersionSuffix = $curVersionArr['version_suffix'];
			$checkVersionSuffix = $checkVersionArr['version_suffix'];

			unset($curVersionArr['version_suffix']);

			foreach($curVersionArr as $index=>$versionNumber) {
				$checkThis = $checkVersionArr[$index];

				if(is_numeric($checkThis) && is_numeric($versionNumber)) {
					//set them as integers.
					settype($versionNumber, 'int');
					settype($checkThis, 'int');

					if($checkThis > $versionNumber) {
						$retval = TRUE;
						break;
					}
					elseif($checkThis == $versionNumber) {
						//they're equal...
					}
					else {
						//TODO: should there maybe be an option to throw an exception (freak out) here?
					}
				}
				else {
					throw new \Exception(__METHOD__ .": ". $index ." is not numeric in one of the strings " .
						"(versionNumber=". $versionNumber .", checkThis=". $checkThis .")");
				}
			}

			//now deal with those damnable suffixes, but only if the versions are so far identical: if
			//	the "$checkIfHigher" is actually higher, don't bother (i.e. suffixes don't matter when
			//	we already know there's a major, minor, or maintenance version that's also higher.
			if($retval === FALSE) {
				//EXAMPLE: $version="1.0.0-BETA3", $checkIfHigher="1.1.0"
				// Moving from a non-suffixed version to a suffixed version isn't supported, but the inverse is:
				//		i.e. (1.0.0-BETA3 to 1.0.0) is okay, but (1.0.0 to 1.0.0-BETA3) is NOT.
				//		Also: (1.0.0-BETA3 to 1.0.0-BETA4) is okay, but (1.0.0-BETA4 to 1.0.0-BETA3) is NOT.
				if(strlen($curVersionSuffix) && strlen($checkVersionSuffix) && $curVersionSuffix == $checkVersionSuffix) {
					//matching suffixes.
				}
				elseif(strlen($curVersionSuffix) || strlen($checkVersionSuffix)) {
					//we know the suffixes are there and DO match.
					if(strlen($curVersionSuffix) && strlen($checkVersionSuffix)) {
						//okay, here's where we do some crazy things...
						$curVersionData = self::parse_suffix($curVersionSuffix);
						$checkVersionData = self::parse_suffix($checkVersionSuffix);

						if($curVersionData['type'] == $checkVersionData['type']) {
							//got the same suffix type (like "BETA"), check the number.
							if($checkVersionData['number'] > $curVersionData['number']) {
								//new version's suffix number higher than current...
								$retval = TRUE;
							}
							elseif($checkVersionData['number'] == $curVersionData['number']) {
								//new version's suffix number is EQUAL TO current...
								$retval = FALSE;
							}
							else {
								//new version's suffix number is LESS THAN current...
								$retval = FALSE;
							}
						}
						else {
							//not the same suffix... see if the new one is higher.
							$suffixValues = array_flip(self::$suffixList);
							if($suffixValues[$checkVersionData['type']] > $suffixValues[$curVersionData['type']]) {
								$retval = TRUE;
							}
							else {
								//current suffix type is higher...
							}
						}

					}
					elseif(strlen($curVersionSuffix) && !strlen($checkVersionSuffix)) {
						//i.e. "1.0.0-BETA1" to "1.0.0" --->>> OKAY!
						$retval = TRUE;
					}
					elseif(!strlen($curVersionSuffix) && strlen($checkVersionSuffix)) {
						//i.e. "1.0.0" to "1.0.0-BETA1" --->>> NOT ACCEPTABLE!
					}
				}
				else {
					//no suffix to care about
				}
			}
		}

		return($retval);

	}//end is_higher_version()
	//=========================================================================

	//=========================================================================
	public static function parse_suffix($suffix) {
		$retval = NULL;
		if(strlen($suffix)) {
			//determine what kind it is.
			foreach(self::$suffixList as $type) {
				if(preg_match('/^'. $type .'/', $suffix)) {
					$checkThis = preg_replace('/^'. $type .'/', '', $suffix);
					if(strlen($checkThis) && is_numeric($checkThis)) {
						//oooh... it's something like "BETA3"
						$retval = array(
							'type'		=> $type,
							'number'	=> $checkThis
						);
					}
					else {
						throw new \Exception(__METHOD__ .": invalid suffix (". $suffix .")");
					}
					break;
				}
			}
		}
		else {
			throw new \Exception(__METHOD__ .": invalid suffix (". $suffix .")");
		}

		return($retval);
	}//end parse_suffix()
	//=========================================================================

	//=========================================================================
	public function __get($name) {
		trigger_error("Deprecated method called (". $name .")", E_USER_DEPRECATED);
	}
	//=========================================================================

}


<?php

/*
 * This file is a part of dflydev/psr0-resource-locator-composer.
 *
 * (c) Dragonfly Development Inc.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Dflydev\Psr0ResourceLocator\Composer;

use Dflydev\Psr0ResourceLocator\AbstractPsr0ResourceLocator;
use Dflydev\Composer\Autoload\ClassLoaderLocator;

/**
 * Composer PSR-0 Resource Locator
 *
 * @author Beau Simensen <beau@dflydev.com>
 */
class ComposerResourceLocator extends AbstractPsr0ResourceLocator
{
    private $classLoaderLocator;

    /**
     * Constructor
     *
     * @param ClassLoaderLocator $classLoaderLocator
     */
    public function __construct(ClassLoaderLocator $classLoaderLocator = null)
    {
        $this->classLoaderLocator = $classLoaderLocator ?: new ClassLoaderLocator;
    }

    /**
     * {@inheritdoc}
     */
    protected function loadMap()
    {
        $reader = $this->classLoaderLocator->getReader();

        $prefixes = $reader->getPrefixes();
        $prefixes[""] = $reader->getFallbackDirs();

        return $prefixes;
    }
}


<?php

namespace Reaction;

use Psr\Http\Message\ServerRequestInterface;
use Reaction;
use Reaction\DI\ServiceLocator;
use Reaction\Exceptions\InvalidConfigException;
use Reaction\Helpers\ArrayHelper;
use Reaction\Promise\ExtendedPromiseInterface;
use Reaction\Routes\RouteInterface;

/**
 * Class RequestApplication
 * @package Reaction
 */
class RequestApplication extends ServiceLocator implements RequestApplicationInterface, Reaction\DI\ServiceLocatorAutoloadInterface
{
    /**
     * @var ServerRequestInterface Application request instance
     */
    public $request;
    /**
     * @var string Application request charset
     */
    public $charset = 'UTF-8';
    /**
     * @var string Application request language
     */
    public $language = 'en-US';
    /**
     * @var string Application home URL
     */
    public $homeUrl;

    /** @var RouteInterface current route */
    protected $_route;

    /**
     * Get route for request
     * @return RouteInterface
     */
    public function getRoute() {
        if (!isset($this->_route)) {
            $this->createRoute();
        }
        return $this->_route;
    }

    /**
     * Setter for route
     * @param RouteInterface|null $route
     */
    public function setRoute($route = null)
    {
        $this->_route = $route;
    }

    /**
     * Create Route with given params
     * @param string|null $routePath
     * @param string|null $method
     * @param array|null  $params
     * @param bool        $onlyReturn
     * @return RouteInterface
     */
    public function createRoute($routePath = null, $method = null, $params = null, $onlyReturn = false)
    {
        $withInternalCtrl = $routePath !== null;
        $routePath = isset($routePath) ? $routePath : $this->reqHelper->getPathInfo();
        $method = isset($method) ? $method : $this->reqHelper->getMethod();
        $data = Reaction::$app->router->searchRoute($this, $routePath, $method, $withInternalCtrl);
        //Parameters overwrite
        if (is_array($params) && count($data) >= 3) {
            $data[3] = $params;
        }
        $route = Reaction::create([
            'class' => RouteInterface::class,
            'app' => $this,
            'dispatchedData' => $data,
        ]);
        if (!$onlyReturn) {
            $this->_route = $route;
        }
        return $route;
    }

    /**
     * Handle incoming request
     * @return ExtendedPromiseInterface
     */
    public function handleRequest()
    {
        //Wait for static application initialization
        $staticInitPromise = !Reaction::$app->initialized ? Reaction::$app->initPromise : Reaction\Promise\resolve(true);
        return $staticInitPromise
            //Load Request app components
            ->then(function() {
                return $this->loadComponents();
            //Try to resolve request action
            })->then(function() {
                return $this->resolveAction();
            //Handle exceptions
            })->otherwise(function($exception) {
                if (Reaction::isConsoleApp() && $exception instanceof Reaction\Exceptions\Http\NotFoundException) {
                    $exception = new Reaction\Console\UnknownCommandException($this->reqHelper->getUrl(), $this, $exception->getCode(), $exception);
                }
                return $this->errorHandler->handleException($exception);
            //Emit EVENT_REQUEST_END event
            })->always(function() {
                return $this->emitAndWait(static::EVENT_REQUEST_END, [$this]);
            });
    }

    /**
     * Resolve app action
     * @param string|null $routePath
     * @param string|null $method
     * @param array|null $params
     * @return Promise\ExtendedPromiseInterface
     */
    public function resolveAction($routePath = null, $method = null, $params = null)
    {
        $route = $this->createRoute($routePath, $method, $params);
        return $route->resolve();
    }

    /**
     * Get app URL manager
     * @return \Reaction\Web\UrlManager
     * @throws InvalidConfigException
     */
    public function getUrlManager()
    {
        /** @var Reaction\Web\UrlManager $manager */
        $manager = $this->get('urlManager');
        return $manager;
    }

    /**
     * Get app home URL
     * @return string
     */
    public function getHomeUrl()
    {
        return isset($this->homeUrl) ? $this->homeUrl : Reaction::$app->urlManager->getHomeUrl();
    }

    /**
     * Registers a component definition with this locator.
     * Overridden to inject application instance
     * @param string $id
     * @param mixed  $definition
     * @throws InvalidConfigException
     */
    public function set($id, $definition)
    {
        unset($this->_components[$id]);

        if ($definition === null) {
            unset($this->_definitions[$id]);
            return;
        }

        //Extract definition from DI Definition
        if ($definition instanceof \Reaction\DI\Definition) {
            $definition = $definition->dumpArrayDefinition();
        }

        if (is_string($definition)) {
            $config = ['class' => $definition];
        } elseif (ArrayHelper::isIndexed($definition) && count($definition) === 2) {
            $config = $definition[0];
            $params = $definition[1];
        } else {
            $config = $definition;
        }

        if (is_array($config)) {
            $config = ArrayHelper::merge(['app' => $this], $config);
            $definition = isset($params) ? [$config, $params] : $config;
        }

        if (is_object($definition) || is_callable($definition, true)) {
            // an object, a class name, or a PHP callable
            $this->_definitions[$id] = $definition;
        } elseif (is_array($definition)) {
            // a configuration array
            if (isset($definition['class'])) {
                $this->_definitions[$id] = $definition;
            } else {
                throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element.");
            }
        } else {
            throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition));
        }
    }

    /**
     * Translates a message to the specified language.
     *
     * @param string $category Message category
     * @param string $message  Message for translation
     * @param array  $params   Parameters array
     * @param string $language Language translate to
     * @return string
     */
    public function t($category, $message, $params = [], $language = null)
    {
        $language = isset($language) ? $language : $this->language;
        return Reaction::t($category, $message, $params, $language);
    }
}


<?php
namespace Gosyl\CommonBundle\Entity;

use Doctrine\ORM\EntityRepository;
use Gosyl\CommonBundle\Service\DataTable;

class ParamUsersRepository extends EntityRepository {
	public function getAllForDataTable($aWhere = null, $aExtras = null) {
		$oDataTable = new DataTable($this->getEntityManager());

		$aRequest = array(
				0 => 'U.id',
				1 => 'U.username',
				2 => 'U.isActive',
				3 => 'U.email',
				4 => 'U.roles',
				5 => 'U.name',
				6 => 'U.prenom',
				7 => 'U.dateInscription',
				8 => 'U.dateNaissance',
				9 => 'U.dateSuppression',
				10 => 'U.roles'
		);

		$aChamps = array();

		foreach ($aRequest as $value) {
			$aChamps[] = $value;
		}

		// Début de la requête
		$oQueryBuilder = $this->createQueryBuilder('U');
		$oQuery = $oQueryBuilder->select($aChamps);

		// Comptage des résultats
		$oQueryBuilderCount = $this->createQueryBuilder('U');
		$oQueryCount = $oQueryBuilderCount->select(array('COUNT(DISTINCT U.id) AS NB'));

		// Comptage des résultats sans filtre
		$oQueryBuilderCountTotal = $this->createQueryBuilder('U');
		$oQueryBuilderCountTotal->select(array('COUNT(DISTINCT U.id) AS NB'));

		$oQueryBuilder = $oDataTable->getExtraParams($oQueryBuilder, $aExtras, $aRequest);
		$oQueryBuilderCount = $oDataTable->getExtraParams($oQueryBuilderCount, $aExtras, $aRequest, true);

		/**
		 * @todo traiter le where
		 */
		if(!is_null($aWhere)) {
			foreach ($aWhere as $sCol => $aValue) {
				if($aValue['type'] == 'number') {
					$oQueryBuilder->where($oQueryBuilder->expr()->eq($sCol, $aValue['bind']))->setParameter($aValue['bind'], $aValue['val']);
					$oQueryBuilderCount->where($oQueryBuilder->expr()->eq($sCol, $aValue['bind']))->setParameter($aValue['bind'], $aValue['val']);
					$oQueryBuilderCountTotal->where($oQueryBuilder->expr()->eq($sCol, $aValue['bind']))->setParameter($aValue['bind'], $aValue['val']);
				}
			}
		}

		$oQueryCount = $oQueryBuilderCount->getQuery();
		$aNbRequest = $oQueryCount->getArrayResult();
		$nNbRequest = $aNbRequest[0]['NB'];

		$oQueryCountTotal = $oQueryBuilderCountTotal->getQuery();
		$aNbTotal = $oQueryCountTotal->getArrayResult();
		$nNbTotal = $aNbTotal[0]['NB'];

		$oQuery = $oQueryBuilder->getQuery();
		$aResultQuery = $oQuery->getArrayResult();

		$aResult = array('data' => $aResultQuery, 'recordsTotal' => $nNbRequest, 'recordsFiltered' => $nNbTotal);

		return $aResult;
	}

	public function modifierUsers($aDataToModifiy, $idUser) {
		$oQb = $this->createQueryBuilder('U');
		$oQb->update();

		foreach ($aDataToModifiy as $key => $value) {
			$aTabKey = explode('.', $key);
			$oQb->set($key, ':' . $aTabKey[1])
				->setParameter($aTabKey[1], $value);
		}

		$oQb->where($oQb->expr()->eq('U.id', $idUser));

		$iReturn = $oQb->getQuery()->execute();

		return $iReturn;
	}

	/**
	 * Désactive l'utilisateur et met à jour la date de suppression
	 *
	 * @param int $idUser
	 * @param \DateTime $oDateNow
	 * @return mixed
	 */
	public function supprimerUtilisateur($idUser, \DateTime $oDateNow) {
		$oQb = $this->createQueryBuilder('U');

		$oQb->update()
			->set('U.dateSuppression', ':dateNow')
			->setParameter(':dateNow', $oDateNow)
			->where($oQb->expr()->eq('U.id', $idUser));

		$iReturn = $oQb->getQuery()->execute();

		return $iReturn;
	}

	/**
	 * Restaure un utilisateur
	 * @param int $idUser
	 * @return mixed
	 */
	public function restaureUtilisateur($idUser) {
		$oQb = $this->createQueryBuilder('U');

		$oQb->update()
			->set('U.dateSuppression', 'NULL')
			->where($oQb->expr()->eq('U.id', $idUser));

		$iReturn = $oQb->getQuery()->execute();

		return $iReturn;
	}

	/**
	 * Désactive un utilisateur
	 *
	 * @param int $idUser
	 * @param int $activeOuDesactive
	 * @return mixed
	 */
	public function banUtilisateur($idUser, $activeOuDesactive) {
		$oQb = $this->createQueryBuilder('U');

		$oQb->update()
			->set('U.isActive', ':param')
			->where($oQb->expr()->eq('U.id', ':idUser'))
			->setParameter(':idUser', $idUser)
			->setParameter(':param', $activeOuDesactive);

		$iRetour = $oQb->getQuery()->execute();

		return $iRetour;
	}

	/**
	 * Récupère la liste des Administrateurs du site
	 * @return array
	 */
	public function getAdmin() {
		$oQB = $this->createQueryBuilder('U');
		$oQB->select(array('U.username', 'U.email'))
			->where($oQB->expr()->like('U.roles', $oQB->expr()->literal(serialize(array('ROLE_ADMIN')))));

		$oQuery = $oQB->getQuery();
		$oResult = $oQuery->getArrayResult();

		return $oResult;
	}

	/**
	 * Retourne les informations d'un utilisateur en fonction de son id
	 *
	 * @param int $id
	 * @return array
	 */
	public function getUserById($id) {
		$oQB = $this->createQueryBuilder('U');
		$oQB->select()
			->where($oQB->expr()->eq('U.id', ':id'))
			->setParameter(':id', $id);

		$aResult = $oQB->getQuery()->getOneOrNullResult();

		return $aResult;
	}

	public function updatePassword($pwdEncoded, $idUser) {
		$oQb = $this->createQueryBuilder('U');

		$oQb->update()
			->set('U.password', ':pwd')
			->where($oQb->expr()->eq('U.id', ':idUser'))
			->setParameter(':pwd', $pwdEncoded)
			->setParameter(':idUser', $idUser);

		$iReturn = $oQb->getQuery()->execute();

		return $iReturn;
	}

	/**
	 * Retourne les utilisateurs actifs
	 *
	 * @return array
	 */
	public function getActive() {
		$oDelay = new \DateTime('now');
		$oDelay->setTimestamp(strtotime('2 minutes ago'));

		$oQb = $this->createQueryBuilder('U');
		$oQb->select()
			->where($oQb->expr()->gt('U.lastActivityAt', ':delay'))
			->setParameter(':delay', $oDelay);

        return $oQb->getQuery()->getArrayResult();
	}
}


<?php
/**
 * LoadAUStates
 */
namespace Delatbabel\Keylists\Console\Commands;

use Delatbabel\Keylists\Models\Keytype;
use Delatbabel\Keylists\Models\Keyvalue;
use Illuminate\Console\Command;

/*
 * LoadAUStates
 *
 * This is an example of a script to load data into the keytypes/keyvalues
 * tables from a CSV file.  The CSV file used in this case contains a list
 * of Australian states and territories.
 */
class LoadAUStates extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'keylists:loadaustates';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Load Australian States';

    /**
     * Filename to load from
     *
     * @var string
     */
    protected $filename = __DIR__ . '/../../../database/data/AUSstates.csv';

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        /** @var Keytype $keytype */
        $keytype = Keytype::create([
            'name'          => 'states.au',
            'description'   => 'Australian States',
            'created_by'    => 'loader',
            'updated_by'    => 'loader',
        ]);

        $handle = fopen($this->filename, 'r');

        // Get this in advance by calling wc -l $file
        $count = 8;
        $bar   = $this->output->createProgressBar($count);

        while ($data = fgetcsv($handle)) {
            try {
                list($country, $state_code, $state_name) = $data;
            } catch (\Exception $e) {
                continue;
            }

            // progress advance
            $bar->advance();

            // Create the entry
            Keyvalue::create([
                'keytype_id'    => $keytype->id,
                'keyvalue'      => $state_code,
                'keyname'       => $state_name,
                'created_by'    => 'loader',
                'updated_by'    => 'loader',
            ]);
        }

        fclose($handle);
        $bar->finish();

        $this->output->writeln('Finished');
    }
}


<?php

namespace Reaction\I18n;

use Reaction\Exceptions\Exception;
use Reaction\Exceptions\InvalidConfigException;

/**
 * GettextMoFile represents an MO Gettext message file.
 *
 * This class is written by adapting Michael's Gettext_MO class in PEAR.
 * Please refer to the following license terms.
 *
 * Copyright (c) 2004-2005, Michael Wallner <mike@iworks.at>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
class GettextMoFile extends GettextFile
{
    /**
     * @var bool whether to use big-endian when reading and writing an integer.
     */
    public $useBigEndian = false;

    /**
     * Loads messages from an MO file.
     * @param string $context message context
     * @return array message translations. Array keys are source messages and array values are translated messages:
     * source message => translated message.
     * @param string|null $filePath file path
     * @throws Exception if unable to read the MO file
     */
    public function load($context, $filePath = null)
    {
        if (isset($filePath)) {
            $this->filePath = $filePath;
        }
        if (!isset($this->_messages)) {
            $this->loadAll();
        }
        return isset($this->_messages[$context]) ? $this->_messages[$context] : [];
    }

    /**
     * Saves messages to an MO file.
     * @param array       $messages message translations. Array keys are source messages and array values are
     * translated messages: source message => translated message. Note if the message has a context,
     * the message ID must be prefixed with the context with chr(4) as the separator.
     * @param string|null $filePath file path
     * @throws Exception if unable to save the MO file
     */
    public function save($messages, $filePath = null)
    {
        if (false === ($fileHandle = @fopen($filePath, 'wb'))) {
            throw new Exception('Unable to write file "' . $filePath . '".');
        }
        if (false === @flock($fileHandle, LOCK_EX)) {
            throw new Exception('Unable to lock file "' . $filePath . '" for reading.');
        }

        // magic
        if ($this->useBigEndian) {
            $this->writeBytes($fileHandle, pack('c*', 0x95, 0x04, 0x12, 0xde)); // -107
        } else {
            $this->writeBytes($fileHandle, pack('c*', 0xde, 0x12, 0x04, 0x95)); // -34
        }

        // revision
        $this->writeInteger($fileHandle, 0);

        // message count
        $messageCount = count($messages);
        $this->writeInteger($fileHandle, $messageCount);

        // offset of source message table
        $offset = 28;
        $this->writeInteger($fileHandle, $offset);
        $offset += $messageCount * 8;
        $this->writeInteger($fileHandle, $offset);

        // hashtable size, omitted
        $this->writeInteger($fileHandle, 0);
        $offset += $messageCount * 8;
        $this->writeInteger($fileHandle, $offset);

        // length and offsets for source messages
        foreach (array_keys($messages) as $id) {
            $length = strlen($id);
            $this->writeInteger($fileHandle, $length);
            $this->writeInteger($fileHandle, $offset);
            $offset += $length + 1;
        }

        // length and offsets for target messages
        foreach ($messages as $message) {
            $length = strlen($message);
            $this->writeInteger($fileHandle, $length);
            $this->writeInteger($fileHandle, $offset);
            $offset += $length + 1;
        }

        // source messages
        foreach (array_keys($messages) as $id) {
            $this->writeString($fileHandle, $id);
        }

        // target messages
        foreach ($messages as $message) {
            $this->writeString($fileHandle, $message);
        }

        @flock($fileHandle, LOCK_UN);
        @fclose($fileHandle);
    }

    /**
     * Get all messages from file
     * @return array
     * @throws Exception
     */
    protected function loadAll()
    {
        if (!isset($this->filePath)) {
            throw new InvalidConfigException("Not specified '\$filePath' parameter");
        }
        if (false === ($fileHandle = @fopen($this->filePath, 'rb'))) {
            throw new Exception('Unable to read file "' . $this->filePath . '".');
        }
        if (false === @flock($fileHandle, LOCK_SH)) {
            throw new Exception('Unable to lock file "' . $this->filePath . '" for reading.');
        }

        // magic
        $array = unpack('c', $this->readBytes($fileHandle, 4));
        $magic = current($array);
        if ($magic == -34) {
            $this->useBigEndian = false;
        } elseif ($magic == -107) {
            $this->useBigEndian = true;
        } else {
            throw new Exception('Invalid MO file: ' . $this->filePath . ' (magic: ' . $magic . ').');
        }

        // revision
        $revision = $this->readInteger($fileHandle);
        if ($revision !== 0) {
            throw new Exception('Invalid MO file revision: ' . $revision . '.');
        }

        $count = $this->readInteger($fileHandle);
        $sourceOffset = $this->readInteger($fileHandle);
        $targetOffset = $this->readInteger($fileHandle);

        $sourceLengths = [];
        $sourceOffsets = [];
        fseek($fileHandle, $sourceOffset);
        for ($i = 0; $i < $count; ++$i) {
            $sourceLengths[] = $this->readInteger($fileHandle);
            $sourceOffsets[] = $this->readInteger($fileHandle);
        }

        $targetLengths = [];
        $targetOffsets = [];
        fseek($fileHandle, $targetOffset);
        for ($i = 0; $i < $count; ++$i) {
            $targetLengths[] = $this->readInteger($fileHandle);
            $targetOffsets[] = $this->readInteger($fileHandle);
        }

        for ($i = 0; $i < $count; ++$i) {
            $id = $this->readString($fileHandle, $sourceLengths[$i], $sourceOffsets[$i]);
            $separatorPosition = strpos($id, chr(4));

            if ($separatorPosition !== false) {
                $context = substr($id, 0, $separatorPosition);
                $id = substr($id, $separatorPosition + 1);
            }
            //Use new context or previously detected but avoid null
            if (isset($context)) {
                $message = $this->readString($fileHandle, $targetLengths[$i], $targetOffsets[$i]);
                $this->_messages[$context][$id] = $message;
            }
        }

        @flock($fileHandle, LOCK_UN);
        @fclose($fileHandle);

        return $this->_messages;
    }

    /**
     * Reads one or several bytes.
     * @param resource $fileHandle to read from
     * @param int $byteCount to be read
     * @return string bytes
     */
    protected function readBytes($fileHandle, $byteCount = 1)
    {
        if ($byteCount > 0) {
            return fread($fileHandle, $byteCount);
        }

        return null;
    }

    /**
     * Write bytes.
     * @param resource $fileHandle to write to
     * @param string $bytes to be written
     * @return int how many bytes are written
     */
    protected function writeBytes($fileHandle, $bytes)
    {
        return fwrite($fileHandle, $bytes);
    }

    /**
     * Reads a 4-byte integer.
     * @param resource $fileHandle to read from
     * @return int the result
     */
    protected function readInteger($fileHandle)
    {
        $array = unpack($this->useBigEndian ? 'N' : 'V', $this->readBytes($fileHandle, 4));

        return current($array);
    }

    /**
     * Writes a 4-byte integer.
     * @param resource $fileHandle to write to
     * @param int $integer to be written
     * @return int how many bytes are written
     */
    protected function writeInteger($fileHandle, $integer)
    {
        return $this->writeBytes($fileHandle, pack($this->useBigEndian ? 'N' : 'V', (int)$integer));
    }

    /**
     * Reads a string.
     * @param resource $fileHandle file handle
     * @param int $length of the string
     * @param int $offset of the string in the file. If null, it reads from the current position.
     * @return string the result
     */
    protected function readString($fileHandle, $length, $offset = null)
    {
        if ($offset !== null) {
            fseek($fileHandle, $offset);
        }

        return $this->readBytes($fileHandle, $length);
    }

    /**
     * Writes a string.
     * @param resource $fileHandle to write to
     * @param string $string to be written
     * @return int how many bytes are written
     */
    protected function writeString($fileHandle, $string)
    {
        return $this->writeBytes($fileHandle, $string . "\0");
    }
}


<?php

/**
 * Jin Framework
 * Diatem
 */

namespace Jin2\Utils;

use Jin2\Utils\StringTools;
use Jin2\Utils\ArrayTools;

/**
 * Gestion de liste de valeurs (inspiré des méthodes de list de coldfusion)
 */
class ListTools
{

  /**
   * Modifie les séparateurs d'une liste
   *
   * @param  string $list          Liste à modifier
   * @param  string $oldDelimiter  Ancien séparateur
   * @param  string $newDelimiter  Nouveau séparateur
   * @return string
   */
  public static function changeDelims($list, $oldDelimiter, $newDelimiter)
  {
    return StringTools::replaceAll($list, $oldDelimiter, $newDelimiter);
  }

  /**
   * Ajoute un élément à la fin d'une liste
   *
   * @param  string $list       Liste à modifier
   * @param  string $value      Valeur à ajouter
   * @param  string $delimiter  Séparateur
   * @return string
   */
  public static function append($list, $value, $delimiter = ',')
  {
    if ($list == '') {
        $list = $value;
    } else {
        $list .= $delimiter.$value;
    }
    return $list;
  }

  /**
   * Recherche la position d'une valeur dans la liste
   *
   * @param  string $list       Liste source
   * @param  string $value      Valeur à rechercher
   * @param  string $delimiter  Séparateur
   * @return int|boolean        Position dans la liste
   */
  public static function find($list, $value, $delimiter = ',')
  {
    return ArrayTools::find(static::toArray($list, $delimiter), $value);
  }

  /**
   * Recherche la position d'une valeur dans la liste (en ne prenant pas en compte la casse)
   *
   * @param  string $list       Liste source
   * @param  string $value      Valeur à rechercher
   * @param  string $delimiter  Séparateur
   * @return int|boolean        Position dans la liste
   */
  public static function findNoCase($list, $value, $delimiter = ',')
  {
    return ArrayTools::findNoCase(static::toArray($list, $delimiter), $value);
  }

  /**
   * Définit si une liste contient une valeur donnée
   *
   * @param  string $list       Liste source
   * @param  string $value      Valeur à rechercher
   * @param  string $delimiter  Séparateur
   * @return boolean
   */
  public static function contains($list, $value, $delimiter = ',')
  {
    return is_numeric(ArrayTools::find(static::toArray($list, $delimiter), $value));
  }

  /**
   * Définit si une liste contient une valeur donnée (en ne tenant pas compte de la casse)
   *
   * @param  string $list       Liste source
   * @param  string $value      Valeur à rechercher
   * @param  string $delimiter  Séparateur
   * @return boolean
   */
  public static function containsNoCase($list, $value, $delimiter = ',')
  {
    return is_numeric(ArrayTools::findNoCase(static::toArray($list, $delimiter), $value));
  }

  /**
   * Supprime une valeur de la liste à une position donnée
   *
   * @param  string $list       Liste source
   * @param  int    $index      Index
   * @param  string $delimiter  Séparateur
   * @return string
   */
  public static function deleteAt($list, $index, $delimiter = ',')
  {
    return ArrayTools::deleteAt(static::toArray($list, $delimiter), $index);
  }

  /**
   * Retourne le premier élément de la liste
   *
   * @param  string $list       Liste source
   * @param  string $delimiter  Séparateur
   * @return string
   */
  public static function first($list, $delimiter = ',')
  {
    $array = static::toArray($list, $delimiter);
    return $array[0];
  }

   /**
   * Retourne le dernier élément de la liste
   *
   * @param  string $list       Liste source
   * @param  string $delimiter  Séparateur
   * @return string
   */
  public static function last($list, $delimiter = ',')
  {
    $array = static::toArray($list, $delimiter);
    if (isset($array[count($array) - 1])) {
      return $array[count($array) - 1];
    }
    return null;
  }

  /**
   * Retourne un élément de la liste, à la position donnée
   *
   * @param  string $list       Liste source
   * @param  int $index         Position
   * @param  string $delimiter  Séparateur
   * @return string
   */
  public static function ListGetAt($list, $index, $delimiter = ',')
  {
    $array = static::toArray($list, $delimiter);
    return $array[$index];
  }

  /**
   * Insère un élément dans la liste, à une position donnée
   *
   * @param  string $list       Liste source
   * @param  int    $index      Position
   * @param  string $value      Valeur
   * @param  string $delimiter  Séparateur
   * @return string
   */
  public static function ListInsertAt($list, $index, $value, $delimiter = ',')
  {
    $array = static::toArray($list, $delimiter);
    $array = ArrayTools::insertAt($array, $index, $value);
    return ArrayTools::toList($array, $delimiter);
  }

  /**
   * Retourne le nombre d'éléments d'une liste
   *
   * @param  string $list       Liste source
   * @param  string $delimiter  Séparateur
   * @return int
   */
  public static function len($list, $delimiter = ',')
  {
    $array = static::toArray($list, $delimiter);
    return count($array);
  }

  /**
   * Ajoute un élément en tête de liste
   * @param  string $list       Liste source
   * @param  string $value      Valeur à ajouter
   * @param  string $delimiter  Séparateur
   * @return string
   */
  public static function prepend($list, $value, $delimiter = ',')
  {
    $list = $value . $delimiter . $list;
     return $list;
  }

  /**
   * Redéfinit la valeur d'un des éléments d'une liste
   *
   * @param  string $list       Liste source
   * @param  int    $index      Position
   * @param  string $value      Valeur
   * @param  string $delimiter  Séparateur
   * @return string
   */
  public static function setAt($list, $index, $value, $delimiter = ',')
  {
    $array = static::toArray($list, $delimiter);
    $array[$index] = $value;
    return ArrayTools::toList($array, $delimiter);
  }

  /**
   * Convertit une liste en tableau
   *
   * @param  string $list       Liste source
   * @param  string $delimiter  Séparateur
   * @return string
   */
  public static function toArray($list, $delimiter = ',')
  {
    if ($list == '') {
      return array();
    } else {
      return StringTools::explode($list, $delimiter);
    }
  }

}


<?php

namespace Reaction\Db\Orm;
use function Reaction\Promise\all;
use function Reaction\Promise\resolve;

/**
 * ActiveQueryTrait implements the common methods and properties for active record query classes.
 */
trait ActiveQueryTrait
{
    /**
     * @var string the name of the ActiveRecord class.
     */
    public $modelClass;
    /**
     * @var array a list of relations that this query should be performed with
     */
    public $with;
    /**
     * @var bool whether to return each record as an array. If false (default), an object
     * of [[modelClass]] will be created to represent each record.
     */
    public $asArray;

    /**
     * Sets the [[asArray]] property.
     * @param bool $value whether to return the query results in terms of arrays instead of Active Records.
     * @return $this the query object itself
     */
    public function asArray($value = true)
    {
        $this->asArray = $value;
        return $this;
    }

    /**
     * Specifies the relations with which this query should be performed.
     *
     * The parameters to this method can be either one or multiple strings, or a single array
     * of relation names and the optional callbacks to customize the relations.
     *
     * A relation name can refer to a relation defined in [[modelClass]]
     * or a sub-relation that stands for a relation of a related record.
     * For example, `orders.address` means the `address` relation defined
     * in the model class corresponding to the `orders` relation.
     *
     * The following are some usage examples:
     *
     * ```php
     * // find customers together with their orders and country
     * Customer::find()->with('orders', 'country')->all();
     * // find customers together with their orders and the orders' shipping address
     * Customer::find()->with('orders.address')->all();
     * // find customers together with their country and orders of status 1
     * Customer::find()->with([
     *     'orders' => function (\yii\db\ActiveQuery $query) {
     *         $query->andWhere('status = 1');
     *     },
     *     'country',
     * ])->all();
     * ```
     *
     * You can call `with()` multiple times. Each call will add relations to the existing ones.
     * For example, the following two statements are equivalent:
     *
     * ```php
     * Customer::find()->with('orders', 'country')->all();
     * Customer::find()->with('orders')->with('country')->all();
     * ```
     *
     * @return $this the query object itself
     */
    public function with()
    {
        $with = func_get_args();
        if (isset($with[0]) && is_array($with[0])) {
            // the parameter is given as an array
            $with = $with[0];
        }

        if (empty($this->with)) {
            $this->with = $with;
        } elseif (!empty($with)) {
            foreach ($with as $name => $value) {
                if (is_int($name)) {
                    // repeating relation is fine as normalizeRelations() handle it well
                    $this->with[] = $value;
                } else {
                    $this->with[$name] = $value;
                }
            }
        }

        return $this;
    }

    /**
     * Converts found rows into model instances.
     * @param array $rows
     * @return array|ActiveRecordInterface[]
     */
    protected function createModels($rows)
    {
        if ($this->asArray) {
            return $rows;
        } else {
            $models = [];
            /* @var $class ActiveRecordInterface */
            $class = $this->modelClass;
            foreach ($rows as $row) {
                $model = $class::instantiate($row);
                $modelClass = get_class($model);
                $modelClass::populateRecord($model, $row);
                $models[] = $model;
            }
            return $models;
        }
    }

    /**
     * Finds records corresponding to one or multiple relations and populates them into the primary models.
     * @param array                         $with a list of relations that this query should be performed with. Please
     * refer to [[with()]] for details about specifying this parameter.
     * @param array|ActiveRecordInterface[] $models the primary models (can be either AR instances or arrays)
     * @return \Reaction\Promise\ExtendedPromiseInterface
     */
    public function findWith($with, &$models)
    {
        $primaryModel = reset($models);
        if (!$primaryModel instanceof ActiveRecordInterface) {
            /* @var $modelClass ActiveRecordInterface */
            $modelClass = $this->modelClass;
            $primaryModel = $modelClass::instance();
        }
        $relations = $this->normalizeRelations($primaryModel, $with);
        /* @var $relation ActiveQuery */
        $promises = [];
        foreach ($relations as $name => $relation) {
            if ($relation->asArray === null) {
                // inherit asArray from primary query
                $relation->asArray($this->asArray);
            }
            $promises[] = $relation->populateRelation($name, $models);
        }
        return !empty($promises) ? all($promises) : resolve(true);
    }

    /**
     * @param ActiveRecordInterface $model
     * @param array $with
     * @return ActiveQueryInterface[]
     */
    private function normalizeRelations($model, $with)
    {
        $relations = [];
        foreach ($with as $name => $callback) {
            if (is_int($name)) {
                $name = $callback;
                $callback = null;
            }
            if (($pos = strpos($name, '.')) !== false) {
                // with sub-relations
                $childName = substr($name, $pos + 1);
                $name = substr($name, 0, $pos);
            } else {
                $childName = null;
            }

            if (!isset($relations[$name])) {
                $relation = $model->getRelation($name);
                $relation->primaryModel = null;
                $relations[$name] = $relation;
            } else {
                $relation = $relations[$name];
            }

            if (isset($childName)) {
                $relation->with[$childName] = $callback;
            } elseif ($callback !== null) {
                call_user_func($callback, $relation);
            }
        }

        return $relations;
    }
}


<?php

namespace Think\Crypt\Driver;

/**
 * Base64 加密实现类
 */
class Base64
{

    /**
     * 加密字符串
     * @param string $data 字符串
     * @param string $key 加密key
     * @param integer $expire 有效期（秒）
     * @return string
     */
    public static function encrypt($data, $key, $expire = 0)
    {
        $expire = sprintf('%010d', $expire ? $expire + time() : 0);
        $key = md5($key);
        $data = base64_encode($expire.$data);
        $x = 0;
        $len = strlen($data);
        $l = strlen($key);
        $char = $str = '';
        for ($i = 0; $i < $len; $i++) {
            if ($x == $l) {
                $x = 0;
            }

            $char .= substr($key, $x, 1);
            $x++;
        }

        for ($i = 0; $i < $len; $i++) {
            $str .= chr(ord(substr($data, $i, 1)) + (ord(substr($char, $i, 1))) % 256);
        }

        return $str;
    }

    /**
     * 解密字符串
     * @param string $data 字符串
     * @param string $key 加密key
     * @return string
     */
    public static function decrypt($data, $key)
    {
        $key = md5($key);
        $x = 0;
        $len = strlen($data);
        $l = strlen($key);
        $char = $str = '';
        for ($i = 0; $i < $len; $i++) {
            if ($x == $l) {
                $x = 0;
            }

            $char .= substr($key, $x, 1);
            $x++;
        }
        for ($i = 0; $i < $len; $i++) {
            if (ord(substr($data, $i, 1)) < ord(substr($char, $i, 1))) {
                $str .= chr((ord(substr($data, $i, 1)) + 256) - ord(substr($char, $i, 1)));
            } else {
                $str .= chr(ord(substr($data, $i, 1)) - ord(substr($char, $i, 1)));
            }
        }
        $data = base64_decode($str);
        $expire = substr($data, 0, 10);
        if ($expire > 0 && $expire < time()) {
            return '';
        }
        $data = substr($data, 10);

        return $data;
    }
}


<?php

namespace Reaction\Db;

use Reaction\Exceptions\InvalidCallException;

/**
 * DataReader represents a forward-only stream of rows from a query result set.
 *
 * To read the current row of data, call [[read()]]. The method [[readAll()]]
 * returns all the rows in a single array. Rows of data can also be read by
 * iterating through the reader. For example,
 *
 * ```php
 * $command = $connection->createCommand('SELECT * FROM post');
 * $reader = $command->query();
 *
 * while ($row = $reader->read()) {
 *     $rows[] = $row;
 * }
 *
 * // equivalent to:
 * foreach ($reader as $row) {
 *     $rows[] = $row;
 * }
 *
 * // equivalent to:
 * $rows = $reader->readAll();
 * ```
 *
 * Note that since DataReader is a forward-only stream, you can only traverse it once.
 * Doing it the second time will throw an exception.
 *
 * It is possible to use a specific mode of data fetching by setting
 * [[fetchMode]]. See the [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
 * for more details about possible fetch mode.
 *
 * @property int $columnCount The number of columns in the result set. This property is read-only.
 * @property int $fetchMode Fetch mode. This property is write-only.
 * @property bool $isClosed Whether the reader is closed or not. This property is read-only.
 * @property int $rowCount Number of rows contained in the result. This property is read-only.
 */
class DataReader extends \Reaction\Base\BaseObject implements \Iterator, \Countable
{
    private $_closed = false;
    private $_row;
    private $_index = -1;
    private $_results;
    private $_command;
    private $_fetchMode;

    /**
     * Constructor.
     * @param CommandInterface $command
     * @param array            $results Associative array of query execution results
     * @param array            $config name-value pairs that will be used to initialize the object properties
     */
    public function __construct(CommandInterface $command, $results = [], $config = [])
    {
        $this->_results = $results;
        $this->_command = $command;
        $this->fetchMode = CommandInterface::FETCH_MODE_ASSOC;
        $this->rewind();
        parent::__construct($config);
    }

    /**
     * Set the default fetch mode for this statement.
     *
     * @param string $mode fetch mode
     * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php
     */
    public function setFetchMode($mode)
    {
        $this->_fetchMode = $mode;
    }

    /**
     * Advances the reader to the next row in a result set.
     * @return array|false the current row, false if no more row available
     */
    public function read()
    {
        $this->next();
        if (!$this->valid()) {
            return false;
        }
        /** @var array|false $fetched */
        $fetched = $this->_command->fetchResultsRow($this->_row, CommandInterface::FETCH_ROW, $this->_fetchMode);
        return $fetched;
    }

    /**
     * Returns a single column from the next row of a result set.
     * @param int $columnIndex zero-based column index
     * @return mixed the column of the current row, false if no more rows available
     */
    public function readColumn($columnIndex)
    {
        $this->next();
        if (!$this->valid()) {
            return false;
        }
        return $this->_command->fetchResultsRow($this->_row, CommandInterface::FETCH_FIELD, CommandInterface::FETCH_MODE_ASSOC, $columnIndex);
    }

    /**
     * Returns an object populated with the next row of data.
     * @param string $className class name of the object to be created and populated
     * @param array $fields Elements of this array are passed to the constructor
     * @return mixed the populated object, false if no more row of data available
     */
    public function readObject($className = 'stdClass', $fields = [])
    {
        $this->next();
        if (!$this->valid()) {
            return false;
        }
        $obj = new $className($fields);
        foreach ($this->_row as $key => $value) {
            $obj->{$key} = $value;
        }
        return $obj;
    }

    /**
     * Reads the whole result set into an array.
     * @return array the result set (each array element represents a row of data).
     * An empty array will be returned if the result contains no row.
     */
    public function readAll()
    {
        /** @var array $fetched */
        $fetched = $this->_command->fetchResults($this->_results, CommandInterface::FETCH_ALL, $this->_fetchMode);
        return $fetched;
    }

    /**
     * Closes the reader.
     * This frees up the resources allocated for executing this SQL statement.
     * Read attempts after this method call are unpredictable.
     */
    public function close()
    {
        $this->_closed = true;
    }

    /**
     * whether the reader is closed or not.
     * @return bool whether the reader is closed or not.
     */
    public function getIsClosed()
    {
        return $this->_closed;
    }

    /**
     * Returns the number of rows in the result set.
     * Note, most DBMS may not give a meaningful count.
     * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows.
     * @return int number of rows contained in the result.
     */
    public function getRowCount()
    {
        return !empty($this->_results) ? count($this->_results) : 0;
    }

    /**
     * Returns the number of rows in the result set.
     * This method is required by the Countable interface.
     * Note, most DBMS may not give a meaningful count.
     * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows.
     * @return int number of rows contained in the result.
     */
    public function count()
    {
        return $this->getRowCount();
    }

    /**
     * Returns the number of columns in the result set.
     * Note, even there's no row in the reader, this still gives correct column number.
     * @return int the number of columns in the result set.
     */
    public function getColumnCount()
    {
        if (empty($this->_results)) {
            return 0;
        }
        $results = $this->_results;
        $firstRow = reset($results);
        return count($firstRow);
    }

    /**
     * Resets the iterator to the initial state.
     * This method is required by the interface [[\Iterator]].
     * @throws InvalidCallException if this method is invoked twice
     */
    public function rewind()
    {
        $this->_index = 0;
        $this->_row = reset($this->_results);
    }

    /**
     * Returns the index of the current row.
     * This method is required by the interface [[\Iterator]].
     * @return int the index of the current row.
     */
    public function key()
    {
        return $this->_index;
    }

    /**
     * Returns the current row.
     * This method is required by the interface [[\Iterator]].
     * @return mixed the current row.
     */
    public function current()
    {
        return is_array($this->_row) ? $this->_command->fetchResultsRow($this->_row, CommandInterface::FETCH_ROW, $this->_fetchMode) : $this->_row;
    }

    /**
     * Moves the internal pointer to the next row.
     * This method is required by the interface [[\Iterator]].
     */
    public function next()
    {
        if ($this->_index === -1) {
            $this->_row = reset($this->_results);
        } else {
            $this->_row = next($this->_results);
        }
        $this->_index++;
    }

    /**
     * Returns whether there is a row of data at current position.
     * This method is required by the interface [[\Iterator]].
     * @return bool whether there is a row of data at current position.
     */
    public function valid()
    {
        return $this->_row !== false;
    }
}


<?php

namespace Reaction\Db\Pgsql;

use Reaction\Db\CommandInterface;
use Reaction\Db\ConnectionInterface;
use Reaction\Db\Constraints\CheckConstraint;
use Reaction\Db\Constraints\Constraint;
use Reaction\Db\Constraints\ConstraintFinderInterface;
use Reaction\Db\Constraints\ConstraintFinderTrait;
use Reaction\Db\Constraints\ForeignKeyConstraint;
use Reaction\Db\Constraints\IndexConstraint;
use Reaction\Db\Expressions\Expression;
use Reaction\Db\TableSchema;
use Reaction\Db\ViewFinderTrait;
use Reaction\Exceptions\NotSupportedException;
use Reaction\Helpers\ArrayHelper;
use Reaction\Promise\ExtendedPromiseInterface;
use function Reaction\Promise\reject;
use function Reaction\Promise\resolve;

/**
 * Class Schema
 * @package Reaction\Db\Pgsql
 */
class Schema extends \Reaction\Db\Schema implements ConstraintFinderInterface
{
    const TYPE_JSONB = 'jsonb';

    use ViewFinderTrait;
    use ConstraintFinderTrait;

    /**
     * @var string the default schema used for the current session.
     */
    public $defaultSchema = 'public';
    /**
     * @var array Table metadata type those are implemented by driver
     */
    public $tableMetaImplemented = [
        self::META_SCHEMA,
        self::META_PK,
        self::META_FK,
        self::META_INDEXES,
        self::META_UNIQUES,
        self::META_CHECKS,
    ];

    /**
     * @var array mapping from physical column types (keys) to abstract
     * column types (values)
     * @see http://www.postgresql.org/docs/current/static/datatype.html#DATATYPE-TABLE
     */
    public $typeMap = [
        'bit' => self::TYPE_INTEGER,
        'bit varying' => self::TYPE_INTEGER,
        'varbit' => self::TYPE_INTEGER,

        'bool' => self::TYPE_BOOLEAN,
        'boolean' => self::TYPE_BOOLEAN,

        'box' => self::TYPE_STRING,
        'circle' => self::TYPE_STRING,
        'point' => self::TYPE_STRING,
        'line' => self::TYPE_STRING,
        'lseg' => self::TYPE_STRING,
        'polygon' => self::TYPE_STRING,
        'path' => self::TYPE_STRING,

        'character' => self::TYPE_CHAR,
        'char' => self::TYPE_CHAR,
        'bpchar' => self::TYPE_CHAR,
        'character varying' => self::TYPE_STRING,
        'varchar' => self::TYPE_STRING,
        'text' => self::TYPE_TEXT,

        'bytea' => self::TYPE_BINARY,

        'cidr' => self::TYPE_STRING,
        'inet' => self::TYPE_STRING,
        'macaddr' => self::TYPE_STRING,

        'real' => self::TYPE_FLOAT,
        'float4' => self::TYPE_FLOAT,
        'double precision' => self::TYPE_DOUBLE,
        'float8' => self::TYPE_DOUBLE,
        'decimal' => self::TYPE_DECIMAL,
        'numeric' => self::TYPE_DECIMAL,

        'money' => self::TYPE_MONEY,

        'smallint' => self::TYPE_SMALLINT,
        'int2' => self::TYPE_SMALLINT,
        'int4' => self::TYPE_INTEGER,
        'int' => self::TYPE_INTEGER,
        'integer' => self::TYPE_INTEGER,
        'bigint' => self::TYPE_BIGINT,
        'int8' => self::TYPE_BIGINT,
        'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!

        'smallserial' => self::TYPE_SMALLINT,
        'serial2' => self::TYPE_SMALLINT,
        'serial4' => self::TYPE_INTEGER,
        'serial' => self::TYPE_INTEGER,
        'bigserial' => self::TYPE_BIGINT,
        'serial8' => self::TYPE_BIGINT,
        'pg_lsn' => self::TYPE_BIGINT,

        'date' => self::TYPE_DATE,
        'interval' => self::TYPE_STRING,
        'time without time zone' => self::TYPE_TIME,
        'time' => self::TYPE_TIME,
        'time with time zone' => self::TYPE_TIME,
        'timetz' => self::TYPE_TIME,
        'timestamp without time zone' => self::TYPE_TIMESTAMP,
        'timestamp' => self::TYPE_TIMESTAMP,
        'timestamp with time zone' => self::TYPE_TIMESTAMP,
        'timestamptz' => self::TYPE_TIMESTAMP,
        'abstime' => self::TYPE_TIMESTAMP,

        'tsquery' => self::TYPE_STRING,
        'tsvector' => self::TYPE_STRING,
        'txid_snapshot' => self::TYPE_STRING,

        'unknown' => self::TYPE_STRING,

        'uuid' => self::TYPE_STRING,
        'json' => self::TYPE_JSON,
        'jsonb' => self::TYPE_JSON,
        'xml' => self::TYPE_STRING,
    ];

    /**
     * {@inheritdoc}
     */
    protected $tableQuoteCharacter = '"';

    /**
     * Returns a server version as a string comparable by [[\version_compare()]].
     * @return string server version as a string.
     */
    public function getServerVersion()
    {
        if ($this->_serverVersion === null) {
            return $this->getServerVersionPromised();
        }
        return $this->_serverVersion;
    }

    /**
     * Executes the INSERT command, returning primary key values.
     * @param string                   $table the table that new rows will be inserted into.
     * @param array                    $columns the column data (name => value) to be inserted into the table.
     * @param ConnectionInterface|null $connection
     * @return ExtendedPromiseInterface with array|false primary key values or false if the command fails
     */
    public function insert($table, $columns, $connection = null)
    {
        $command = $this->db->createCommand()->insertAndReturn($table, $columns);
        if (isset($connection)) {
            $command->setConnection($connection);
        }
        return $command->queryOne();
    }

    /**
     * {@inheritdoc}
     */
    protected function resolveTableName($name)
    {
        $resolvedName = new TableSchema();
        $parts = explode('.', str_replace('"', '', $name));
        if (isset($parts[1])) {
            $resolvedName->schemaName = $parts[0];
            $resolvedName->name = $parts[1];
        } else {
            $resolvedName->schemaName = $this->defaultSchema;
            $resolvedName->name = $name;
        }
        $resolvedName->fullName = ($resolvedName->schemaName !== $this->defaultSchema ? $resolvedName->schemaName . '.' : '') . $resolvedName->name;
        return $resolvedName;
    }

    /**
     * {@inheritdoc}
     */
    protected function findSchemaNames()
    {
        static $sql = <<<'SQL'
SELECT "ns"."nspname"
FROM "pg_namespace" AS "ns"
WHERE "ns"."nspname" != 'information_schema' AND "ns"."nspname" NOT LIKE 'pg_%'
ORDER BY "ns"."nspname" ASC
SQL;

        return $this->db->createCommand($sql)->queryColumn();
    }

    /**
     * {@inheritdoc}
     */
    protected function findTableNames($schema = '')
    {
        if ($schema === '') {
            $schema = $this->defaultSchema;
        }
        $sql = <<<'SQL'
SELECT c.relname AS table_name
FROM pg_class c
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
WHERE ns.nspname = :schemaName AND c.relkind IN ('r','v','m','f')
ORDER BY c.relname
SQL;
        return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();
    }

    /**
     * {@inheritdoc}
     */
    protected function loadTableSchema($name)
    {
        $table = new TableSchema();
        $this->resolveTableNames($table, $name);
        return $this->findColumns($table)->then(
            function() use (&$table) {
                return $this->findConstraints($table);
            }
        )->then(
            function() use (&$table) {
                return $table;
            }
        );
    }

    /**
     * Resolves the table name and schema name (if any).
     * @param TableSchema $table the table metadata object
     * @param string $name the table name
     */
    protected function resolveTableNames($table, $name)
    {
        $parts = explode('.', str_replace('"', '', $name));

        if (isset($parts[1])) {
            $table->schemaName = $parts[0];
            $table->name = $parts[1];
        } else {
            $table->schemaName = $this->defaultSchema;
            $table->name = $parts[0];
        }

        $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name;
    }

    /**
     * Collects the metadata of table columns.
     * @param TableSchema $table the table metadata
     * @return ExtendedPromiseInterface with bool whether the table exists in the database
     */
    protected function findColumns(&$table)
    {
        $tableName = $this->db->quoteValue($table->name);
        $schemaName = $this->db->quoteValue($table->schemaName);
        $sql = <<<SQL
SELECT
    d.nspname AS table_schema,
    c.relname AS table_name,
    a.attname AS column_name,
    COALESCE(td.typname, tb.typname, t.typname) AS data_type,
    COALESCE(td.typtype, tb.typtype, t.typtype) AS type_type,
    a.attlen AS character_maximum_length,
    pg_catalog.col_description(c.oid, a.attnum) AS column_comment,
    a.atttypmod AS modifier,
    a.attnotnull = false AS is_nullable,
    CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default,
    coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc,
    CASE WHEN COALESCE(td.typtype, tb.typtype, t.typtype) = 'e'::char
        THEN array_to_string((SELECT array_agg(enumlabel) FROM pg_enum WHERE enumtypid = COALESCE(td.oid, tb.oid, a.atttypid))::varchar[], ',')
        ELSE NULL
    END AS enum_values,
    CASE atttypid
         WHEN 21 /*int2*/ THEN 16
         WHEN 23 /*int4*/ THEN 32
         WHEN 20 /*int8*/ THEN 64
         WHEN 1700 /*numeric*/ THEN
              CASE WHEN atttypmod = -1
               THEN null
               ELSE ((atttypmod - 4) >> 16) & 65535
               END
         WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/
         WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/
         ELSE null
      END   AS numeric_precision,
      CASE
        WHEN atttypid IN (21, 23, 20) THEN 0
        WHEN atttypid IN (1700) THEN
        CASE
            WHEN atttypmod = -1 THEN null
            ELSE (atttypmod - 4) & 65535
        END
           ELSE null
      END AS numeric_scale,
    CAST(
             information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t))
             AS numeric
    ) AS size,
    a.attnum = any (ct.conkey) as is_pkey,
    COALESCE(NULLIF(a.attndims, 0), NULLIF(t.typndims, 0), (t.typcategory='A')::int) AS dimension
FROM
    pg_class c
    LEFT JOIN pg_attribute a ON a.attrelid = c.oid
    LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
    LEFT JOIN pg_type t ON a.atttypid = t.oid
    LEFT JOIN pg_type tb ON (a.attndims > 0 OR t.typcategory='A') AND t.typelem > 0 AND t.typelem = tb.oid OR t.typbasetype > 0 AND t.typbasetype = tb.oid
    LEFT JOIN pg_type td ON t.typndims > 0 AND t.typbasetype > 0 AND tb.typelem = td.oid
    LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
    LEFT JOIN pg_constraint ct ON ct.conrelid = c.oid AND ct.contype = 'p'
WHERE
    a.attnum > 0 AND t.typname != ''
    AND c.relname = {$tableName}
    AND d.nspname = {$schemaName}
ORDER BY
    a.attnum;
SQL;
        return $this->db->createCommand($sql)->queryAll()->then(
            function($columns) use (&$table) {
                if (empty($columns)) {
                    return reject(false);
                }
                foreach ($columns as $column) {
                    $column = $this->loadColumnSchema($column);
                    $table->columns[$column->name] = $column;
                    if ($column->isPrimaryKey) {
                        $table->primaryKey[] = $column->name;
                        if ($table->sequenceName === null && preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $column->defaultValue) === 1) {
                            $table->sequenceName = preg_replace(['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'], '', $column->defaultValue);
                        }
                        $column->defaultValue = null;
                    } elseif ($column->defaultValue) {
                        if ($column->type === 'timestamp' && $column->defaultValue === 'now()') {
                            $column->defaultValue = new Expression($column->defaultValue);
                        } elseif ($column->type === 'boolean') {
                            $column->defaultValue = ($column->defaultValue === 'true');
                        } elseif (strncasecmp($column->dbType, 'bit', 3) === 0 || strncasecmp($column->dbType, 'varbit', 6) === 0) {
                            $column->defaultValue = bindec(trim($column->defaultValue, 'B\''));
                        } elseif (preg_match("/^'(.*?)'::/", $column->defaultValue, $matches)) {
                            $column->defaultValue = $column->phpTypecast($matches[1]);
                        } elseif (preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $column->defaultValue, $matches)) {
                            if ($matches[2] === 'NULL') {
                                $column->defaultValue = null;
                            } else {
                                $column->defaultValue = $column->phpTypecast($matches[2]);
                            }
                        } else {
                            $column->defaultValue = $column->phpTypecast($column->defaultValue);
                        }
                    }
                }
                return true;
            }
        );
    }

    /**
     * Collects the foreign key column details for the given table.
     * @param TableSchema $table the table metadata
     * @return ExtendedPromiseInterface with TableSchema
     */
    protected function findConstraints(&$table)
    {
        $tableName = $this->quoteValue($table->name);
        $tableSchema = $this->quoteValue($table->schemaName);

        //We need to extract the constraints de hard way since:
        //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us

        $sql = <<<SQL
select
    ct.conname as constraint_name,
    a.attname as column_name,
    fc.relname as foreign_table_name,
    fns.nspname as foreign_table_schema,
    fa.attname as foreign_column_name
from
    (SELECT ct.conname, ct.conrelid, ct.confrelid, ct.conkey, ct.contype, ct.confkey, generate_subscripts(ct.conkey, 1) AS s
       FROM pg_constraint ct
    ) AS ct
    inner join pg_class c on c.oid=ct.conrelid
    inner join pg_namespace ns on c.relnamespace=ns.oid
    inner join pg_attribute a on a.attrelid=ct.conrelid and a.attnum = ct.conkey[ct.s]
    left join pg_class fc on fc.oid=ct.confrelid
    left join pg_namespace fns on fc.relnamespace=fns.oid
    left join pg_attribute fa on fa.attrelid=ct.confrelid and fa.attnum = ct.confkey[ct.s]
where
    ct.contype='f'
    and c.relname={$tableName}
    and ns.nspname={$tableSchema}
order by
    fns.nspname, fc.relname, a.attnum
SQL;

        return $this->db->createCommand($sql)->queryAll()->then(
            function($_constraints) use (&$table) {
                $constraints = [];
                foreach ($_constraints as $constraint) {
                    if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {
                        $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
                    } else {
                        $foreignTable = $constraint['foreign_table_name'];
                    }
                    $name = $constraint['constraint_name'];
                    if (!isset($constraints[$name])) {
                        $constraints[$name] = [
                            'tableName' => $foreignTable,
                            'columns' => [],
                        ];
                    }
                    $constraints[$name]['columns'][$constraint['column_name']] = $constraint['foreign_column_name'];
                }

                foreach ($constraints as $name => $constraint) {
                    $table->foreignKeys[$name] = array_merge([$constraint['tableName']], $constraint['columns']);
                }
                return $table;
            }
        );
    }

    /**
     * Loads the column information into a [[ColumnSchema]] object.
     * @param array $info column information
     * @return ColumnSchema the column schema object
     */
    protected function loadColumnSchema($info)
    {
        /** @var ColumnSchema $column */
        $column = $this->createColumnSchema();
        $column->allowNull = $info['is_nullable'];
        $column->autoIncrement = $info['is_autoinc'];
        $column->comment = $info['column_comment'];
        $column->dbType = $info['data_type'];
        $column->defaultValue = $info['column_default'];
        $column->enumValues = ($info['enum_values'] !== null) ? explode(',', str_replace(["''"], ["'"], $info['enum_values'])) : null;
        $column->unsigned = false; // has no meaning in PG
        $column->isPrimaryKey = $info['is_pkey'];
        $column->name = $info['column_name'];
        $column->precision = $info['numeric_precision'];
        $column->scale = $info['numeric_scale'];
        $column->size = $info['size'] === null ? null : (int)$info['size'];
        $column->dimension = (int)$info['dimension'];
        if (isset($this->typeMap[$column->dbType])) {
            $column->type = $this->typeMap[$column->dbType];
        } else {
            $column->type = self::TYPE_STRING;
        }
        $column->phpType = $this->getColumnPhpType($column);

        return $column;
    }

    /**
     * Loads multiple types of constraints and returns the specified ones.
     * @param string $tableName table name.
     * @param string $returnType return type:
     * - primaryKey
     * - foreignKeys
     * - uniques
     * - checks
     * @return ExtendedPromiseInterface with mixed constraints.
     */
    protected function loadTableConstraints($tableName, $returnType)
    {
        static $sql = <<<'SQL'
SELECT
    "c"."conname" AS "name",
    "a"."attname" AS "column_name",
    "c"."contype" AS "type",
    "ftcns"."nspname" AS "foreign_table_schema",
    "ftc"."relname" AS "foreign_table_name",
    "fa"."attname" AS "foreign_column_name",
    "c"."confupdtype" AS "on_update",
    "c"."confdeltype" AS "on_delete",
    "c"."consrc" AS "check_expr"
FROM "pg_class" AS "tc"
INNER JOIN "pg_namespace" AS "tcns"
    ON "tcns"."oid" = "tc"."relnamespace"
INNER JOIN "pg_constraint" AS "c"
    ON "c"."conrelid" = "tc"."oid"
INNER JOIN "pg_attribute" AS "a"
    ON "a"."attrelid" = "c"."conrelid" AND "a"."attnum" = ANY ("c"."conkey")
LEFT JOIN "pg_class" AS "ftc"
    ON "ftc"."oid" = "c"."confrelid"
LEFT JOIN "pg_namespace" AS "ftcns"
    ON "ftcns"."oid" = "ftc"."relnamespace"
LEFT JOIN "pg_attribute" "fa"
    ON "fa"."attrelid" = "c"."confrelid" AND "fa"."attnum" = ANY ("c"."confkey")
WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableName
ORDER BY "a"."attnum" ASC, "fa"."attnum" ASC
SQL;
        static $actionTypes = [
            'a' => 'NO ACTION',
            'r' => 'RESTRICT',
            'c' => 'CASCADE',
            'n' => 'SET NULL',
            'd' => 'SET DEFAULT',
        ];

        $resolvedName = $this->resolveTableName($tableName);
        return $this->db->createCommand($sql, [
            ':schemaName' => $resolvedName->schemaName,
            ':tableName' => $resolvedName->name,
        ])->queryAll()->then(
            function($constraints) use (&$actionTypes, $tableName, $returnType) {
                $constraints = ArrayHelper::index($constraints, null, ['type', 'name']);
                $result = [
                    'primaryKey' => null,
                    'foreignKeys' => [],
                    'uniques' => [],
                    'checks' => [],
                ];
                foreach ($constraints as $type => $names) {
                    foreach ($names as $name => $constraint) {
                        switch ($type) {
                            case 'p':
                                $result['primaryKey'] = new Constraint([
                                    'name' => $name,
                                    'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
                                ]);
                                break;
                            case 'f':
                                $result['foreignKeys'][] = new ForeignKeyConstraint([
                                    'name' => $name,
                                    'columnNames' => array_keys(array_count_values(ArrayHelper::getColumn($constraint, 'column_name'))),
                                    'foreignSchemaName' => $constraint[0]['foreign_table_schema'],
                                    'foreignTableName' => $constraint[0]['foreign_table_name'],
                                    'foreignColumnNames' => array_keys(array_count_values(ArrayHelper::getColumn($constraint, 'foreign_column_name'))),
                                    'onDelete' => isset($actionTypes[$constraint[0]['on_delete']]) ? $actionTypes[$constraint[0]['on_delete']] : null,
                                    'onUpdate' => isset($actionTypes[$constraint[0]['on_update']]) ? $actionTypes[$constraint[0]['on_update']] : null,
                                ]);
                                break;
                            case 'u':
                                $result['uniques'][] = new Constraint([
                                    'name' => $name,
                                    'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
                                ]);
                                break;
                            case 'c':
                                $result['checks'][] = new CheckConstraint([
                                    'name' => $name,
                                    'columnNames' => ArrayHelper::getColumn($constraint, 'column_name'),
                                    'expression' => $constraint[0]['check_expr'],
                                ]);
                                break;
                        }
                    }
                }
                foreach ($result as $type => $data) {
                    $this->setTableMetadata($tableName, $type, $data);
                }
                return $result[$returnType];
            }
        );
    }

    /**
     * Get server version promised
     * @return ExtendedPromiseInterface
     */
    protected function getServerVersionPromised() {
        $sql = "SELECT VERSION()";
        return $this->db->createCommand($sql)->queryScalar()->then(
            function($result) {
                $version = preg_match('/PostgreSQL\s?([\d\.\w]+)/', $result, $matches) ? $matches[1] : null;
                $this->_serverVersion = $version;
                return $version;
            }
        );
    }

    /**
     * Loads a primary key for the given table.
     * @param string $tableName table name.
     * @return ExtendedPromiseInterface with Constraint|null primary key for the given table, `null` if the table has no primary key.
     */
    protected function loadTablePrimaryKey($tableName)
    {
        return $this->loadTableConstraints($tableName, static::META_PK);
    }

    /**
     * Loads all foreign keys for the given table.
     * @param string $tableName table name.
     * @return ExtendedPromiseInterface with ForeignKeyConstraint[] foreign keys for the given table.
     */
    protected function loadTableForeignKeys($tableName)
    {
        return $this->loadTableConstraints($tableName, static::META_FK);
    }

    /**
     * Loads all indexes for the given table.
     * @param string $tableName table name.
     * @return ExtendedPromiseInterface with IndexConstraint[] indexes for the given table.
     */
    protected function loadTableIndexes($tableName)
    {
        static $sql = <<<'SQL'
SELECT
    "ic"."relname" AS "name",
    "ia"."attname" AS "column_name",
    "i"."indisunique" AS "index_is_unique",
    "i"."indisprimary" AS "index_is_primary"
FROM "pg_class" AS "tc"
INNER JOIN "pg_namespace" AS "tcns"
    ON "tcns"."oid" = "tc"."relnamespace"
INNER JOIN "pg_index" AS "i"
    ON "i"."indrelid" = "tc"."oid"
INNER JOIN "pg_class" AS "ic"
    ON "ic"."oid" = "i"."indexrelid"
INNER JOIN "pg_attribute" AS "ia"
    ON "ia"."attrelid" = "i"."indrelid" AND "ia"."attnum" = ANY ("i"."indkey")
WHERE "tcns"."nspname" = :schemaName AND "tc"."relname" = :tableName
ORDER BY "ia"."attnum" ASC
SQL;

        $resolvedName = $this->resolveTableName($tableName);
        return $this->db->createCommand($sql, [
            ':schemaName' => $resolvedName->schemaName,
            ':tableName' => $resolvedName->name,
        ])->queryAll()->then(
            function($indexes) {
                $indexes = ArrayHelper::index($indexes, null, 'name');
                $result = [];
                foreach ($indexes as $name => $index) {
                    $result[] = new IndexConstraint([
                        'isPrimary' => (bool)$index[0]['index_is_primary'],
                        'isUnique' => (bool)$index[0]['index_is_unique'],
                        'name' => $name,
                        'columnNames' => ArrayHelper::getColumn($index, 'column_name'),
                    ]);
                }

                return $result;
            }
        );
    }

    /**
     * Loads all unique constraints for the given table.
     * @param string $tableName table name.
     * @return ExtendedPromiseInterface with Constraint[] unique constraints for the given table.
     */
    protected function loadTableUniques($tableName)
    {
        return $this->loadTableConstraints($tableName, static::META_UNIQUES);
    }

    /**
     * Loads all check constraints for the given table.
     * @param string $tableName table name.
     * @return ExtendedPromiseInterface with CheckConstraint[] check constraints for the given table.
     */
    protected function loadTableChecks($tableName)
    {
        return $this->loadTableConstraints($tableName, static::META_CHECKS);
    }

    /**
     * Loads all default value constraints for the given table.
     *
     * @param string $tableName table name.
     * @return ExtendedPromiseInterface with DefaultValueConstraint[] default value constraints for the given table.
     * @throws NotSupportedException
     */
    protected function loadTableDefaultValues($tableName)
    {
        throw new NotSupportedException('PostgreSQL does not support default value constraints.');
    }

    /**
     * @inheritdoc
     */
    protected function findViewNames($schema = '')
    {
        if ($schema === '') {
            $schema = $this->defaultSchema;
        }
        $sql = <<<'SQL'
SELECT c.relname AS table_name
FROM pg_class c
INNER JOIN pg_namespace ns ON ns.oid = c.relnamespace
WHERE ns.nspname = :schemaName AND (c.relkind = 'v' OR c.relkind = 'm')
ORDER BY c.relname
SQL;
        return $this->db->createCommand($sql, [':schemaName' => $schema])->queryColumn();
    }
}


<?php

namespace Reaction\Routes;

use FastRoute\Dispatcher;
use FastRoute\RouteParser;
use Reaction;
use Reaction\Helpers\ArrayHelper;
use Reaction\RequestApplicationInterface;

/**
 * Class Router
 * @package Reaction\Routes
 * @property RouteParser $routeParser
 */
class Router extends RouterAbstract implements RouterInterface
{
    public $dispatcherClass = '\FastRoute\simpleDispatcher';
    public $dispatcherOptions = [];
    public $routeParserClass = '\FastRoute\RouteParser\Std';
    public $routeParserOptions = [];

    /**
     * @var RouteParser
     */
    protected $_routeParser;

    /**
     * Search for a given route
     * @param RequestApplicationInterface $app
     * @param string                      $routePath
     * @param string                      $method
     * @param bool                        $withInternal
     * @return array
     */
    public function searchRoute(RequestApplicationInterface $app, $routePath, $method = 'GET', $withInternal = false)
    {
        $dispatcherData = $this->dispatcher->dispatch($method, $routePath);
        if ($dispatcherData[0] === Dispatcher::NOT_FOUND) {
            return [static::ERROR_NOT_FOUND, null, null, []];
        } elseif ($dispatcherData[0] === Dispatcher::METHOD_NOT_ALLOWED) {
            return [static::ERROR_METHOD_NOT_ALLOWED, null, null, []];
        } else {
            $ctrlAndAction = $dispatcherData[1];
            $params = isset($dispatcherData[2]) ? $dispatcherData[2] : [];
            $params = (array)$params;
            return $withInternal || !$dispatcherData[1][0] instanceof ControllerInternalInterface
                ? [static::ERROR_OK, $ctrlAndAction[0], $ctrlAndAction[1], $params]
                : [static::ERROR_NOT_FOUND, null, null, []];
        }
    }

    /**
     * @inheritdoc
     */
    public function initRoutes()
    {
        parent::initRoutes();
        $this->publishRoutes();
    }

    /**
     * Create route dispatcher
     * @param callable|string|array $callback
     * @return mixed
     */
    protected function createDispatcher($callback) {
        $function = $this->dispatcherClass;
        return $function($callback, $this->dispatcherOptions);
    }

    /**
     * Getter for $routeParser
     * @return RouteParser
     * @throws \Reaction\Exceptions\InvalidConfigException
     */
    protected function getRouteParser() {
        if (!isset($this->_routeParser)) {
            $this->_routeParser = Reaction::create($this->routeParserClass, $this->routeParserOptions);
        }
        return $this->_routeParser;
    }

    /**
     * Register all defined routes in dispatcher
     */
    protected function publishRoutes() {
        $routes = $this->routes;
        $this->parseRoutesData();
        $this->dispatcher = $this->createDispatcher(function(\FastRoute\RouteCollector $r) use ($routes) {
            foreach ($routes as $routeRow) {
                $r->addRoute($routeRow['httpMethod'], $routeRow['route'], $routeRow['handler']);
            }
        });
    }

    /**
     * Parse routes data to obtain path expressions, those used for URL building
     */
    protected function parseRoutesData() {
        $routes = $this->routes;
        foreach ($routes as $routeData) {
            $path = $routeData['route'];
            $this->buildPathExpressions($path, true);
        }
        foreach ($this->_routePaths as &$routePaths) {
            $prevCnt = 0;
            ArrayHelper::multisort($routePaths, function($row) use (&$prevCnt) {
                return count($row['params']);
            }, SORT_DESC);
        }
    }

    /**
     * Build path expression from router path
     * @param string $path
     * @param bool $store
     * @return array
     */
    protected function buildPathExpressions($path, $store = false) {
        $segments = $this->routeParser->parse($path);
        $expressions = [];
        foreach ($segments as $segmentGroup) {
            $expression = '';
            $params = [];
            $staticPart = '/';
            $staticPartEnded = false;
            foreach ($segmentGroup as $segment) {
                if (is_string($segment)) {
                    $expression .= $segment;
                    if (!$staticPartEnded) {
                        $staticPart .= $segment;
                    }
                } elseif (is_array($segment)) {
                    $staticPartEnded = true;
                    $expression .= '{' . $segment[0] . '}';
                    $params[] = $segment[0];
                }
            }
            $staticPart = '/' . trim($staticPart, '/');
            if ($store && !empty($params)) {
                $row = [
                    'exp' => $expression,
                    'params' => $params,
                ];
                $this->_routePaths[$staticPart] = isset($this->_routePaths[$staticPart]) ? $this->_routePaths[$staticPart] : [];
                $this->_routePaths[$staticPart][] = $row;
            }
            $expressions[$expression] = [
                'expression' => $expression,
                'prefix' => $staticPart,
                'params' => $params,
            ];
        }

        return $expressions;
    }
}