<?php

namespace Think;

/**
 * 系统钩子实现
 * Class Hook
 * @package Think
 */
final class Hook
{

    /**
     * @var array $tags 行为集合
     */
    private static $tags = [];

    /**
     * 动态添加插件到某个标签
     *
     * @param string $tag 标签名称
     * @param array|string $name Behavior名称
     *
     * @return void
     */
    public static function add($tag, $name)
    {
        if (!isset(static::$tags[$tag])) {
            static::$tags[$tag] = [];
        }
        if (is_array($name)) {
            static::$tags[$tag] = array_merge(static::$tags[$tag], $name);
        } else {
            static::$tags[$tag][] = $name;
        }
    }

    /**
     * 批量导入插件
     *
     * @param array $data 插件信息
     * @param bool $recursive 是否递归合并
     *
     * @return void
     */
    public static function import(array $data = [], $recursive = true)
    {
        // 覆盖导入
        if (!$recursive) {
            static::$tags = array_merge(static::$tags, $data);
            // 合并导入
        } else {
            foreach ($data as $tag => $val) {
                if (!isset(static::$tags[$tag])) {
                    static::$tags[$tag] = [];
                }
                if (!empty($val['_overlay'])) {
                    // 可以针对某个标签指定覆盖模式
                    unset($val['_overlay']);
                    static::$tags[$tag] = $val;
                } else {
                    // 合并模式
                    static::$tags[$tag] = array_merge(static::$tags[$tag], $val);
                }
            }
        }
    }

    /**
     * 获取插件信息
     *
     * @param string $tag 插件位置 留空获取全部
     *
     * @return array
     */
    public static function get($tag = '')
    {
        if (empty($tag)) {
            // 获取全部的插件信息
            return static::$tags;
        } else {
            return static::$tags[$tag];
        }
    }

    /**
     * 监听标签的插件
     *
     * @param string $tag 标签名称
     * @param mixed $params 传入参数
     *
     * @return void
     */
    public static function listen($tag, &$params = null)
    {
        if (isset(static::$tags[$tag])) {
            if (APP_DEBUG) {
                G($tag.'Start');
                trace('[ '.$tag.' ] --START--', '', 'INFO');
            }
            foreach (static::$tags[$tag] as $name) {
                APP_DEBUG && G($name.'_start');
                $result = static::exec($name, $tag, $params);
                if (APP_DEBUG) {
                    G($name.'_end');
                    trace('Run '.$name.' [ RunTime:'.G($name.'_start', $name.'_end', 6).'s ]', '', 'INFO');
                }
                if (false === $result) {
                    // 如果返回false 则中断插件执行
                    return;
                }
            }
            // 记录行为的执行日志
            if (APP_DEBUG) {
                trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start', $tag.'End', 6).'s ]', '', 'INFO');
            }
        }

        return;
    }

    /**
     * 执行某个插件
     * @todo $name 这传参方式真的有点- -
     *
     * @param string $name 插件名称
     * @param string $tag 方法名（标签名）
     * @param mixed $params 传入的参数
     *
     * @return mixed
     */
    public static function exec($name, $tag, &$params = null)
    {
        if ('Behavior' == substr($name, -8)) {
            // 行为扩展必须用run入口方法
            //@todo 注意，这里还必须区分，$tag还可能不是run！这标识符设计还真有点坑
            $tag = 'run';
        }
        $addon = new $name();

        return $addon->$tag($params);
    }
}


<?php

namespace Txiki\Callback;

use Txiki\Callback\ICallable;
use Txiki\Callback\CallException;
use \ReflectionClass;
use \Closure;

/**
 * Call dispatch class
 */
class Call
{
    /**
     * Dispatch callable object
     *
     * @param Txiki\Callback\ICallable $object
     *
     * @return mixed
     */
    public static function dispatch(ICallable $object)
    {
        $params = $object->getParams();
        $call = $object->getCallable();

        if (!is_callable($call)) {
            throw new CallException("Non callable object found");
        }

        if ($call instanceof Closure) {
            return call_user_func_array($call, $params);
        }

        $class = explode('::', $call);

        $reflection = new ReflectionClass($class[0]);
        $method = $reflection->getMethod($class[1]);

        if ($method->isStatic()) {
            return call_user_func_array($call, $params);
        }

        return call_user_func_array(array(new $class[0], $class[1]), $params);
    }
}


<?php

namespace Ekyna\Bundle\CoreBundle\EventListener;

use Ekyna\Bundle\CoreBundle\Cache\TagManager;
use Ekyna\Bundle\CoreBundle\Event\HttpCacheEvent;
use Ekyna\Bundle\CoreBundle\Event\HttpCacheEvents;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Class HttpCacheEventSubscriber
 * @package Ekyna\Bundle\CoreBundle\EventListener
 * @author Étienne Dauvergne <contact@ekyna.com>
 */
class HttpCacheEventSubscriber implements EventSubscriberInterface
{
    /**
     * @var TagManager
     */
    protected $tagManager;

    /**
     * Constructor.
     *
     * @param TagManager $tagManager
     */
    public function __construct(TagManager $tagManager)
    {
        $this->tagManager = $tagManager;
    }

    /**
     * Tag response event handler.
     *
     * @param HttpCacheEvent $event
     */
    public function onTagResponse(HttpCacheEvent $event)
    {
        $tags = $event->getData();

        if (empty($tags)) {
            return;
        }

        $this->tagManager->addTags($tags);
    }

    /**
     * Invalidate tags event handler.
     *
     * @param HttpCacheEvent $event
     */
    public function onInvalidateTag(HttpCacheEvent $event)
    {
        $tags = $event->getData();

        if (empty($tags)) {
            return;
        }

        $this->tagManager->invalidateTags($tags);
    }

    /**
     * Kernel response event handler.
     */
    public function onKernelResponse()
    {
        $this->tagManager->flush();
    }

    /**
     * Kernel terminate event handler.
     */
    public function onKernelTerminate()
    {
        $this->tagManager->flush();
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
            HttpCacheEvents::TAG_RESPONSE   => ['onTagResponse', 0],
            HttpCacheEvents::INVALIDATE_TAG => ['onInvalidateTag', 0],

            KernelEvents::RESPONSE          => ['onKernelResponse', 0],

            KernelEvents::TERMINATE         => ['onKernelTerminate', 0],
            ConsoleEvents::TERMINATE        => ['onKernelTerminate', 0],
        ];
    }
}


<?php

namespace Oxygen\Data\Validation;

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Oxygen\Data\Exception\InvalidEntityException;

class ValidationSubscriber implements EventSubscriber {

    /**
     * The validation service.
     *
     * @var ValidationService
     */

    protected $validator;

    /**
     * Constructs the ValidationSubscriber
     *
     * @param ValidationService $validator
     */
    public function __construct(ValidationService $validator) {
        $this->validator = $validator;
    }

    /**
     * Returns the subscribed events.
     *
     * @return array
     */
    public function getSubscribedEvents() {
        return [Events::prePersist, Events::preUpdate];
    }

    /**
     * Performs validation before the entity is persisted.
     *
     * @param LifecycleEventArgs $args
     * @throws InvalidEntityException if the entity is invalid
     * @return boolean
     */

    protected function validate(LifecycleEventArgs $args) {
        $entity = $args->getEntity();
        $metadata = $args->getEntityManager()->getClassMetadata(get_class($entity));
        if($this->shouldBeValidated($entity)) {
            $rules = $entity->getValidationRules();
            $fields = $metadata->getFieldNames();
            $data = [];
            foreach($fields as $field) {
                $data[$field] = $metadata->getFieldValue($entity, $field);
            }

            $this->validator->with($data, $rules);
            if(!$this->validator->passes()) {
                throw new InvalidEntityException($entity, $this->validator->errors());
            }
        }
    }

    /**
     * Determines if the entity should be validated.
     *
     * @param object $entity
     * @return bool
     */

    protected function shouldBeValidated($entity) {
        return $entity instanceof Validatable;
    }

    public function prePersist(LifecycleEventArgs $args) {
        return $this->validate($args);
    }

    public function preUpdate(LifecycleEventArgs $args) {
        return $this->validate($args);
    }

}


<?php

namespace SubjectivePHP\Psr\Log;

/**
 * Trait for PSR-3 Message Interpolation.
 */
trait MessageInterpolationTrait
{
    /**
     * Interpolates context values into the message placeholders.
     *
     * @link https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#12-message
     *
     * @param string $message The string containing the placeholders.
     * @param array  $context The key/value array of replacement values.
     *
     * @return string
     */
    protected function interpolateMessage($message, array $context)//@codingStandardsIgnoreLine Ignore missing type hint
    {
        $context = array_filter(
            $context,
            function ($value) {
                return (is_scalar($value) || (is_object($value) && method_exists($value, '__toString')));
            }
        );

        $search = [];
        $replace = [];
        foreach ($context as $key => $value) {
            $search[] = "{{$key}}";
            $replace[] = (string)$value;
        }

        return str_replace($search, $replace, $message);
    }
}


<?php
/**
 * This file is part of the Maths package.
 *
 * Copyright (c) 2014-2016 Pierre Cassat <me@e-piwi.fr> and contributors
 *
 * 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.
 *
 * The source code of this package is available online at
 * <http://github.com/atelierspierrot/maths>.
 */

namespace Maths;

use \Maths\PointInterface;
use \Maths\Geometry\Point;
use \Maths\Geometry\Line;
use \Maths\Geometry\Segment;

/**
 * Basic class Maths
 *
 * @author  piwi <me@e-piwi.fr>
 */
class Maths
{

    /**
     */
    const CARTESIAN_1D  = 1;

    /**
     */
    const CARTESIAN_2D  = 2;

    /**
     */
    const CARTESIAN_3D  = 4;

    /**
     */
    const ABSCISSA    = 'x';

    /**
     */
    const ORDINATE    = 'y';

    /**
     */
    const RATE        = 'z';

    /**
     */
    const ORIGIN      = 0;

    /**
     */
    const RIGHT_ANGLE = 90;

    /**
     */
    const DIRECTION_POSITIVE    = 1;

    /**
     */
    const DIRECTION_NEGATIVE    = -1;

    /**
     */
    const DIRECTION_NULL        = 0;

    /**
     * Infinity expression
     */
    const INFINITY              = null;

// ToString utilities

    /**
     * @param   string|array    $coordinates
     * @return  string
     */
    public static function coordinatesToString($coordinates)
    {
        if (!is_array($coordinates)) {
            $coordinates = array( $coordinates );
        }
        return '('.join(',', $coordinates).')';
    }

    /**
     * @param   string|array    $coordinates_a
     * @param   string|array    $coordinates_b
     * @return  string
     */
    public static function segmentToString($coordinates_a, $coordinates_b)
    {
        return self::polygonToString($coordinates_a, $coordinates_b);
    }

    /**
     * @return  string
     */
    public static function polygonToString()
    {
        $data = array();
        foreach (func_get_args() as $arg) {
            $data[] = self::coordinatesToString($arg);
        }
        return '['.join(',', $data).']';
    }

// Comparisons utilities

    /**
     * Test if two points are in the same space type
     *
     * @param   \Maths\PointInterface $point1
     * @param   \Maths\PointInterface $point2
     * @return  bool
     */
    public static function areSameSpace(PointInterface $point1, PointInterface $point2)
    {
        return (bool) (
            ($point1->is1D() && $point2->is1d()) ||
            ($point1->is2D() && $point2->is2d()) ||
            ($point1->is3D() && $point2->is3d())
        );
    }

    /**
     * Test if two points are in the same position
     *
     * @param   \Maths\PointInterface $point1
     * @param   \Maths\PointInterface $point2
     * @return  bool
     * @throws  \InvalidArgumentException if the two points are not in the same space type
     */
    public static function areSamePoint(PointInterface $point1, PointInterface $point2)
    {
        if (self::areSameSpace($point1, $point2)) {
            if ($point1->is1D()) {
                return (bool) ($point1->getAbscissa()==$point2->getAbscissa());
            } elseif ($point1->is2D()) {
                return (bool) (
                    ($point1->getAbscissa()==$point2->getAbscissa()) &&
                    ($point1->getOrdinate()==$point2->getOrdinate())
                );
            } elseif ($point1->is3D()) {
                return (bool) (
                    ($point1->getAbscissa()==$point2->getAbscissa()) &&
                    ($point1->getOrdinate()==$point2->getOrdinate()) &&
                    ($point1->getApplicate()==$point2->getApplicate())
                );
            }
        } else {
            throw new \InvalidArgumentException(
                "Can not compare positions of two points of different space types"
            );
        }
    }

    /**
     * Get the intersection point between two lines
     *
     *      y = (A1 * x) + B1  &&  y = (A2 * x) + B2
     *      x = (B2 - B1) / (A1 - A2)
     *
     * @param   \Maths\Geometry\Line    $line1
     * @param   \Maths\Geometry\Line    $line2
     * @return  \Maths\Geometry\Point
     */
    public static function getLinesIntersection(Line $line1, Line $line2)
    {
        $div = ($line1->getSlope() - $line2->getSlope());
        $x = ($div!=0 ?
            (($line2->getYIntercept() - $line1->getYIntercept()) / $div) : ($line2->getYIntercept() - $line1->getYIntercept()));
        $y = $line1->getOrdinateByAbscissa($x);
        return new Point($x, $y);
    }

    /**
     * Test if two segments are perpendiculars
     *
     * This will test it two lines are perpendiculars constructing a triangle and
     * testing if it is perpendicular
     *
     * @param   \Maths\Geometry\Line     $line1
     * @param   \Maths\Geometry\Line     $line2
     * @return  bool
     */
    public function arePerpendiculars(Line $line1, Line $line2)
    {
        if (self::areParallels($line1, $line2)) {
            return false;
        }
    }

    /**
     * Test if two segments are parallels
     *
     * This will test if the two lines drawn from the two segments verify the Thales theorem
     * by comparing [ab] (segment 1), [cd] (segment 2) and a fifth point [e] used to build
     * triangles:
     *
     *      [ae]/[ce] == [be]/[de] ?
     *
     * @param   \Maths\Geometry\Line     $line1
     * @param   \Maths\Geometry\Line     $line2
     * @return  bool
     */
    public static function areParallels(Line $line1, Line $line2)
    {
        if (
            ($line1->isHorizontal() && $line2->isHorizontal()) ||
            ($line1->isVertical() && $line2->isVertical())
        ) {
            return true;
        }
        $line1->rearrange();
        $line2->rearrange();
        $abs = array(
            $line1->getPointA()->getAbscissa(),
            $line1->getPointB()->getAbscissa(),
            $line2->getPointA()->getAbscissa(),
            $line2->getPointB()->getAbscissa()
        );
        $ords = array(
            $line1->getPointA()->getOrdinate(),
            $line1->getPointB()->getOrdinate(),
            $line2->getPointA()->getOrdinate(),
            $line2->getPointB()->getOrdinate()
        );
        $e = new Point(
            rand(0, 10) + (max($abs) + abs(min($abs))),
            rand(0, 10) + (max($ords) + abs(min($ords)))
        );
        $segAE = new Segment($line1->getPointA(), $e);
        $segBE = new Segment($line1->getPointB(), $e);

        $intersectCE = self::getLinesIntersection($segAE, $line2);
        $segCE = new Segment($intersectCE, $e);
        $intersectDE = self::getLinesIntersection($segBE, $line2);
        $segDE = new Segment($intersectDE, $e);

/*
echo <<<TYPEOTHER
        var demo1 = brd.create('point', [{$line1->getPointA()->x},{$line1->getPointA()->y}], {
            name: 'a'
        });
        var demo2 = brd.create('point', [{$line1->getPointB()->x},{$line1->getPointB()->y}], {
            name: 'b'
        });
        var segment1 = brd.create('line', [demo1,demo2], {straightFirst:false,straightLast:false});

        var demo3 = brd.create('point', [{$intersectCE->x},{$intersectCE->y}], {
            name: 'c'
        });
        var demo4 = brd.create('point', [{$intersectDE->x},{$intersectDE->y}], {
            name: 'd'
        });
        var segment2 = brd.create('line', [demo3,demo4], {straightFirst:false,straightLast:false});

        var demo5 = brd.create('point', [{$e->x},{$e->y}], {
            name: 'e'
        });
        var segment3 = brd.create('line', [demo1,demo5], {straightFirst:false,straightLast:false,color:'#404040'});
        var segment4 = brd.create('line', [demo2,demo5], {straightFirst:false,straightLast:false,color:'#404040'});
        var segment5 = brd.create('line', [demo3,demo5], {straightFirst:false,straightLast:false,color:'#404040'});
        var segment6 = brd.create('line', [demo4,demo5], {straightFirst:false,straightLast:false,color:'#404040'});
TYPEOTHER;
*/

        return (bool) (($segAE->getLength() / $segCE->getLength()) == ($segBE->getLength() / $segDE->getLength()));
    }

    /**
     * Get the directions between two points for each axis
     *
     * This will return an array with the value `1,-1,0` for each axis.
     *
     * @param   PointInterface  $point1
     * @param   PointInterface  $point2
     * @return  array
     * @throws  \InvalidArgumentException
     */
    public static function getDirectionByPoints(PointInterface $point1, PointInterface $point2)
    {
        if (self::areSameSpace($point1, $point2)) {
            $directions = array(
                0 => self::getDirectionByCoordinates($point1->getAbscissa(), $point2->getAbscissa())
            );
            if ($point1->is2D() || $point1->is3D()) {
                $directions[1] = self::getDirectionByCoordinates($point1->getOrdinate(), $point2->getOrdinate());
            }
            if ($point1->is3D()) {
                $directions[2] = self::getDirectionByCoordinates($point1->getApplicate(), $point2->getApplicate());
            }
            return $directions;
        } else {
            throw new \InvalidArgumentException(
                "Can not guess directions between two points of different space types"
            );
        }
    }

    /**
     * Get the directions between two points
     *
     * This will return:
     *
     * -    `1` if [a->b] evolves in positive direction
     * -    `-1` if [a->b] evolves in negative direction
     * -    `0` if [a->b] does not evolve (same point)
     *
     * @param   int  $a
     * @param   int  $b
     * @return  int
     */
    public static function getDirectionByCoordinates($a, $b)
    {
        if ($a < $b) {
            return self::DIRECTION_POSITIVE;
        } elseif ($a > $b) {
            return self::DIRECTION_NEGATIVE;
        } else {
            return self::DIRECTION_NULL;
        }
    }
}


<?php

namespace Reaction\Db\Orm;

use Reaction\Db\CommandInterface;
use Reaction\Db\DatabaseInterface;
use Reaction\Db\DbConnectionGetterInterface;
use Reaction\Db\Expressions\ExpressionInterface;
use Reaction\Db\Query;
use Reaction\Db\QueryInterface;
use Reaction\DI\BaseInjectorTrait;
use Reaction\Exceptions\InvalidConfigException;
use Reaction\Promise\ExtendedPromiseInterface;
use Reaction\Promise\LazyPromise;
use Reaction\Promise\LazyPromiseInterface;
use Reaction\Promise\Promise;
use function Reaction\Promise\reject;
use function Reaction\Promise\resolve;

/**
 * ActiveQuery represents a DB query associated with an Active Record class.
 *
 * An ActiveQuery can be a normal query or be used in a relational context.
 *
 * ActiveQuery instances are usually created by [[ActiveRecord::find()]] and [[ActiveRecord::findBySql()]].
 * Relational queries are created by [[ActiveRecord::hasOne()]] and [[ActiveRecord::hasMany()]].
 *
 * Normal Query
 * ------------
 *
 * ActiveQuery mainly provides the following methods to retrieve the query results:
 *
 * - [[one()]]: returns a single record populated with the first row of data.
 * - [[all()]]: returns all records based on the query results.
 * - [[count()]]: returns the number of records.
 * - [[sum()]]: returns the sum over the specified column.
 * - [[average()]]: returns the average over the specified column.
 * - [[min()]]: returns the min over the specified column.
 * - [[max()]]: returns the max over the specified column.
 * - [[scalar()]]: returns the value of the first column in the first row of the query result.
 * - [[column()]]: returns the value of the first column in the query result.
 * - [[exists()]]: returns a value indicating whether the query result has data or not.
 *
 * Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]],
 * [[orderBy()]] to customize the query options.
 *
 * ActiveQuery also provides the following additional query options:
 *
 * - [[with()]]: list of relations that this query should be performed with.
 * - [[joinWith()]]: reuse a relation query definition to add a join to a query.
 * - [[indexBy()]]: the name of the column by which the query result should be indexed.
 * - [[asArray()]]: whether to return each record as an array.
 *
 * These options can be configured using methods of the same name. For example:
 *
 * ```php
 * $customers = Customer::find()->with('orders')->asArray()->all();
 * ```
 *
 * Relational query
 * ----------------
 *
 * In relational context ActiveQuery represents a relation between two Active Record classes.
 *
 * Relational ActiveQuery instances are usually created by calling [[ActiveRecord::hasOne()]] and
 * [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
 * a getter method which calls one of the above methods and returns the created ActiveQuery object.
 *
 * A relation is specified by [[link]] which represents the association between columns
 * of different tables; and the multiplicity of the relation is indicated by [[multiple]].
 *
 * If a relation involves a junction table, it may be specified by [[via()]] or [[viaTable()]] method.
 * These methods may only be called in a relational context. Same is true for [[inverseOf()]], which
 * marks a relation as inverse of another relation and [[onCondition()]] which adds a condition that
 * is to be added to relational query join condition.
 */
class ActiveQuery extends Query implements ActiveQueryInterface
{
    use ActiveQueryTrait;
    use ActiveRelationTrait;
    use BaseInjectorTrait;

    /**
     * @event Event an event that is triggered when the query is initialized via [[init()]].
     */
    const EVENT_INIT = 'init';

    /**
     * @var string the SQL statement to be executed for retrieving AR records.
     * This is set by [[ActiveRecord::findBySql()]].
     */
    public $sql;
    /**
     * @var string|array the join condition to be used when this query is used in a relational context.
     * The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
     * Otherwise, the condition will be used in the WHERE part of a query.
     * Please refer to [[Query::where()]] on how to specify this parameter.
     * @see onCondition()
     */
    public $on;
    /**
     * @var array a list of relations that this query should be joined with
     */
    public $joinWith;

    /**
     * Constructor.
     * @param string $modelClass the model class associated with this query
     * @param array $config configurations to be applied to the newly created query object
     */
    public function __construct($modelClass, $config = [])
    {
        $this->modelClass = $modelClass;
        parent::__construct($config);
    }

    /**
     * Initializes the object.
     * This method is called at the end of the constructor. The default implementation will trigger
     * an [[EVENT_INIT]] event. If you override this method, make sure you call the parent implementation at the end
     * to ensure triggering of the event.
     */
    public function init()
    {
        parent::init();
        $this->emit(self::EVENT_INIT, [$this]);
    }

    /**
     * Executes query and returns all results as an array.
     * @param DatabaseInterface|DbConnectionGetterInterface|null $db the DB connection used to create the DB command.
     * If null, the DB connection returned by [[modelClass]] will be used.
     * @return ExtendedPromiseInterface with array|ActiveRecordInterface[] the query results. If the query results in nothing, an empty array will be returned.
     */
    public function all($db = null)
    {
        if ($this->emulateExecution) {
            return resolve([]);
        }
        return $this->createCommand($db)->thenLazy(
            function(CommandInterface $command) {
                return $command->queryAll();
            }
        )->thenLazy(
            function($results) {
                return $this->populate($results);
            }
        );
    }

    /**
     * {@inheritdoc}
     */
    public function prepareAsync($builder)
    {
        // NOTE: because the same ActiveQuery may be used to build different SQL statements
        // (e.g. by ActiveDataProvider, one for count query, the other for row data query,
        // it is important to make sure the same ActiveQuery can be used to build SQL statements
        // multiple times.
        if (!empty($this->joinWith)) {
            $this->buildJoinWith();
            $this->joinWith = null; // clean it up to avoid issue https://github.com/yiisoft/yii2/issues/2687
        }

        if (empty($this->from)) {
            $this->from = [$this->getPrimaryTableName()];
        }

        if (empty($this->select) && !empty($this->join)) {
            list(, $alias) = $this->getTableNameAndAlias();
            $this->select = ["$alias.*"];
        }

        if ($this->primaryModel === null) {
            // eager loading
            $queryPr = new LazyPromise(function() {
                return resolve(Query::create($this));
            });
        } else {
            // lazy loading of a relation
            $queryPr = $this->prepareAsyncVia();
        }

        return $queryPr->thenLazy(function($query) {
            /** @var QueryInterface $query */
            if (!empty($this->on)) {
                $query->andWhere($this->on);
            }
            return $query;
        });
    }

    /**
     * Prepare query with relations
     * @return ExtendedPromiseInterface|LazyPromise
     */
    protected function prepareAsyncVia() {
        // lazy loading of a relation
        $where = $this->where;

        $promise = new LazyPromise(function() {
            return resolve(true);
        });

        if ($this->via instanceof self) {
            // via junction table
            $promise->thenLazy(
                function() {
                    return $this->via->findJunctionRows([$this->primaryModel]);
                }
            )->thenLazy(
                function($viaModels) {
                    $this->filterByModels($viaModels);
                    return true;
                }
            );
        } elseif (is_array($this->via)) {
            // via relation
            /* @var $viaQuery ActiveQuery */
            list($viaName, $viaQuery) = $this->via;
            $viaModelsPr = $viaQuery->multiple ? $viaQuery->all() : $viaQuery->one();
            $viaModelsPr->otherwise(function() use (&$viaQuery) {
                return $viaQuery->multiple ? [] : null;
            });
            $promise->thenLazy(
                function() use ($viaModelsPr) {
                    return $viaModelsPr;
                }
            )->thenLazy(
                function($viaModels) use ($viaName, &$viaQuery) {
                    $this->primaryModel->populateRelation($viaName, $viaModels);
                    if (!$viaQuery->multiple) {
                        $viaModels = $viaModels === null ? [] : [$viaModels];
                    }
                    return $viaModels;
                }
            )->thenLazy(
                function($viaModels) {
                    $this->filterByModels($viaModels);
                    return true;
                }
            );
        } else {
            $this->filterByModels([$this->primaryModel]);
        }
        return $promise->thenLazy(
            function() use (&$where) {
                $query = Query::create($this);
                $this->where = $where;
                return $query;
            }
        );
    }

    /**
     * {@inheritdoc}
     */
    public function populate($rows)
    {
        if (empty($rows)) {
            return resolve([]);
        }

        $models = $this->createModels($rows);
        if (!empty($this->join) && $this->indexBy === null) {
            $models = $this->removeDuplicatedModels($models);
        }
        $promise = !empty($this->with) ? $this->findWith($this->with, $models) : resolve(true);
        return $promise->then(function() use (&$models) {
            if ($this->inverseOf !== null) {
                $this->addInverseRelations($models);
            }

            if (!$this->asArray) {
                foreach ($models as $model) {
                    $model->afterFind();
                }
            }
            return parent::populate($models);
        })->then(function($models) {
            $this->emit($this->eventChildrenInject, [&$models]);
            return $models;
        });
    }

    /**
     * Removes duplicated models by checking their primary key values.
     * This method is mainly called when a join query is performed, which may cause duplicated rows being returned.
     * @param array $models the models to be checked
     * @throws InvalidConfigException if model primary key is empty
     * @return array|ActiveRecordInterface[] the distinctive models
     */
    private function removeDuplicatedModels($models)
    {
        $hash = [];
        /* @var $class ActiveRecordInterface */
        $class = $this->modelClass;
        $pks = $class::primaryKey();

        if (count($pks) > 1) {
            // composite primary key
            foreach ($models as $i => $model) {
                $key = [];
                foreach ($pks as $pk) {
                    if (!isset($model[$pk])) {
                        // do not continue if the primary key is not part of the result set
                        break 2;
                    }
                    $key[] = $model[$pk];
                }
                $key = serialize($key);
                if (isset($hash[$key])) {
                    unset($models[$i]);
                } else {
                    $hash[$key] = true;
                }
            }
        } elseif (empty($pks)) {
            throw new InvalidConfigException("Primary key of '{$class}' can not be empty.");
        } else {
            // single column primary key
            $pk = reset($pks);
            foreach ($models as $i => $model) {
                if (!isset($model[$pk])) {
                    // do not continue if the primary key is not part of the result set
                    break;
                }
                $key = $model[$pk];
                if (isset($hash[$key])) {
                    unset($models[$i]);
                } elseif ($key !== null) {
                    $hash[$key] = true;
                }
            }
        }

        return array_values($models);
    }

    /**
     * Executes query and returns a single row of result.
     * @param DatabaseInterface|DbConnectionGetterInterface|null $db the DB connection used to create the DB command.
     * If `null`, the DB connection returned by [[modelClass]] will be used.
     * @return ExtendedPromiseInterface with ActiveRecordInterface|array|null a single row of query result. Depending on the setting of [[asArray]],
     * the query result may be either an array or an ActiveRecord object. `null` will be returned
     * if the query results in nothing.
     */
    public function one($db = null)
    {
        if ($this->emulateExecution) {
            return reject(false);
        }

        return $this->createCommand($db)->thenLazy(
            function(CommandInterface $command) {
                return $command->queryOne();
            }
        )->thenLazy(
            function($row) {
                if ($row !== false) {
                    return $this->populate([$row]);
                }
                return reject(null);
            }
        )->thenLazy(
            function($models) {
                return reset($models) ?: reject(null);
            }
        );
    }

    /**
     * Returns the query result as a scalar value.
     * The value returned will be the first column in the first row of the query results.
     * @param DatabaseInterface|DbConnectionGetterInterface|null $db the database connection used to generate the SQL statement.
     * If this parameter is not given, the `db` application component will be used.
     * @return ExtendedPromiseInterface with string|null the value of the first column in the first row of the query result.
     * False is returned if the query result is empty.
     */
    public function scalar($db = null)
    {
        if ($this->emulateExecution) {
            return reject(null);
        }

        return $this->createCommand($db)->then(
            function(CommandInterface $command) {
                return $command->queryScalar();
            }
        );
    }

    /**
     * Creates a DB command that can be used to execute this query.
     * @param DatabaseInterface|DbConnectionGetterInterface|null $db the DB connection used to create the DB command.
     * If `null`, the DB connection returned by [[modelClass]] will be used.
     * @return LazyPromiseInterface with Command the created DB command instance.
     */
    public function createCommand($db = null)
    {
        /* @var $modelClass ActiveRecordInterface */
        $modelClass = $this->modelClass;
        $connection = null;
        if ($db === null) {
            $db = $modelClass::getDb();
        } elseif ($db instanceof DbConnectionGetterInterface && !$db instanceof DatabaseInterface) {
            $connection = $db->getConnection();
            $db = $db->getDb();
        }

        if ($this->sql === null) {
            $sqlParamsPr = $db->getQueryBuilder()->buildAsync($this);
        } else {
            $sqlParamsPr = new LazyPromise(function() { return resolve([$this->sql, $this->params]); });
        }

        return $sqlParamsPr->thenLazy(
            function($data) use (&$db, &$connection) {
                list($sql, $params) = $data;
                $command = $db->createCommand($sql, $params, $connection);
                $this->setCommandCache($command);

                return $command;
            }
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function queryScalar($selectExpression, $db = null)
    {
        if ($this->emulateExecution) {
            return resolve(null);
        }
        /* @var $modelClass ActiveRecordInterface */
        $modelClass = $this->modelClass;
        if ($db === null) {
            $db = $modelClass::getDb();
        }

        if ($this->sql === null) {
            return $this->queryScalarAsync($selectExpression, $db);
        }

        $command = (new Query())->select([$selectExpression])
            ->from(['c' => "({$this->sql})"])
            ->params($this->params)
            ->createCommand($db);
        $this->setCommandCache($command);

        return $command->queryScalar();
    }

    /**
     * Queries a scalar value by setting [[select]] first.
     * Restores the value of select to make this query reusable.
     * @param string|ExpressionInterface $selectExpression
     * @param DatabaseInterface|DbConnectionGetterInterface|null $db
     * @return LazyPromiseInterface with bool|string
     */
    protected function queryScalarAsync($selectExpression, $db)
    {
        if (
            !$this->distinct
            && empty($this->groupBy)
            && empty($this->having)
            && empty($this->union)
        ) {
            $select = $this->select;
            $order = $this->orderBy;
            $limit = $this->limit;
            $offset = $this->offset;

            $this->select = [$selectExpression];
            $this->orderBy = null;
            $this->limit = null;
            $this->offset = null;
            return $this->createCommand($db)->thenLazy(
                function(CommandInterface $command) use (&$select, &$order, &$limit, &$offset) {
                    $this->select = $select;
                    $this->orderBy = $order;
                    $this->limit = $limit;
                    $this->offset = $offset;
                    return $command->queryScalar();
                }
            );
        }

        $command = (new Query())
            ->select([$selectExpression])
            ->from(['c' => $this])
            ->createCommand($db);
        $this->setCommandCache($command);

        return $command->queryScalar();
    }

    /**
     * Joins with the specified relations.
     *
     * This method allows you to reuse existing relation definitions to perform JOIN queries.
     * Based on the definition of the specified relation(s), the method will append one or multiple
     * JOIN statements to the current query.
     *
     * If the `$eagerLoading` parameter is true, the method will also perform eager loading for the specified relations,
     * which is equivalent to calling [[with()]] using the specified relations.
     *
     * Note that because a JOIN query will be performed, you are responsible to disambiguate column names.
     *
     * This method differs from [[with()]] in that it will build up and execute a JOIN SQL statement
     * for the primary table. And when `$eagerLoading` is true, it will call [[with()]] in addition with the specified relations.
     *
     * @param string|array $with the relations to be joined. This can either be a string, representing a relation name or
     * an array with the following semantics:
     *
     * - Each array element represents a single relation.
     * - You may specify the relation name as the array key and provide an anonymous functions that
     *   can be used to modify the relation queries on-the-fly as the array value.
     * - If a relation query does not need modification, you may use the relation name as the array value.
     *
     * The relation name may optionally contain an alias for the relation table (e.g. `books b`).
     *
     * Sub-relations can also be specified, see [[with()]] for the syntax.
     *
     * In the following you find some examples:
     *
     * ```php
     * // find all orders that contain books, and eager loading "books"
     * Order::find()->joinWith('books', true, 'INNER JOIN')->all();
     * // find all orders, eager loading "books", and sort the orders and books by the book names.
     * Order::find()->joinWith([
     *     'books' => function (\yii\db\ActiveQuery $query) {
     *         $query->orderBy('item.name');
     *     }
     * ])->all();
     * // find all orders that contain books of the category 'Science fiction', using the alias "b" for the books table
     * Order::find()->joinWith(['books b'], true, 'INNER JOIN')->where(['b.category' => 'Science fiction'])->all();
     * ```
     *
     * The alias syntax is available since version 2.0.7.
     *
     * @param bool|array $eagerLoading whether to eager load the relations
     * specified in `$with`.  When this is a boolean, it applies to all
     * relations specified in `$with`. Use an array to explicitly list which
     * relations in `$with` need to be eagerly loaded.  Note, that this does
     * not mean, that the relations are populated from the query result. An
     * extra query will still be performed to bring in the related data.
     * Defaults to `true`.
     * @param string|array $joinType the join type of the relations specified in `$with`.
     * When this is a string, it applies to all relations specified in `$with`. Use an array
     * in the format of `relationName => joinType` to specify different join types for different relations.
     * @return $this the query object itself
     */
    public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN')
    {
        $relations = [];
        foreach ((array)$with as $name => $callback) {
            if (is_int($name)) {
                $name = $callback;
                $callback = null;
            }

            if (preg_match('/^(.*?)(?:\s+AS\s+|\s+)(\w+)$/i', $name, $matches)) {
                // relation is defined with an alias, adjust callback to apply alias
                list(, $relation, $alias) = $matches;
                $name = $relation;
                $callback = function($query) use ($callback, $alias) {
                    /* @var $query ActiveQuery */
                    $query->alias($alias);
                    if ($callback !== null) {
                        call_user_func($callback, $query);
                    }
                };
            }

            if ($callback === null) {
                $relations[] = $name;
            } else {
                $relations[$name] = $callback;
            }
        }
        $this->joinWith[] = [$relations, $eagerLoading, $joinType];
        return $this;
    }

    protected function buildJoinWith()
    {
        $join = $this->join;
        $this->join = [];

        /* @var $modelClass ActiveRecordInterface */
        $modelClass = $this->modelClass;
        $model = $modelClass::instance();
        foreach ($this->joinWith as $config) {
            list($with, $eagerLoading, $joinType) = $config;
            $this->joinWithRelations($model, $with, $joinType);

            if (is_array($eagerLoading)) {
                foreach ($with as $name => $callback) {
                    if (is_int($name)) {
                        if (!in_array($callback, $eagerLoading, true)) {
                            unset($with[$name]);
                        }
                    } elseif (!in_array($name, $eagerLoading, true)) {
                        unset($with[$name]);
                    }
                }
            } elseif (!$eagerLoading) {
                $with = [];
            }

            $this->with($with);
        }

        // remove duplicated joins added by joinWithRelations that may be added
        // e.g. when joining a relation and a via relation at the same time
        $uniqueJoins = [];
        foreach ($this->join as $j) {
            $uniqueJoins[serialize($j)] = $j;
        }
        $this->join = array_values($uniqueJoins);

        if (!empty($join)) {
            // append explicit join to joinWith()
            // https://github.com/yiisoft/yii2/issues/2880
            $this->join = empty($this->join) ? $join : array_merge($this->join, $join);
        }
    }

    /**
     * Inner joins with the specified relations.
     * This is a shortcut method to [[joinWith()]] with the join type set as "INNER JOIN".
     * Please refer to [[joinWith()]] for detailed usage of this method.
     * @param string|array $with the relations to be joined with.
     * @param bool|array $eagerLoading whether to eager load the relations.
     * Note, that this does not mean, that the relations are populated from the
     * query result. An extra query will still be performed to bring in the
     * related data.
     * @return $this the query object itself
     * @see joinWith()
     */
    public function innerJoinWith($with, $eagerLoading = true)
    {
        return $this->joinWith($with, $eagerLoading, 'INNER JOIN');
    }

    /**
     * Modifies the current query by adding join fragments based on the given relations.
     * @param ActiveRecordInterface $model the primary model
     * @param array $with the relations to be joined
     * @param string|array $joinType the join type
     */
    private function joinWithRelations($model, $with, $joinType)
    {
        $relations = [];

        foreach ($with as $name => $callback) {
            if (is_int($name)) {
                $name = $callback;
                $callback = null;
            }

            $primaryModel = $model;
            $parent = $this;
            $prefix = '';
            while (($pos = strpos($name, '.')) !== false) {
                $childName = substr($name, $pos + 1);
                $name = substr($name, 0, $pos);
                $fullName = $prefix === '' ? $name : "$prefix.$name";
                if (!isset($relations[$fullName])) {
                    /** @var ActiveQuery $relation */
                    $relations[$fullName] = $relation = $primaryModel->getRelation($name);
                    $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName));
                } else {
                    $relation = $relations[$fullName];
                }
                /* @var $relationModelClass ActiveRecordInterface */
                $relationModelClass = $relation->modelClass;
                $primaryModel = $relationModelClass::instance();
                $parent = $relation;
                $prefix = $fullName;
                $name = $childName;
            }

            $fullName = $prefix === '' ? $name : "$prefix.$name";
            if (!isset($relations[$fullName])) {
                $relations[$fullName] = $relation = $primaryModel->getRelation($name);
                if ($callback !== null) {
                    call_user_func($callback, $relation);
                }
                if (!empty($relation->joinWith)) {
                    $relation->buildJoinWith();
                }
                $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName));
            }
        }
    }

    /**
     * Returns the join type based on the given join type parameter and the relation name.
     * @param string|array $joinType the given join type(s)
     * @param string $name relation name
     * @return string the real join type
     */
    private function getJoinType($joinType, $name)
    {
        if (is_array($joinType) && isset($joinType[$name])) {
            return $joinType[$name];
        }

        return is_string($joinType) ? $joinType : 'INNER JOIN';
    }

    /**
     * Returns the table name and the table alias for [[modelClass]].
     * @return array the table name and the table alias.
     * @internal
     */
    private function getTableNameAndAlias()
    {
        if (empty($this->from)) {
            $tableName = $this->getPrimaryTableName();
        } else {
            $tableName = '';
            foreach ($this->from as $alias => $tableName) {
                if (is_string($alias)) {
                    return [$tableName, $alias];
                }
                break;
            }
        }

        if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $tableName, $matches)) {
            $alias = $matches[2];
        } else {
            $alias = $tableName;
        }

        return [$tableName, $alias];
    }

    /**
     * Joins a parent query with a child query.
     * The current query object will be modified accordingly.
     * @param ActiveQuery $parent
     * @param ActiveQuery $child
     * @param string $joinType
     */
    private function joinWithRelation($parent, $child, $joinType)
    {
        $via = $child->via;
        $child->via = null;
        if ($via instanceof self) {
            // via table
            $this->joinWithRelation($parent, $via, $joinType);
            $this->joinWithRelation($via, $child, $joinType);
            return;
        } elseif (is_array($via)) {
            // via relation
            $this->joinWithRelation($parent, $via[1], $joinType);
            $this->joinWithRelation($via[1], $child, $joinType);
            return;
        }

        list($parentTable, $parentAlias) = $parent->getTableNameAndAlias();
        list($childTable, $childAlias) = $child->getTableNameAndAlias();

        if (!empty($child->link)) {
            if (strpos($parentAlias, '{{') === false) {
                $parentAlias = '{{' . $parentAlias . '}}';
            }
            if (strpos($childAlias, '{{') === false) {
                $childAlias = '{{' . $childAlias . '}}';
            }

            $on = [];
            foreach ($child->link as $childColumn => $parentColumn) {
                $on[] = "$parentAlias.[[$parentColumn]] = $childAlias.[[$childColumn]]";
            }
            $on = implode(' AND ', $on);
            if (!empty($child->on)) {
                $on = ['and', $on, $child->on];
            }
        } else {
            $on = $child->on;
        }
        $this->join($joinType, empty($child->from) ? $childTable : $child->from, $on);

        if (!empty($child->where)) {
            $this->andWhere($child->where);
        }
        if (!empty($child->having)) {
            $this->andHaving($child->having);
        }
        if (!empty($child->orderBy)) {
            $this->addOrderBy($child->orderBy);
        }
        if (!empty($child->groupBy)) {
            $this->addGroupBy($child->groupBy);
        }
        if (!empty($child->params)) {
            $this->addParams($child->params);
        }
        if (!empty($child->join)) {
            foreach ($child->join as $join) {
                $this->join[] = $join;
            }
        }
        if (!empty($child->union)) {
            foreach ($child->union as $union) {
                $this->union[] = $union;
            }
        }
    }

    /**
     * Sets the ON condition for a relational query.
     * The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
     * Otherwise, the condition will be used in the WHERE part of a query.
     *
     * Use this method to specify additional conditions when declaring a relation in the [[ActiveRecord]] class:
     *
     * ```php
     * public function getActiveUsers()
     * {
     *     return $this->hasMany(User::className(), ['id' => 'user_id'])
     *                 ->onCondition(['active' => true]);
     * }
     * ```
     *
     * Note that this condition is applied in case of a join as well as when fetching the related records.
     * Thus only fields of the related table can be used in the condition. Trying to access fields of the primary
     * record will cause an error in a non-join-query.
     *
     * @param string|array $condition the ON condition. Please refer to [[Query::where()]] on how to specify this parameter.
     * @param array $params the parameters (name => value) to be bound to the query.
     * @return $this the query object itself
     */
    public function onCondition($condition, $params = [])
    {
        $this->on = $condition;
        $this->addParams($params);
        return $this;
    }

    /**
     * Adds an additional ON condition to the existing one.
     * The new condition and the existing one will be joined using the 'AND' operator.
     * @param string|array $condition the new ON condition. Please refer to [[where()]]
     * on how to specify this parameter.
     * @param array $params the parameters (name => value) to be bound to the query.
     * @return $this the query object itself
     * @see onCondition()
     * @see orOnCondition()
     */
    public function andOnCondition($condition, $params = [])
    {
        if ($this->on === null) {
            $this->on = $condition;
        } else {
            $this->on = ['and', $this->on, $condition];
        }
        $this->addParams($params);
        return $this;
    }

    /**
     * Adds an additional ON condition to the existing one.
     * The new condition and the existing one will be joined using the 'OR' operator.
     * @param string|array $condition the new ON condition. Please refer to [[where()]]
     * on how to specify this parameter.
     * @param array $params the parameters (name => value) to be bound to the query.
     * @return $this the query object itself
     * @see onCondition()
     * @see andOnCondition()
     */
    public function orOnCondition($condition, $params = [])
    {
        if ($this->on === null) {
            $this->on = $condition;
        } else {
            $this->on = ['or', $this->on, $condition];
        }
        $this->addParams($params);
        return $this;
    }

    /**
     * Specifies the junction table for a relational query.
     *
     * Use this method to specify a junction table when declaring a relation in the [[ActiveRecord]] class:
     *
     * ```php
     * public function getItems()
     * {
     *     return $this->hasMany(Item::className(), ['id' => 'item_id'])
     *                 ->viaTable('order_item', ['order_id' => 'id']);
     * }
     * ```
     *
     * @param string $tableName the name of the junction table.
     * @param array $link the link between the junction table and the table associated with [[primaryModel]].
     * The keys of the array represent the columns in the junction table, and the values represent the columns
     * in the [[primaryModel]] table.
     * @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 query object itself
     * @see via()
     */
    public function viaTable($tableName, $link, callable $callable = null)
    {
        $modelClass = $this->primaryModel !== null ? get_class($this->primaryModel) : __CLASS__;

        $relation = new self($modelClass, [
            'from' => [$tableName],
            'link' => $link,
            'multiple' => true,
            'asArray' => true,
        ]);
        $this->via = $relation;
        if ($callable !== null) {
            call_user_func($callable, $relation);
        }

        return $this;
    }

    /**
     * Define an alias for the table defined in [[modelClass]].
     *
     * This method will adjust [[from]] so that an already defined alias will be overwritten.
     * If none was defined, [[from]] will be populated with the given alias.
     *
     * @param string $alias the table alias.
     * @return $this the query object itself
     */
    public function alias($alias)
    {
        if (empty($this->from) || count($this->from) < 2) {
            list($tableName) = $this->getTableNameAndAlias();
            $this->from = [$alias => $tableName];
        } else {
            $tableName = $this->getPrimaryTableName();

            foreach ($this->from as $key => $table) {
                if ($table === $tableName) {
                    unset($this->from[$key]);
                    $this->from[$alias] = $tableName;
                }
            }
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getTablesUsedInFrom()
    {
        if (empty($this->from)) {
            return $this->cleanUpTableNames([$this->getPrimaryTableName()]);
        }

        return parent::getTablesUsedInFrom();
    }

    /**
     * @return string primary table name
     */
    protected function getPrimaryTableName()
    {
        /* @var $modelClass ActiveRecordInterface */
        $modelClass = $this->modelClass;
        return $modelClass::tableName();
    }
}


<?php
/**
 * Created by PhpStorm.
 * User: coffeekizoku
 * Date: 2017/8/18
 * Time: 上午11:13
 */

namespace SimpleShop\Brand;

use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Model;
use SimpleShop\Brand\Contracts\Brand;
use SimpleShop\Brand\Repositories\BrandRepository;
use SimpleShop\Brand\Repositories\Order;
use SimpleShop\Brand\Repositories\Search;

class BrandImpl implements Brand
{
    private $repo;

    public function __construct(BrandRepository $brandRepository)
    {
        $this->repo = $brandRepository;
    }

    /**
     * 获取品牌列表
     *
     * @param array $search
     * @param int   $limit
     * @param array $columns
     * @param array $order
     * @param int   $page
     *
     * @return LengthAwarePaginator
     */
    public function getList(
        array $search = [],
        int $limit = 20,
        array $columns = ["*"],
        array $order = ['id' => 'desc'],
        int $page = 1
    ): LengthAwarePaginator {
        $this->repo->pushCriteria(new Search($search))
            ->pushCriteria(new Order($order))
            ->paginate($limit, $columns,
                $page);
    }

    /**
     * @param $id
     *
     * @return Model
     */
    public function show($id): Model
    {
        return $this->repo->find($id);
    }

    /**
     * @param $id
     *
     * @return mixed
     */
    public function destroy($id)
    {
        return $this->repo->delete($id);
    }

    /**
     * @param array $data
     *
     * @return mixed
     */
    public function create(array $data)
    {
        return $this->repo->create($data);
    }

    /**
     * @param       $id
     * @param array $data
     *
     * @return mixed
     */
    public function update($id, array $data)
    {
        return $this->repo->update($data, $id);
    }
}


<?php

/*
 * This file is part of the Valkyrja framework.
 *
 * (c) Melech Mizrachi
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Valkyrja\HttpMessage;

use InvalidArgumentException;

/**
 * Class HeaderSecurity.
 *
 * @author Melech Mizrachi
 */
final class HeaderSecurity
{
    /**
     * Private constructor; non-instantiable.
     *
     * @codeCoverageIgnore
     */
    private function __construct()
    {
    }

    /**
     * Filter a header value.
     *
     * Ensures CRLF header injection vectors are filtered.
     *
     * Per RFC 7230, only VISIBLE ASCII characters, spaces, and horizontal
     * tabs are allowed in values; header continuations MUST consist of
     * a single CRLF sequence followed by a space or horizontal tab.
     *
     * This method filters any values not allowed from the string, and is
     * lossy.
     *
     * @see http://en.wikipedia.org/wiki/HTTP_response_splitting
     *
     * @param string $value
     *
     * @return string
     */
    public static function filter(string $value): string
    {
        $length = \strlen($value);
        $string = '';

        for ($i = 0; $i < $length; $i++) {
            $ascii = \ord($value[$i]);

            // Detect continuation sequences
            if ($ascii === 13) {
                $lf = \ord($value[$i + 1]);
                $ws = \ord($value[$i + 2]);

                if ($lf === 10 && \in_array($ws, [9, 32], true)) {
                    $string .= $value[$i] . $value[$i + 1];
                    $i++;
                }

                continue;
            }

            // Non-visible, non-whitespace characters
            // 9 === horizontal tab
            // 32-126, 128-254 === visible
            // 127 === DEL
            // 255 === null byte
            if (($ascii < 32 && $ascii !== 9)
                || $ascii === 127
                || $ascii > 254
            ) {
                continue;
            }

            $string .= $value[$i];
        }

        return $string;
    }

    /**
     * Validate a header value.
     *
     * Per RFC 7230, only VISIBLE ASCII characters, spaces, and horizontal
     * tabs are allowed in values; header continuations MUST consist of
     * a single CRLF sequence followed by a space or horizontal tab.
     *
     * @see http://en.wikipedia.org/wiki/HTTP_response_splitting
     *
     * @param string $value
     *
     * @return bool
     */
    public static function isValid(string $value): bool
    {
        // Look for:
        // \n not preceded by \r, OR
        // \r not followed by \n, OR
        // \r\n not followed by space or horizontal tab; these are all CRLF attacks
        if (preg_match("#(?:(?:(?<!\r)\n)|(?:\r(?!\n))|(?:\r\n(?![ \t])))#", $value)) {
            return false;
        }

        // Non-visible, non-whitespace characters
        // 9 === horizontal tab
        // 10 === line feed
        // 13 === carriage return
        // 32-126, 128-254 === visible
        // 127 === DEL (disallowed)
        // 255 === null byte (disallowed)
        if (preg_match('/[^\x09\x0a\x0d\x20-\x7E\x80-\xFE]/', $value)) {
            return false;
        }

        return true;
    }

    /**
     * Assert a header value is valid.
     *
     * @param string $value
     *
     * @throws InvalidArgumentException for invalid values
     *
     * @return void
     */
    public static function assertValid(string $value): void
    {
        if (! self::isValid($value)) {
            throw new InvalidArgumentException(
                sprintf(
                    '"%s" is not valid header value',
                    $value
                )
            );
        }
    }

    /**
     * Assert whether or not a header name is valid.
     *
     * @see http://tools.ietf.org/html/rfc7230#section-3.2
     *
     * @param mixed $name
     *
     * @throws InvalidArgumentException
     *
     * @return void
     */
    public static function assertValidName(string $name): void
    {
        if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $name)) {
            throw new InvalidArgumentException(
                sprintf(
                    '"%s" is not valid header name',
                    $name
                )
            );
        }
    }
}


<?php

namespace Ekyna\Bundle\CoreBundle\Form\Type;

use Doctrine\Common\Persistence\ObjectManager;
use Ekyna\Bundle\CoreBundle\Form\DataTransformer\ObjectToIdentifierTransformer;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * Class HiddenEntityType
 * @package Ekyna\Bundle\CoreBundle\Form\Type
 * @author Étienne Dauvergne <contact@ekyna.com>
 */
class HiddenEntityType extends AbstractType
{
    /**
     * @var ObjectManager
     */
    private $om;

    /**
     * @var DoctrineOrmTypeGuesser
     */
    private $guesser;

    /**
     * @param ObjectManager $om
     * @param DoctrineOrmTypeGuesser $guesser
     */
    public function __construct(ObjectManager $om, DoctrineOrmTypeGuesser $guesser)
    {
        $this->om = $om;
        $this->guesser = $guesser;
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $transformer = new ObjectToIdentifierTransformer();
        $builder->addViewTransformer($transformer);

        if(0 === strlen($options['class'])) {
            $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($transformer) {
                $form = $event->getForm();
                $class = $form->getParent()->getConfig()->getDataClass();
                $property = $form->getName();
                if (null === $guessedType = $this->guesser->guessType($class, $property)) {
                    throw new \RuntimeException(sprintf('Unable to guess the type for "%s" property.', $property));
                }
                $typeOptions = $guessedType->getOptions();
                $repository = $this->om->getRepository($typeOptions['class']);
                $transformer->setRepository($repository);
            });
        } else {
            $repository = $this->om->getRepository($options['class']);
            $transformer->setRepository($repository);
        }

        $transformer->setIdentifier($options['identifier']);
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver
            ->setDefaults([
                'class'    => null,
                'identifier' => 'id',
            ])
            ->setAllowedTypes('class',      ['null', 'string'])
            ->setAllowedTypes('identifier', 'string')
        ;
    }

    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return 'hidden';
    }

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


<?php

/*
 * This file is part of the Pondtunes package.
 *
 * (c) Marcus Stöhr <dafish@soundtrack-board.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Pond\Tunes;

use Pond\Tunes\ResultSet;
use HttpAdapter\HttpAdapterInterface;

abstract class Tunes
{
    /**
     * @var array
     */
    private $results = array();

    /**
     * @var integer
     */
    private $resultCount = 0;

    /**
     * Properties with default values
     *
     * @var array
     */
    private $defaultOptions = array(
        'country'   => 'us',
        'language'  => '',
        'mediaType' => '',
        'entity'    => array(self::MEDIATYPE_ALL => 'album'),
        'attribute' => '',
        'limit'     => 100,
        'callback'  => '',
        'version'   => 2,
        'explicit'  => 'yes'
    );

    /**
     * List of countries where the iTunes store is available
     *
     * @var array
     */
    protected static $countryList = array(
        'ae', 'ag', 'ai', 'al', 'am', 'ao', 'ar', 'at', 'au', 'az',
        'bb', 'be', 'bf', 'bg', 'bh', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bw', 'by', 'bz',
        'ca', 'cg', 'ch', 'cl', 'cn', 'co', 'cr', 'cv', 'cy', 'cz',
        'de', 'dk', 'dm', 'do', 'dz',
        'ec', 'ee', 'eg', 'es',
        'fi', 'fj', 'fm', 'fr',
        'gb', 'gd', 'gh', 'gm', 'gr', 'gt', 'gw', 'gy',
        'hk', 'hn', 'hr', 'hu',
        'id', 'ie', 'il', 'in', 'is', 'it',
        'jm', 'jo', 'jp',
        'ke', 'kg', 'kh', 'kn', 'kr', 'kw', 'ky', 'kz',
        'la', 'lb', 'lc', 'lk', 'lr', 'lt', 'lu', 'lv',
        'md', 'mg', 'mk', 'ml', 'mn', 'mo', 'mr', 'ms', 'mt', 'mu', 'mw', 'mx', 'my', 'mz',
        'na', 'ne', 'ng', 'ni', 'nl', 'np', 'no', 'nz',
        'om',
        'pa', 'pe', 'pg', 'ph', 'pk', 'pl', 'pt', 'pw', 'py',
        'qa',
        'ro', 'ru',
        'sa', 'sb', 'sc', 'se', 'sg', 'si', 'sk', 'sl', 'sn', 'sr', 'st', 'sv', 'sz',
        'tc', 'td', 'th', 'tj', 'tm', 'tn', 'tr', 'tt', 'tw', 'tz',
        'ua', 'ug', 'us', 'uy', 'uz',
        'vc', 've', 'vg', 'vn',
        'ye',
        'za', 'zw'
    );

    /**
     * List of available languages for the result
     *
     * @var array
     */
    protected $languageList = array('en_us', 'ja_jp');

    const MEDIATYPE_ALL        = 'all';
    const MEDIATYPE_PODCAST    = 'podcast';
    const MEDIATYPE_MUSIC      = 'music';
    const MEDIATYPE_MUSICVIDEO = 'musicVideo';
    const MEDIATYPE_AUDIOBOOK  = 'audiobook';
    const MEDIATYPE_SHORTFILM  = 'shortFilm';
    const MEDIATYPE_SOFTWARE   = 'software';
    const MEDIATYPE_TVSHOW     = 'tvShow';
    const MEDIATYPE_MOVIE      = 'movie';
    const MEDIATYPE_EBOOK      = 'ebook';

    /**
     * List of available media types
     *
     * @var $_mediaTypes array
     */
    protected $mediaTypes = array(
        self::MEDIATYPE_ALL,
        self::MEDIATYPE_AUDIOBOOK,
        self::MEDIATYPE_MOVIE,
        self::MEDIATYPE_MUSIC,
        self::MEDIATYPE_MUSICVIDEO,
        self::MEDIATYPE_PODCAST,
        self::MEDIATYPE_SHORTFILM,
        self::MEDIATYPE_SOFTWARE,
        self::MEDIATYPE_TVSHOW
    );

    /**
     * List of all available entity types
     *
     * @var array
     */
    protected $entityList = array(
        self::MEDIATYPE_MOVIE => array(
            'movieArtist',
            'movie',
        ),
        self::MEDIATYPE_PODCAST => array(
            'podcastAuthor',
            'podcast',
        ),
        self::MEDIATYPE_MUSIC => array(
            'musicArtist',
            'musicTrack',
            'album',
            'musicVideo',
            'mix',
            'song',
        ),
        self::MEDIATYPE_MUSICVIDEO => array(
            'musicArtist',
            'musicVideo',
        ),
        self::MEDIATYPE_AUDIOBOOK => array(
            'audiobookAuthor',
            'audiobook',
        ),
        self::MEDIATYPE_SHORTFILM => array(
            'shortFilmArtist',
            'shortFilm',
        ),
        self::MEDIATYPE_TVSHOW => array(
            'tvEpisode',
            'tvSeason',
        ),
        self::MEDIATYPE_SOFTWARE => array(
            'software',
            'iPadSoftware',
            'macSoftware',
        ),
        self::MEDIATYPE_EBOOK => array(
            'ebook',
        ),
        self::MEDIATYPE_ALL => array(
            'movie',
            'album',
            'allArtist',
            'podcast',
            'musicVideo',
            'mix',
            'audiobook',
            'tvSeason',
            'allTrack',
        ),
    );

    /**
     * List of possible attributes
     *
     * @var array
     */
    protected $attributesTypes = array(
        self::MEDIATYPE_MOVIE => array(
            'actorTerm',
            'genreIndex',
            'artistTerm',
            'shortFilmTerm',
            'producerTerm',
            'ratingTerm',
            'directorTerm',
            'releaseYearTerm',
            'featureFilmTerm',
            'movieArtistTerm',
            'movieTerm',
            'ratingIndex',
            'descriptionTerm'
        ),
        self::MEDIATYPE_PODCAST => array(
            'titleTerm',
            'languageTerm',
            'authorTerm',
            'genreIndex',
            'artistTerm',
            'ratingIndex',
            'keywordsTerm',
            'descriptionTerm'
        ),
        self::MEDIATYPE_MUSIC => array(
            'mixTerm',
            'genreIndex',
            'artistTerm',
            'composerTerm',
            'albumTerm',
            'ratingIndex',
            'songTerm',
            'musicTrackTerm'
        ),
        self::MEDIATYPE_MUSICVIDEO => array(
            'genreIndex',
            'artistTerm',
            'albumTerm',
            'ratingIndex',
            'songTerm'
        ),
        self::MEDIATYPE_AUDIOBOOK => array(
            'titleTerm',
            'authorTerm',
            'genreIndex',
            'ratingIndex'
        ),
        self::MEDIATYPE_SHORTFILM => array(
            'genreIndex',
            'artistTerm',
            'shortFilmTerm',
            'ratingIndex',
            'descriptionTerm'
        ),
        self::MEDIATYPE_SOFTWARE => array(
            'softwareDeveloper'
        ),
        self::MEDIATYPE_TVSHOW => array(
            'genreIndex',
            'tvEpisodeTerm',
            'showTerm',
            'tvSeasonTerm',
            'ratingIndex',
            'descriptionTerm'
        ),
        self::MEDIATYPE_ALL => array(
            'actorTerm',
            'languageTerm',
            'allArtistTerm',
            'tvEpisodeTerm',
            'shortFilmTerm',
            'directorTerm',
            'releaseYearTerm',
            'titleTerm',
            'featureFilmTerm',
            'ratingIndex',
            'keywordsTerm',
            'descriptionTerm',
            'authorTerm',
            'genreIndex',
            'mixTerm',
            'allTrackTerm',
            'artistTerm',
            'composerTerm',
            'tvSeasonTerm',
            'producerTerm',
            'ratingTerm',
            'songTerm',
            'movieArtistTerm',
            'showTerm',
            'movieTerm',
            'albumTerm'
        )
    );

    /**
     * @var string
     */
    const RESULT_JSON = 'json';

    /**
     * @var string
     */
    const RESULT_ARRAY = 'array';

    /**
     * @var array
     */
    protected $resultFormats = array(
        self::RESULT_ARRAY,
        self::RESULT_JSON
    );

    /**
     * @var string
     */
    protected $resultFormat = self::RESULT_JSON;

    /**
     * @var string
     */
    protected $rawRequestUrl = '';

    /**
     * @var array
     */
    protected $explicitTypes = array('yes', 'no');

    /**
     * @var HttpAdapterInterface
     */
    protected $httpClient;

    /**
     * Constructor
     *
     * @param HttpAdapterInterface $client
     * @param array                $options
     */
    public function __construct(HttpAdapterInterface $client, array $options = array())
    {
        $this->httpClient = $client;

        if (!empty($options)) {
            $this->setOptions($options);
        }
    }

    /**
     * @return HttpAdapterInterface
     */
    public function getHttpClient()
    {
        return $this->httpClient;
    }

    /**
     * @param HttpAdapterInterface $client
     *
     * @return Tunes
     */
    public function setHttpClient(HttpAdapterInterface $client)
    {
        $this->httpClient = $client;

        return $this;
    }

    /**
     * Return assembled request url
     *
     * @abstract
     *
     * @return string
     */
    abstract public function getRawRequestUrl();

    /**
     * Set configuration
     *
     * @param array $options
     *
     * @return void
     */
    public function setOptions($options)
    {
        foreach ($options as $key => $value) {
            $option = str_replace('_', ' ', strtolower($key));
            $option = str_replace(' ', '', ucwords($option));
            $method = 'set' . $option;

            if (method_exists($this, $method)) {
                $this->$method($value);
            }
        }
    }

    /**
     * Query the service and save result
     *
     * @throws \BadMethodCallException
     * @throws \RuntimeException
     *
     * @return ResultSet|string
     */
    public function request()
    {
        if ('' !== $this->defaultOptions['callback']) {
            $msg = 'Cannot run query when callback is set. Get query using getRawRequestUrl().';
            throw new \BadMethodCallException($msg);
        }

        $this->buildSpecificRequestUri();

        try {
            if (null === $content = $this->getHttpClient()->getContent($this->getRawRequestUrl())) {
                throw new \RuntimeException('The request was not successful.');
            }
        } catch (\RuntimeException $e) {
            throw new \RuntimeException('The request was not successful.');
        }

        if (self::RESULT_ARRAY === $this->resultFormat) {
            $resultSet = json_decode($content, true);
            $resultSet = new ResultSet($resultSet['results']);

            return $resultSet;
        } else {
            // convert JSON-string to array
            $jsonString = json_decode($content);

            $this->resultCount = (integer) $jsonString->resultCount;
            $this->results     = json_encode($jsonString->results);

            return $this->results;
        }
    }

    /**
     * Build the request uri for all common parameters
     *
     * @return string
     */
    protected function buildRequestUri()
    {
        $requestParameters = array();

        // add entity
        if (!empty($this->defaultOptions['entity'])) {
            $tmp = array_keys($this->defaultOptions['entity']);
            $key = array_pop($tmp);
            $requestParameters[] = 'entity=' . $this->defaultOptions['entity'][$key];
        }

        // add media type
        if (!empty($this->defaultOptions['mediaType'])) {
            $requestParameters[] = 'media=' . $this->defaultOptions['mediaType'];
        }

        // add attribute
        if (!empty($this->defaultOptions['attribute'])) {
            $requestParameters[] = 'attribute=' . $this->defaultOptions['attribute'];
        }

        // add language
        if (!empty($this->defaultOptions['language'])) {
            $requestParameters[] = 'lang=' . $this->defaultOptions['language'];
        }

        // add limit
        if ($this->defaultOptions['limit'] <> 100) {
            $requestParameters[] = 'limit=' . $this->defaultOptions['limit'];
        }

        // add country
        if ($this->defaultOptions['country'] != 'us') {
            $requestParameters[] = 'country=' . $this->defaultOptions['country'];
        }

        // add callback
        if (!empty($this->defaultOptions['callback'])) {
            $requestParameters[] = 'callback=' . $this->defaultOptions['callback'];
        }

        // add version
        if ($this->defaultOptions['version'] <> 2) {
            $requestParameters[] = 'version=' . $this->defaultOptions['version'];
        }

        // add explicity
        if ($this->defaultOptions['explicit'] != 'yes') {
            $requestParameters[] = 'explicit=' . $this->defaultOptions['explicit'];
        }

        return implode('&', $requestParameters);
    }

    /**
     * Builds the request uri for parameters specifically used in:
     *     - Lookup
     *     - Search
     */
    abstract protected function buildSpecificRequestUri();

    /**
     * Magic method for retrieving properties
     *
     * @throws \BadMethodCallException
     *
     * @return string
     */
    public function getResults()
    {
        if (self::RESULT_JSON != $this->resultFormat) {
            throw new \BadMethodCallException("Cannot call '" . __METHOD__ . "' when using Array");
        }

        return $this->results;
    }

    /**
     * Get the result count from query()
     *
     * @throws \BadMethodCallException
     *
     * @return integer
     */
    public function getResultCount()
    {
        if (self::RESULT_JSON != $this->resultFormat) {
            throw new \BadMethodCallException("Cannot call '" . __METHOD__ . "' when using Array");
        }

        return $this->resultCount;
    }

    /**
     * Magic method for accessing properties
     *
     * @param string $key
     *
     * @return mixed
     */
    public function __get($key)
    {
        if (isset($this->defaultOptions[$key])) {
            return $this->defaultOptions[$key];
        }

        return null;
    }

    /**
     * Return a list of all countries
     *
     * @return array
     */
    public static function getCountries()
    {
        return self::$countryList;
    }

    /**
     * Set country
     *
     * @param string $country
     *
     * @return Tunes
     */
    public function setCountry($country = 'us')
    {
        if (in_array($country, self::$countryList)) {
            $this->defaultOptions['country'] = $country;
        }

        return $this;
    }

    /**
     * Sets the language for the result
     *
     * @param string $language
     *
     * @return Tunes
     */
    public function setLanguage($language = 'en_us')
    {
        if (in_array($language, $this->languageList)) {
            $this->defaultOptions['language'] = $language;
        }

        return $this;
    }

    /**
     * Set the used mediatype
     *
     * @param string $mediatype
     *
     * @return Tunes
     */
    public function setMediaType($mediatype = self::MEDIATYPE_ALL)
    {
        if (in_array($mediatype, $this->mediaTypes)) {
            $this->defaultOptions['mediaType'] = $mediatype;
        }

        return $this;
    }

    /**
     * Set the result format
     *
     * @param string $format
     *
     * @return Tunes
     */
    public function setResultFormat($format = self::RESULT_ARRAY)
    {
        if (in_array($format, $this->resultFormats)) {
            $this->resultFormat = $format;
        }

        return $this;
    }

    /**
     * Get the result format
     *
     * @return string
     */
    public function getResultFormat()
    {
        return $this->resultFormat;
    }

    /**
     * Set the limit
     *
     * @param integer $limit
     *
     * @throws \OutOfBoundsException
     *
     * @return Tunes
     */
    public function setLimit($limit = 100)
    {
        // the limit must be within the boundaries of the service
        if ($limit <= 0 || $limit > 200) {
            throw new \OutOfBoundsException('The limit must be within 0 and 200.');
        }

        $this->defaultOptions['limit'] = (integer) $limit;

        return $this;
    }

    /**
     * Set the entity for the result
     *
     * @param array $entity
     *
     * @throws \InvalidArgumentException
     *
     * @return Tunes
     */
    public function setEntity($entity = array())
    {
        // check if only one entry is given
        if (count($entity) <> 1) {
            throw new \InvalidArgumentException('Must be set with one key/value-pair!');
        }

        // fetch key from parameter
        $_tmp = array_keys($entity);
        $key  = array_pop($_tmp);

        // check if the key of the given array exists
        if (array_key_exists($key, $this->entityList)) {
            // check if value exists for key
            if (in_array($entity[$key], $this->entityList[$key])) {
                $this->defaultOptions['entity'] = $entity;
            }
        }

        return $this;
    }

    /**
     * Set the attribute you want to search in the iTunes
     * store.
     * This is relative to the set media type.
     *
     * @param string $attribute
     *
     * @throws \InvalidArgumentException
     */
    public function setAttribute($attribute = '')
    {
        if ('' === $this->defaultOptions['mediaType']) {
            throw new \InvalidArgumentException('Attribute relates to media type but no media type is set.');
        }

        // check if the attribute is in the set of attributes for media type
        if (in_array($attribute, $this->attributesTypes[$this->defaultOptions['mediaType']])) {
            $this->defaultOptions['attribute'] = $attribute;
        } else {
            throw new \InvalidArgumentException('Attribute is not in the set of attributes for this media type.');
        }
    }

    /**
     * Set a custom callback function you want to use
     * when returning search results.
     * This setting works only when result format is set to
     * RESULT_JSON
     *
     * @param string $callback
     *
     * @throws \BadMethodCallException
     */
    public function setCallback($callback = '')
    {
        if (self::RESULT_JSON !== $this->getResultFormat()) {
            throw new \BadMethodCallException('Callback can only be set with RESULT_JSON');
        }

        $this->defaultOptions['callback'] = $callback;
    }

    /**
     * Set the flag indicating whether or not you want to include
     * explicit content in your search result
     *
     * @param string $setting
     *
     * @return Tunes
     */
    public function setExplicit($setting = 'yes')
    {
        if (in_array($setting, $this->explicitTypes)) {
            $this->defaultOptions['explicit'] = $setting;
        }

        return $this;
    }

    /**
     * Set the iTunes Store search result key version you want
     * to receive back from your search.
     *
     * @param integer $version
     *
     * @return Tunes
     */
    public function setVersion($version = 2)
    {
        if (in_array($version, array(1, 2))) {
            $this->defaultOptions['version'] = $version;
        }

        return $this;
    }

    /**
     * Get the Uri set to query the service
     *
     * @abstract
     *
     * @return string
     */
    abstract public function getUri();
}


<?php namespace RestGalleries\APIs;

use Illuminate\Support\Fluent;
use RestGalleries\Http\Guzzle\GuzzleRequest;
use RestGalleries\Http\RequestAdapter;
use RestGalleries\Interfaces\PhotoAdapter;

/**
 * Normalizes all APIs photo classes under one single interface, and simplifies the reuse of public methods.
 */
abstract class ApiPhoto implements PhotoAdapter
{
    /**
     * Url to the API REST. the base for all requests.
     *
     * @var string
     */
    protected $endPoint;

    /**
     * It will store the plugins of request client, such as cache and auth.
     *
     * @var array
     */
    protected $plugins = [];

    /**
     * Gets IDs of photos, seeks and gets the photo and makes an array to send it back.
     *
     * @param  string $galleryId
     * @return array|null
     */
    public function all($galleryId)
    {
        return $this->getPhotos($galleryId);
    }

    /**
     * Returns all photo currently available in a gallery.
     *
     * @return array|null
     */
    protected function getPhotos($galleryId)
    {
        if (! is_null($ids = $this->fetchIds($galleryId))) {
            return array_map([$this, 'getPhoto'], $ids);
        }
    }

    /**
     * Makes the request to get the IDs of gallery photos and return it as array.
     *
     * @param  string $galleryId
     * @return array|null
     */
    abstract protected function fetchIds($galleryId);

    /**
     * Gets the photo and returns an object.
     *
     * @param  string $id
     * @return \Illuminate\Support\Fluent|null
     */
    public function find($id)
    {
        return $this->getPhoto($id);
    }

    /**
     * Fetch a gallery as array and returns a object ArrayAccess-type with that data.
     *
     * @param  string $id
     * @return \Illuminate\Support\Fluent|null
     */
    protected function getPhoto($id)
    {
        if (! is_null($photo = $this->fetchPhoto($id))) {
            return new Fluent($photo);
        }
    }

    /**
     * Here are made the necessary requests to get info of photography, turn it into an array, and send it back.
     *
     * @param  string $id
     * @return array|null
     */
    abstract protected function fetchPhoto($id);

    /**
     * Object builder to create and use the Http request client.
     * In this case, is set up as default Guzzle Http client.
     *
     * @param  \RestGalleries\Http\RequestAdapter $request
     * @return \RestGalleries\Http\RequestAdapter
     */
    public function newRequest(RequestAdapter $request = null)
    {
        if (empty($request)) {
            $request = new GuzzleRequest;
        }

        $request = $request::init($this->endPoint);

        if (! empty($this->plugins)) {
            array_walk($this->plugins, [$request, 'addPlugin']);
        }

        return $request;

    }

    /**
     * Stores the request plugins to add them to the request client.
     *
     * @param  \RestGalleries\Http\Plugins\RequestPluginAdapter $plugin
     * @return void
     */
    public function addPlugin($plugin)
    {
        $this->plugins[] = $plugin;
    }

}