<?php

namespace DIMicroKernel;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use ProjectServiceContainer as CachedContainer;
use DIMicroKernel\Exception\Exception;

/**
 * This micro-kernel can be used for bootstrapping almost any PHP application.
 * It can be extended to customize it for a specific purpose, e.g. command line applications.
 *
 * @see https://github.com/symlex/di-microkernel/blob/master/README.md
 * @author Michael Mayer <michael@liquidbytes.net>
 * @license MIT
 */
class Kernel
{
    /** @var ContainerBuilder */
    protected $container;

    protected $environment;
    protected $defaultSubEnvironment = 'local';

    protected $debug = false;
    protected $name;
    protected $version = '1.0';
    protected $appInitialized = false;

    protected $appPath;
    protected $basePath;
    protected $storagePath;
    protected $srcPath;
    protected $configPath;
    protected $logPath;
    protected $cachePath;
    protected $charSet;

    /**
     * Kernel constructor.
     *
     * @param string $environment e.g. console tells the kernel to load the service config from config/console.yml
     * @param string $appPath e.g. __DIR__
     * @param bool $debug service container is not cached, if true
     */
    public function __construct(string $environment = 'app', string $appPath = '', bool $debug = false)
    {
        $this->setEnvironment($environment);
        $this->setAppPath($appPath);
        $this->setDebug($debug);

        $this->init();
    }

    /**
     * Executes the app configured as 'app' in the service container
     *
     * @throws \Exception
     * @return mixed
     */
    public function run()
    {
        $arguments = func_get_args();

        return $this->__call('run', $arguments);
    }

    /**
     * Returns the container instance (automatically creates an instance, if none exists)
     *
     * @return Container
     */
    public function getContainer(): Container
    {
        if (!$this->container) {
            $this->boot();
        }

        return $this->container;
    }

    public function setContainer(Container $container)
    {
        if ($this->container instanceof Container) {
            throw new Exception('Container already set');
        }

        $this->container = $container;
    }

    /**
     * Returns true, if kernel is in debug mode
     *
     * @return bool
     */
    public function isDebug(): bool
    {
        return $this->debug;
    }

    public function setDebug(bool $debug)
    {
        $this->debug = $debug;
    }

    /**
     * Returns application name, e.g. App
     *
     * @return string
     */
    public function getName(): string
    {
        if (null === $this->name) {
            $this->setName(ucfirst(preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->getAppPath()))));
        }

        return $this->name;
    }

    public function setName(string $appName)
    {
        $this->name = $appName;
    }

    /**
     * Returns application version, e.g. 1.0
     *
     * @return string
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    public function setVersion(string $appVersion)
    {
        $this->version = $appVersion;
    }

    /**
     * Returns environment name, e.g. console, web,...
     *
     * @return string
     */
    public function getEnvironment(): string
    {
        return $this->environment;
    }

    public function setEnvironment(string $environment)
    {
        $this->environment = $environment;
    }

    /**
     * Returns sub environment name, e.g. local, test, production,...
     *
     * Use for overwriting the default service configuration e.g. with console.local.yml
     *
     * @return string
     */
    public function getSubEnvironment(): string
    {
        $result = $this->defaultSubEnvironment;

        if ($this->container && $this->container->hasParameter('app.sub_environment')) {
            $result = (string)$this->container->getParameter('app.sub_environment');
        }

        return $result;
    }

    public function setSubEnvironment(string $subEnvironment)
    {
        $this->defaultSubEnvironment = $subEnvironment;
    }

    /**
     * Returns character set, e.g. UTF-8
     *
     * @return string
     */
    public function getCharset(): string
    {
        if ($this->charSet == '') {
            $this->setCharset('UTF-8');
        }

        return $this->charSet;
    }

    public function setCharset(string $charSet)
    {
        $this->charSet = $charSet;
    }

    /**
     * Returns app path, e.g. /var/www/app
     *
     * @return string
     */
    public function getAppPath(): string
    {
        if ($this->appPath == '') {
            $r = new \ReflectionObject($this);
            $this->setAppPath(str_replace('\\', '/', dirname($r->getFileName())));
        }

        return $this->appPath;
    }

    public function setAppPath(string $appPath)
    {
        $this->appPath = $appPath;
    }

    /**
     * Returns config path, e.g. /var/www/app/config
     *
     * @return string
     */
    public function getConfigPath(): string
    {
        if ($this->configPath == '') {
            $this->setConfigPath($this->getAppPath() . '/config');
        }

        return $this->configPath;
    }

    public function setConfigPath(string $configPath)
    {
        $this->configPath = $configPath;
    }

    /**
     * Returns base path, e.g. /var/www
     *
     * @return string
     */
    public function getBasePath(): string
    {
        if ($this->basePath == '') {
            $this->setBasePath(realpath($this->getAppPath() . '/..'));
        }

        return $this->basePath;
    }

    public function setBasePath(string $basePath)
    {
        $this->basePath = $basePath;
    }

    /**
     * Returns storage path, e.g. /var/www/storage
     *
     * @return string
     */
    public function getStoragePath(): string
    {
        if ($this->storagePath == '') {
            $this->setStoragePath($this->getBasePath() . '/storage');
        }

        return $this->storagePath;
    }

    public function setStoragePath(string $storagePath)
    {
        $this->storagePath = $storagePath;
    }

    /**
     * Returns log path, e.g. /var/www/storage/logs
     *
     * @return string
     */
    public function getLogPath(): string
    {
        if ($this->logPath == '') {
            $this->setLogPath($this->getStoragePath() . '/log');
        }

        return $this->logPath;
    }

    public function setLogPath(string $logPath)
    {
        $this->logPath = $logPath;
    }

    /**
     * Returns app path, e.g. /var/www/storage/cache
     *
     * @return string
     */
    public function getCachePath(): string
    {
        if ($this->cachePath == '') {
            $this->setCachePath($this->getStoragePath() . '/cache');
        }

        return $this->cachePath;
    }

    public function setCachePath(string $cachePath)
    {
        $this->cachePath = $cachePath;
    }

    /**
     * Returns source code path e.g. /var/www/src
     *
     * @return string
     */
    public function getSrcPath(): string
    {
        if ($this->srcPath == '') {
            $this->setSrcPath($this->getBasePath() . '/src');
        }

        return $this->srcPath;
    }

    public function setSrcPath(string $srcPath)
    {
        $this->srcPath = $srcPath;
    }

    /**
     * Returns app config parameters like app.name, app.version and app.base_path as array
     *
     * @return array
     */
    public function getContainerParameters(): array
    {
        $result = array(
            'app.name' => $this->getName(),
            'app.version' => $this->getVersion(),
            'app.environment' => $this->getEnvironment(),
            'app.sub_environment' => $this->getSubEnvironment(),
            'app.debug' => $this->isDebug(),
            'app.charset' => $this->getCharset(),
            'app.path' => $this->getAppPath(),
            'app.config_path' => $this->getConfigPath(),
            'app.base_path' => $this->getBasePath(),
            'app.storage_path' => $this->getStoragePath(),
            'app.log_path' => $this->getLogPath(),
            'app.cache_path' => $this->getCachePath(),
            'app.src_path' => $this->getSrcPath(),
        );

        return $result;
    }

    /**
     * Returns container cache filename, e.g. /var/www/storage/cache/container_8a4ba7589ab6aba0b5.php
     *
     * @return string
     */
    public function getContainerCacheFilename(): string
    {
        $environment = $this->getEnvironment();
        $appPath = $this->getAppPath();

        $filename = $this->getCachePath() . '/container_' . md5($environment . $appPath) . '.php';

        return $filename;
    }

    /**
     * Returns true, if the container.cache parameter does not exist or is false
     *
     * @return bool
     */
    public function containerIsCacheable(): bool
    {
        $result = true; // container is cacheable by default

        if ($this->container->hasParameter('container.cache')) {
            $result = (bool)$this->container->getParameter('container.cache');
        }

        return $result;
    }

    /**
     * Calls a method of the app service
     *
     * @throws \Exception
     * @param $name
     * @param $arguments
     * @return mixed
     */
    public function __call($name, $arguments)
    {
        $application = $this->getApplication();

        return call_user_func_array(array($application, $name), $arguments);
    }

    /**
     * Optional code to be executed, after the kernel constructor
     */
    protected function init()
    {
        // Optional
    }

    /**
     * Returns true, if a container instance exists
     *
     * @return bool
     */
    protected function hasBooted(): bool
    {
        $result = $this->container instanceof Container;

        return $result;
    }

    /**
     * Creates a container instance
     */
    protected function boot()
    {
        if ($this->hasBooted()) return; // Nothing to do

        if ($this->debug) {
            $this->setContainer(new ContainerBuilder(new EnvPlaceholderParameterBag($this->getContainerParameters())));
            $this->loadContainerConfiguration();
            $this->container->compile(true);
        } else {
            $filename = $this->getContainerCacheFilename();

            if (file_exists($filename)) {
                require_once($filename);
                $this->setContainer(new CachedContainer());
            } else {
                $this->setContainer(new ContainerBuilder(new EnvPlaceholderParameterBag($this->getContainerParameters())));
                $this->loadContainerConfiguration();
                $this->container->compile(true);

                if ($this->containerIsCacheable()) {
                    $dumper = new PhpDumper($this->container);
                    file_put_contents($filename, $dumper->dump());
                }
            }
        }
    }

    /**
     * Loads the container config from YAML files in the config directory
     */
    protected function loadContainerConfiguration()
    {
        $configPath = $this->getConfigPath();
        $environment = $this->getEnvironment();

        $loader = new YamlFileLoader($this->container, new FileLocator($configPath));

        if (file_exists($configPath . '/' . $environment . '.yml')) {
            $loader->load($environment . '.yml');
        }

        $subEnvironment = $this->getSubEnvironment();

        if (file_exists($configPath . '/' . $environment . '.' . $subEnvironment . '.yml')) {
            $loader->load($environment . '.' . $subEnvironment . '.yml');
        }
    }

    /**
     * Returns true, if setUp() was not called already
     *
     * @return bool
     */
    protected function appIsUninitialized(): bool
    {
        return !$this->appInitialized;
    }

    /**
     * Returns the app service from the container and calls setUp() once, if not done yet
     *
     * @throws \Exception
     * @return object
     */
    protected function getApplication()
    {
        if ($this->appIsUninitialized()) {
            $this->setUp();
        }

        $result = $this->getContainer()->get('app');

        $this->appInitialized = true;

        return $result;
    }

    /**
     * Optional code to be executed, before the app instance is created
     */
    protected function setUp()
    {
        // Optional
    }
}


<?php
/**
 * Contains the BaseTable class definition.
 *
 * @author    Iron Bound Designs
 * @since     2.0
 * @license   MIT
 * @copyright Iron Bound Designs, 2016.
 */

namespace IronBound\DB\Table;
use Doctrine\Common\Inflector\Inflector;

/**
 * Class BaseTable
 * @package IronBound\DB\Table
 */
abstract class BaseTable implements Table {

	/**
	 * @inheritDoc
	 */
	public function get_creation_sql( \wpdb $wpdb ) {

		$tn = $this->get_table_name( $wpdb );

		$sql = "CREATE TABLE {$tn} (\n";
		$sql .= $this->get_columns_definition();

		if ( $keys = $this->get_keys_definition() ) {
			$sql .= ",\n{$keys}";
		}

		$sql .= "\n) {$wpdb->get_charset_collate()};";

		return $sql;
	}

	/**
	 * Get the column definitions.
	 *
	 * @since 2.0
	 *
	 * @return string
	 */
	protected function get_columns_definition() {
		return implode( ",\n", $this->get_columns() );
	}

	/**
	 * Get the keys definition.
	 *
	 * @since 2.0
	 *
	 * @return string
	 */
	protected function get_keys_definition() {
		return implode( ",\n", $this->get_keys() );
	}

	/**
	 * Get all keys on the table.
	 *
	 * @since 2.0
	 *
	 * @return array
	 */
	protected function get_keys() {

		$keys = array();

		if ( $this->get_primary_key() ) {
			$keys[] = "PRIMARY KEY  ({$this->get_primary_key()})";
		}

		return $keys;
	}

	/**
	 * Build the column name for a table.
	 *
	 * @since 2.0
	 *
	 * @param Table $table
	 *
	 * @return string
	 */
	protected function build_column_name_for_table( Table $table ) {

		$basename  = $this->class_basename( $table );
		$tableized = Inflector::tableize( $basename );

		$parts         = explode( '_', $tableized );
		$last_plural   = array_pop( $parts );
		$last_singular = Inflector::singularize( $last_plural );
		$parts[]       = $last_singular;

		$column_name = implode( '_', $parts );
		$column_name .= '_' . $table->get_primary_key();

		return $column_name;
	}

	/**
	 * Get the basename for a class.
	 *
	 * @since 2.0
	 *
	 * @param string|object $class
	 *
	 * @return string
	 */
	protected function class_basename( $class ) {

		$class = is_object( $class ) ? get_class( $class ) : $class;

		return basename( str_replace( '\\', '/', $class ) );
	}
}


<?php
/**
 * ManyToMany relation.
 *
 * @author    Iron Bound Designs
 * @since     2.0
 * @license   MIT
 * @copyright Iron Bound Designs, 2016.
 */

namespace IronBound\DB\Relations;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection as DoctrineCollection;
use IronBound\DB\Collection;
use IronBound\DB\Model;
use IronBound\DB\Query\FluentQuery;
use IronBound\DB\Query\Tag\Where;
use IronBound\DB\Saver\ModelSaver;
use IronBound\DB\Table\Association\AssociationTable;
use IronBound\WPEvents\GenericEvent;

/**
 * Class ManyToMany
 *
 * @package IronBound\DB\Relations
 */
class ManyToMany extends Relation {

	/**
	 * @var AssociationTable
	 */
	protected $association;

	/**
	 * @var string
	 */
	protected $other_column;

	/**
	 * @var string
	 */
	protected $primary_column;

	/**
	 * @var string
	 */
	protected $other_attribute;

	/**
	 * @var string
	 */
	protected $join_on;

	/**
	 * ManyToMany constructor.
	 *
	 * @param string           $related         Related class name.
	 * @param Model            $parent          Parent object.
	 * @param AssociationTable $association     Association table.
	 * @param string           $attribute       Attribute name on this model.
	 * @param string           $other_attribute Attribute name of the corresponding relation on the related model.
	 */
	public function __construct( $related, Model $parent, AssociationTable $association, $attribute, $other_attribute = '' ) {

		$saver = $association->get_saver();

		if ( $related && $saver instanceof ModelSaver ) {
			$saver->set_model_class( $related );
		}

		parent::__construct( $related, $parent, $attribute, $saver );

		$this->association = $association;

		$this->other_column   = $association->get_other_column_for_table( $parent::table() );
		$this->primary_column = $association->get_primary_column_for_table( $parent::table() );

		$this->other_attribute = $other_attribute;

		if ( $related ) {
			$this->join_on = $related::table()->get_primary_key();
		}
	}

	/**
	 * Make a FluentQuery object.
	 *
	 * @since 2.0
	 *
	 * @param bool $model_class Set the Model class if possible.
	 *
	 * @return FluentQuery
	 */
	protected function make_query_object( $model_class = false ) {

		$query = call_user_func( array( $this->related_model, 'query_with_no_global_scopes' ) );

		if ( ! $model_class ) {
			$query->set_model_class( null );
		}

		return $query;
	}

	/**
	 * @inheritDoc
	 */
	protected function fetch_results() {

		$query = $this->make_query_object( true );
		$query->distinct();

		$parent = $this->parent;
		$column = $this->other_column;

		$query->join( $this->association, $this->join_on, $this->primary_column, '=',
			function ( FluentQuery $query ) use ( $parent, $column ) {
				$query->where( $column, true, $parent->get_pk() );
			} );

		$results = $query->results( $this->saver );
		$results->keep_memory();

		return $results;
	}

	/**
	 * @inheritDoc
	 */
	protected function register_events( Collection $results ) {

		$related = $this->related_model;

		$self            = $this;
		$parent          = $this->parent;
		$attribute       = $this->attribute;
		$other_attribute = $this->other_attribute;
		$saver           = $this->saver;

		$related::saved( function ( GenericEvent $event ) use ( $parent, $results, $attribute, $other_attribute, $saver ) {

			/** @var Model $model */
			$model = $event->get_subject();

			if ( ! $model->is_relation_loaded( $other_attribute ) ) {
				return;
			}

			/** @var Collection $relation */
			$relation = $model->get_attribute( $other_attribute );

			$added = $relation->get_added();

			if ( $added->exists( function ( $key, $model ) use ( $saver, $parent ) {
				return $saver->get_pk( $model ) === $parent->get_pk();
			} )
			) {
				$results->dont_remember( function ( Collection $collection ) use ( $model ) {
					$collection->add( $model );
				} );
			}

			$removed = $relation->get_removed();

			if ( $removed->exists( function ( $key, $model ) use ( $saver, $parent ) {
				return $saver->get_pk( $model ) === $parent->get_pk();
			} )
			) {
				$results->dont_remember( function ( Collection $collection ) use ( $model ) {
					$collection->remove_model( $model->get_pk() );
				} );
			}

		} );

		$related::deleted( function ( GenericEvent $event ) use ( $self, $results ) {
			$results->remove_model( $event->get_subject()->get_pk() );
		} );
	}

	/**
	 * @inheritDoc
	 */
	protected function register_cache_events() {
		parent::register_cache_events();

		$related = $this->related_model;

		$parent          = $this->parent;
		$attribute       = $this->attribute;
		$other_attribute = $this->other_attribute;
		$cache_group     = $this->get_cache_group();
		$saver           = $this->saver;

		$related::saved( function ( GenericEvent $event ) use ( $parent, $attribute, $other_attribute, $cache_group, $saver ) {

			/** @var Model $model */
			$model = $event->get_subject();

			if ( ! $model->is_relation_loaded( $other_attribute ) ) {
				return;
			}

			$ids = wp_cache_get( $parent->get_pk(), $cache_group );

			/** @var Collection $relation */
			$relation = $model->get_attribute( $other_attribute );

			$added = $relation->get_added();

			if ( $added->exists( function ( $key, $model ) use ( $saver, $parent ) {
				return $saver->get_pk( $model ) === $parent->get_pk();
			} )
			) {
				$ids[] = $model->get_pk();
			}

			$removed = $relation->get_removed();

			if ( $removed->exists( function ( $key, $model ) use ( $saver, $parent ) {
				return $saver->get_pk( $model ) === $parent->get_pk();
			} )
			) {

				$i = array_search( $model->get_pk(), $ids );

				if ( $i !== false ) {
					unset( $ids[ $i ] );
				}
			}

			wp_cache_set( $parent->get_pk(), $ids, $cache_group );
		} );
	}

	/**
	 * @inheritDoc
	 */
	protected function load_collection_from_cache( array $cached, Model $for ) {
		$collection = parent::load_collection_from_cache( $cached, $for );
		$collection->keep_memory();

		return $collection;
	}

	/**
	 * Fetch results for eager loading.
	 *
	 * @since 2.0
	 *
	 * @param Model[]  $models
	 * @param callable $callback
	 *
	 * @return DoctrineCollection
	 */
	protected function fetch_results_for_eager_load( array $models, $callback = null ) {

		$cached = array();
		$found  = array();

		if ( $callback === null ) {
			foreach ( $models as $pk => $model ) {
				$result = $this->load_from_cache( $model );

				$result = $result instanceof Collection ? $result->toArray() : (array) $result;

				if ( ! $result ) {
					continue;
				}

				$found[] = $pk;

				foreach ( $result as $item ) {

					if ( empty( $item ) ) {
						continue;
					}

					$item_pk = $this->saver->get_pk( $item );

					$item = $item->to_array();

					$item[ $this->primary_column ] = $item_pk;
					$item[ $this->other_column ]   = $pk;

					$cached[] = $item;
				}
			}

			if ( count( $found ) === count( $models ) ) {
				return new ArrayCollection( $cached );
			}
		}

		$other_column = $this->other_column;

		$query = $this->make_query_object();
		$query->distinct();
		$query->select_all( false );

		$pks = array_keys( $models );
		$pks = array_diff( $pks, $found );

		$query->join( $this->association, $this->join_on, $this->primary_column, '=',
			function ( FluentQuery $query ) use ( $other_column, $models, $pks ) {
				$query->where( $other_column, true, $pks );
			}, 'LEFT' );

		if ( $callback ) {
			$callback( $query );
		}

		$results = $query->results()->toArray();

		return new ArrayCollection( array_merge( $results, $cached ) );
	}

	/**
	 * @inheritDoc
	 */
	public function eager_load( array $models, $callback = null ) {

		$results   = $this->fetch_results_for_eager_load( $models, $callback );
		$memory    = (bool) $this->keep_synced;
		$attribute = $this->attribute;
		$related   = array();

		$relationship_map = array();

		foreach ( $results as $result ) {

			$attributes = $result;
			unset( $attributes[ $this->primary_column ] );
			unset( $attributes[ $this->other_column ] );

			$model = $this->saver->make_model( $attributes );
			$pk    = $this->saver->get_pk( $model );

			if ( isset( $related[ $pk ] ) ) {
				$model = $related[ $pk ];
			} else {
				$related[ $pk ] = $model;
			}

			$relationship_map[ $result[ $this->other_column ] ][ $pk ] = $model;
		}

		foreach ( $models as $model ) {
			$data = isset( $relationship_map[ $model->get_pk() ] ) ? $relationship_map[ $model->get_pk() ] : array();
			$model->set_relation_value( $attribute, new Collection( $data, $memory, $this->saver ) );
		}

		return new Collection( $related, true, $this->saver );
	}

	/**
	 * @inheritdoc
	 */
	public function persist( $values ) {

		if ( $this->parent->get_pk() && ! $values->get_removed()->isEmpty() ) {
			$this->persist_removed( $values->get_removed() );
		}

		$added = $this->persist_do_save( $values );

		$this->persist_added( new ArrayCollection( $values->get_added()->toArray() + $added ) );

		return $values;
	}

	/**
	 * Save all models that are being persisted.
	 *
	 * @since 2.0
	 *
	 * @param Collection $values
	 *
	 * @return array Models that have been newly created, not updated.
	 */
	protected function persist_do_save( Collection $values ) {

		$added = array();

		foreach ( $values as $value ) {

			$new = ! $this->saver->get_pk( $value );

			// prevent recursion by excluding the relation that references this from being saved.
			$saved = $this->saver->save( $value, array( 'exclude_relations' => $this->other_attribute ) );
			$pk    = $this->saver->get_pk( $saved );

			if ( $new && $pk ) {
				$added[ $pk ] = $saved;

				$values->removeElement( $value );
			}

			$values->dont_remember( function ( Collection $collection ) use ( $saved, $pk ) {
				if ( ! $collection->contains( $saved ) ) {
					$collection->add( $saved );
				}
			} );
		}

		return $added;
	}

	/**
	 * Persist the removed models.
	 *
	 * @since 2.0
	 *
	 * @param DoctrineCollection $removed
	 */
	protected function persist_removed( $removed ) {

		$cached = wp_cache_get( $this->parent->get_pk(), $this->get_cache_group() ) ?: array();

		global $wpdb;

		$where = new Where( 1, false, 1 );

		foreach ( $removed as $model ) {

			$i = array_search( $this->saver->get_pk( $model ), $cached );

			if ( $i !== false ) {
				unset( $cached[ $i ] );
			}

			$remove_where = new Where( $this->other_column, true, esc_sql( $this->parent->get_pk() ) );
			$remove_where->qAnd(
				new Where( $this->primary_column, true, esc_sql( $this->saver->get_pk( $model ) ) )
			);

			$where->qOr( $remove_where );
		}

		wp_cache_set( $this->parent->get_pk(), $cached, $this->get_cache_group() );

		$wpdb->query( "DELETE FROM `{$this->association->get_table_name( $wpdb )}` $where" );
	}

	/**
	 * Persist the added models.
	 *
	 * @since 2.0
	 *
	 * @param DoctrineCollection $added
	 */
	protected function persist_added( $added ) {

		$cached = wp_cache_get( $this->parent->get_pk(), $this->get_cache_group() ) ?: array();

		global $wpdb;

		$insert = array();

		$parent = esc_sql( $this->parent->get_pk() );

		foreach ( $added as $model ) {
			$pk = esc_sql( $this->saver->get_pk( $model ) );

			if ( $pk ) {
				$insert[] = "({$parent},{$pk})";
				$cached[] = $pk;
			}
		}

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

		wp_cache_set( $this->parent->get_pk(), $cached, $this->get_cache_group() );

		$insert = implode( ',', $insert );

		$sql = "INSERT IGNORE INTO `{$this->association->get_table_name( $wpdb )}` ";
		$sql .= "({$this->other_column},{$this->primary_column}) VALUES $insert";

		$wpdb->query( $sql );
	}

	/**
	 * @inheritDoc
	 */
	public function on_delete( Model $model ) {

		/** @var \wpdb $wpdb */
		global $wpdb;

		$wpdb->delete( $this->association->get_table_name( $wpdb ), array(
			$this->other_column => $model->get_pk()
		) );
	}
}


<?php

namespace Tokenly\LaravelApiProvider\Commands;

use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class MakeAPIRespositoryCommand extends GeneratorCommand {

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'api:new-repository';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a new API Repository';

    public function getHelp() { return 'Generates a new API repository class'; }

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return [
            ['name', InputArgument::REQUIRED, 'Model Class Name like BearCub'],
        ];
    }
    /**
     * Get the console command options.
     *
     * @return array
     */
    protected function getOptions()
    {
        return [
        ];
    }

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        if (parent::handle() !== false) {
        }
    }

    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        return __DIR__.'/stubs/apirepository.stub';
    }

    /**
     * Get the default namespace for the class.
     *
     * @param  string  $rootNamespace
     * @return string
     */
    protected function getDefaultNamespace($rootNamespace)
    {
        return $rootNamespace.'\Repositories';
    }

    /**
     * Build the class with the given name.
     *
     * @param  string  $name
     * @return string
     */
    protected function buildClass($name)
    {
        $class = parent::buildClass($name);
        $class = str_replace('DummyModelClass', trim($this->laravel->getNamespace(), '\\').'\Models\\'.$this->argument('name'), $class);
        return $class;
    }

    protected function getNameInput()
    {
        $model_name = parent::getNameInput();

        return $model_name.'Repository';
    }

}


<?php

namespace Tokenly\LaravelApiProvider\Repositories;

use Exception;
use Ramsey\Uuid\Uuid;
use Tokenly\LaravelApiProvider\Contracts\APIResourceRepositoryContract;
use Tokenly\LaravelApiProvider\Model\Uuid\UuidHelper;
use Tokenly\LaravelApiProvider\Repositories\BaseRepository;

/*
* APIRepository
*/
abstract class APIRepository extends BaseRepository implements APIResourceRepositoryContract
{

    // ------------------------------------------------------------------------
    // add UUID when creating

    public function create($attributes) {
        if (!isset($attributes['uuid'])) { $attributes['uuid'] = Uuid::uuid4()->toString(); }
        return parent::create($attributes);
    }

    // ------------------------------------------------------------------------
    // UUID methods

    public function findByUuid($uuid) {
        return $this->prototype_model->where('uuid', $uuid)->first();
    }

    public function updateByUuid($uuid, $attributes) {
        $model = $this->findByUuid($uuid);
        if (!$model) { throw new Exception("Unable to find model for uuid $uuid", 1); }
        $this->update($model, $attributes);
        return $model;
    }

    public function deleteByUuid($uuid) {
        $model = $this->findByUuid($uuid);
        if (!$model) { throw new Exception("Unable to find model for uuid $uuid", 1); }

        $this->delete($model);
        return $model;
    }

}


<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddApiProviderFieldsToUserTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            if (!Schema::hasColumn($table->getTable(), 'uuid')) {
                $table->char('uuid', 36)->unique()->default('');
            }
            if (!Schema::hasColumn($table->getTable(), 'username')) {
                $table->string('username')->unique()->default('');
            }
            if (!Schema::hasColumn($table->getTable(), 'privileges')) {
                $table->text('privileges')->nullable();
            }
            if (!Schema::hasColumn($table->getTable(), 'apitoken')) {
                $table->string('apitoken', 16)->unique()->nullable();
            }
            if (!Schema::hasColumn($table->getTable(), 'apisecretkey')) {
                $table->string('apisecretkey', 40)->nullable();
            }
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
    }
}


<?php

namespace Tokenly\LaravelApiProvider\Middleware;

use Closure;
use Exception;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;
use Tokenly\HmacAuth\Exception\AuthorizationException;
use Tokenly\LaravelApiProvider\Contracts\APIUserRepositoryContract;

class AuthenticateProtectedAPIRequest extends AuthenticateAPIRequest {

    /**
     * The Guard implementation.
     *
     * @var Guard
     */
    protected $auth;

    /**
     * Create a new filter instance.
     *
     * @param  Guard  $auth
     * @return void
     */
    protected function initAuthenticator() {
        $auth = $this->auth;
        $this->hmac_validator  = new \Tokenly\HmacAuth\Validator(function($api_token) use ($auth) {
            // lookup the API secrect by $api_token using $this->auth
            $user = $this->user_repository->findByAPIToken($api_token);
            if (!$user) { return null; }

            // populate Guard with the $user
            $auth->setUser($user);

            // the purpose of this function is to look up the secret
            $api_secret = $user->getApiSecretKey();
            return $api_secret;
        });

        $substitions = Config::get('protectedApi.allowedSubstitutions');
        if ($substitions) {
            $this->hmac_validator->setSignedURLValidationFunction(function($actual_url, $signed_url) use ($substitions) {
                return $this->validateSignedURL($actual_url, $signed_url, $substitions);
            });
        }
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $authenticated = false;

        try {
            $authenticated = $this->hmac_validator->validateFromRequest($request);

        } catch (AuthorizationException $e) {
            // unauthorized
            $this->event_log->logError('error.auth.unauthenticated', $e, ['remoteIp' => $request->getClientIp()]);
            $error_message = $e->getAuthorizationErrorString();
            $error_code = $e->getCode();

            if (!$error_message) { $error_message = 'Authorization denied.'; }

        } catch (Exception $e) {
            // something else went wrong
            $this->event_log->logError('error.auth.unexpected', $e);
            $error_message = 'An unexpected error occurred';
            $error_code = 500;
        }

        if (!$authenticated) {
            $response = new JsonResponse([
                'message' => $error_message,
                'errors' => [$error_message],
            ], $error_code);
            return $response;
        }

        return $next($request);
    }

    public function validateSignedURL($actual_url, $signed_url, $substitions) {
        $current_route = Request::route();
        $current_route_name = $current_route->getName();
        if (isset($substitions[$current_route_name])) {
            $substition = $substitions[$current_route_name];

            // make sure the host of the signed URL matches the host of the substitution URL
            if (isset($substition['host'])) {
                $signed_host = $this->getHostFromURL($signed_url);
                $allowed_host = $substition['host'];
                if ($signed_host != $allowed_host) {
                    Log::debug("HOST MISMATCH: \$signed_host={$signed_host} \$allowed_host={$allowed_host}");
                    // no host match - return false
                    return false;
                }
            }

            // check the route
            $substitute_route = new Route($current_route->getMethods(), $substition['route'], []);
            $signed_request = \Illuminate\Http\Request::create($signed_url, Request::method());
            if ($substitute_route->matches($signed_request)) {
                // the allowed substitute route matches the signed request
                return true;
            }

            Log::debug("ROUTE MISMATCH: pathinfo=".json_encode(Request::getFacadeRoot()->getPathInfo())." allowed route={$substition['route']}");
        }

        // this signed URL was not valid
        return false;
    }
    // ------------------------------------------------------------------------

    protected function getHostFromURL($url) {
        $pieces = parse_url($url);
        return $pieces['host'];
    }
}


<?php

namespace Tokenly\LaravelApiProvider\Middleware;

use Closure;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Tokenly\HmacAuth\Exception\AuthorizationException;

class AuthenticatePublicAPIRequest extends AuthenticateAPIRequest {

    /**
     * The Guard implementation.
     *
     * @var Guard
     */
    protected $auth;

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $authenticated = false;

        try {
            // get the api token
            $api_token = $request->headers->get('X-Tokenly-Auth-Api-Token');
            if (!$api_token) { $api_token = $request->input('apitoken'); }
            if (!strlen($api_token)) { throw new AuthorizationException("Missing API Token"); }

            // load the user
            $user = $this->user_repository->findByAPIToken($api_token);
            if (!$user) { throw new AuthorizationException("Invalid API Token", "Failed to find user for token $api_token"); }

            // populate Guard with the $user
            $this->auth->setUser($user);

            $authenticated = true;

        } catch (AuthorizationException $e) {
            // unauthorized
            $this->event_log->logError('error.auth.unauthenticated', $e, ['remoteIp' => $request->getClientIp()]);
            $error_message = $e->getAuthorizationErrorString();
            $error_code = $e->getCode();

            if (!$error_message) { $error_message = 'Authorization denied.'; }

        } catch (Exception $e) {
            // something else went wrong
            $this->event_log->logError('error.auth.unexpected', $e);
            $error_message = 'An unexpected error occurred';
            $error_code = 500;
        }

        if (!$authenticated) {
            $response = new JsonResponse([
                'message' => $error_message,
                'errors' => [$error_message],
            ], $error_code);
            return $response;
        }

        return $next($request);
    }

}


<?php

namespace Tokenly\LaravelApiProvider;

use Exception;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\ServiceProvider;

/*
* APIServiceProvider
*/
class APIServiceProvider extends ServiceProvider
{

    public function boot()
    {
        // migrations
        $this->loadMigrationsFrom(__DIR__.'/../migrations');
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('Tokenly\LaravelApiProvider\Contracts\APIUserRepositoryContract', function($app) {
            return $app->make(Config::get('api.userRepositoryClass', 'Tokenly\LaravelApiProvider\Repositories\UserRepository'));
        });

        $this->app->bind('Tokenly\LaravelApiProvider\Contracts\APIUserContract', function($app) {
            return $app->make(Config::get('api.userClass', 'Tokenly\LaravelApiProvider\Model\APIUser'));
        });

        $this->app->bind('api.catchErrors', function($app) {
            return $app->make('Tokenly\LaravelApiProvider\Middleware\HandleAPIErrors');
        });
        $this->app->bind('api.publicAuth', function($app) {
            return $app->make('Tokenly\LaravelApiProvider\Middleware\AuthenticatePublicAPIRequest');
        });
        $this->app->bind('api.protectedAuth', function($app) {
            return $app->make('Tokenly\LaravelApiProvider\Middleware\AuthenticateProtectedAPIRequest');
        });

        // add artisan commands
        if ($this->app->runningInConsole()) {
            $this->commands([
                'Tokenly\LaravelApiProvider\Commands\NewAPIUserCommand',
                'Tokenly\LaravelApiProvider\Commands\ListAPIUsersCommand',
                'Tokenly\LaravelApiProvider\Commands\MakeAPIModelCommand',
                'Tokenly\LaravelApiProvider\Commands\MakeAPIRespositoryCommand',
            ]);
        }
    }

}


<?php

namespace Tokenly\LaravelApiProvider\Repositories;

use Tokenly\LaravelApiProvider\Events\ModelCreated;
use Tokenly\LaravelApiProvider\Events\ModelDeleted;
use Tokenly\LaravelApiProvider\Events\ModelUpdated;
use Tokenly\LaravelApiProvider\Repositories\Concerns\BroadcastsRepositoryEvents;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Application;
use Tokenly\LaravelApiProvider\Filter\RequestFilter;

/*
* BaseRepository
*/
abstract class BaseRepository
{

    // must define this when using the default constructor
    protected $model_type = '';

    protected $prototype_model;

    function __construct(Application $app) {
        $this->prototype_model = $app->make($this->model_type);
    }

    public function findByID($id) {
        return $this->prototype_model->find($id);
    }

    public function update(Model $model, $attributes) {
        $attributes = $this->modifyAttributesBeforeUpdate($attributes, $model);
        $result = $model->update($attributes);

        // broadcast update event
        if ($this->usesTrait(BroadcastsRepositoryEvents::class)) {
            $this->broadcastRepositoryEvent(new ModelUpdated($model, $attributes));
        }

        return $result;
    }

    public function delete(Model $model) {
        $result = $model->delete();

        // broadcast delete event
        if ($this->usesTrait(BroadcastsRepositoryEvents::class)) {
            $this->broadcastRepositoryEvent(new ModelDeleted($model));
        }

        return $result;
    }

    // ------------------------------------------------------------------------
    // API Model Contract

    public function create($attributes) {
        $attributes = $this->modifyAttributesBeforeCreate($attributes);

        $model = $this->prototype_model->create($attributes);

        // broadcast create event
        if ($this->usesTrait(BroadcastsRepositoryEvents::class)) {
            $this->broadcastRepositoryEvent(new ModelCreated($model, $attributes));
        }

        return $model;
    }

    public function findAll(RequestFilter $filter=null) {
        if ($filter === null) {
            return $this->prototype_model->all();
        }

        $query = $this->prototype_model->newQuery();

        if ($filter !== null) {
            $filter->filter($query);
            $filter->limit($query);
            $filter->sort($query);
        }

        return $query->get();
    }

    // ------------------------------------------------------------------------
    // Modify Hooks

    protected function modifyAttributesBeforeCreate($attributes) {
        return $attributes;
    }

    protected function modifyAttributesBeforeUpdate($attributes, Model $model) {
        return $attributes;
    }

    // ------------------------------------------------------------------------
    // traits

    protected $_traits_used = [];

    protected function usesTrait($trait_class) {
        if (!isset($this->_traits_used[$trait_class])) {
            $this->_traits_used = class_uses($this);
        }

        return isset($this->_traits_used[$trait_class]);
    }
}


<?php

namespace Tokenly\LaravelApiProvider\Commands;

use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class MakeAPIModelCommand extends GeneratorCommand {

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'api:new-model';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a new API Model';

    public function getHelp() { return 'Generates a new API model class'; }

    /**
     * Get the console command arguments.
     *
     * @return array
     */
    protected function getArguments()
    {
        return [
            ['name', InputArgument::REQUIRED, 'Model Class Name like BearCub'],
        ];
    }
    /**
     * Get the console command options.
     *
     * @return array
     */
    protected function getOptions()
    {
        return [
            ['with-repository', 'r', InputOption::VALUE_NONE, 'Also generate a repository'],
            ['with-migration', 'm', InputOption::VALUE_NONE, 'Also generate a migration'],
        ];
    }

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function handle()
    {
        if (parent::handle() !== false) {
            if ($this->option('with-repository')) {
                $this->comment('making repository');
                $this->call('api:new-repository', ['name' => $this->argument('name')]);
            }
            if ($this->option('with-migration')) {
                $this->comment('making migration');
                $name = $this->argument('name');
                $table = str_plural(snake_case($name));
                $this->call('make:migration', ['name' => "create_{$table}_table", '--create' => $table]);

            }
        }
    }

    /**
     * Get the stub file for the generator.
     *
     * @return string
     */
    protected function getStub()
    {
        return __DIR__.'/stubs/apimodel.stub';
    }

    /**
     * Get the default namespace for the class.
     *
     * @param  string  $rootNamespace
     * @return string
     */
    protected function getDefaultNamespace($rootNamespace)
    {
        return $rootNamespace.'\Models';
    }

    /**
     * Build the class with the given name.
     *
     * @param  string  $name
     * @return string
     */
    protected function buildClass($name)
    {
        $class = parent::buildClass($name);
        return $class;
    }

}


<?php

namespace Tokenly\LaravelApiProvider\Commands;

use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Tokenly\LaravelEventLog\Facade\EventLog;

class NewAPIUserCommand extends Command {

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'api:new-user';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Create a new API User';

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this
            ->addArgument('email', InputArgument::REQUIRED, 'Email Address')
            ->addArgument('name', InputArgument::OPTIONAL, 'Name', '')
            ->addArgument('username', InputArgument::OPTIONAL, 'Username')
            ->addOption('password', 'p', InputOption::VALUE_OPTIONAL, 'Password', null)
            ->setHelp(<<<EOF
Create a new user with API Credentials
EOF
        );
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $user_repository = $this->laravel->make('Tokenly\LaravelApiProvider\Contracts\APIUserRepositoryContract');
        $user_vars = [
            'name'     => $this->input->getArgument('name'),
            'username' => $this->input->getArgument('username'),
            'email'    => $this->input->getArgument('email'),
            'password' => $this->input->getOption('password'),

        ];
        $user_model = $user_repository->create($user_vars);

        // log
        EventLog::log('user.create.cli', $user_model, ['id', 'email', 'apisecretkey']);

        // show the new user
        $user = clone $user_model;
        $user['password'] = '********';
        $this->line(json_encode($user, 192));
    }

}


<?php

namespace Tokenly\LaravelApiProvider\Commands;

use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;

class ListAPIUsersCommand extends Command {

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'api:list-users';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'List API Users';

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        parent::configure();

        $this
            ->setHelp(<<<EOF
Show User API Credentials
EOF
        );
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {

        $user_repository = $this->laravel->make('Tokenly\LaravelApiProvider\Contracts\APIUserRepositoryContract');
        $users = $user_repository->findAll();

        foreach($users as $user) {
            $user['password'] = '********';
            $this->line(json_encode($user, 192));
        }
    }

}


<?php
/**
 * Created by PhpStorm.
 * User: Emad Omar <emad2030@gmail.com>
 * Date: 2/25/2016
 * Time: 3:25 PM
 */

namespace TelebotSDK;

use TelebotSDK\JsonMapper\JsonMapper;

trait Jsonable {

    /**
     * Parses/maps the text json to the current class
     *
     * @param string $jsonText JSON data that represents the current class
     *
     * @return object A new object of the current class
     * @throws Telebot_InvalidJsonException When a required property is missing or unexpected data
     *     provided
     */
    public static function parse($jsonText) {
        // Create a mapper instance
        $mapper = JsonMapper::getInstance();

        // To throw an exception if there's a missing required property
        $mapper->bExceptionOnMissingData = true;

        try {
            // Map the parsed json to an object of the current class
            $obj = $mapper->map(
            // Parse JSON text to an object
                json_decode($jsonText),
                // Create a new object of the current class
                self::instantiate(__CLASS__)
            );
        } catch (\JsonMapper_Exception $e) {
            // This is usually caused when a missing required property is detected
            throw new Telebot_InvalidJsonException(
                "Invalid telegram update json data: " . $e->getMessage(), 0, $e
            );
        }

        return $obj;
    }

    /**
     * Creates a new instance of the provided class
     *
     * @param string $class A fully qualified name of the class to be created
     * @param array $args Arguments to be passed to the class constructor
     *
     * @return mixed Object of the created class
     */
    public static function instantiate($class, $args = []) {
        // Prepare a reflection class with the passed `$class`
        $reflection_class = new \ReflectionClass($class);

        // Create a new instance with arguments provided by `$args`
        return $reflection_class->newInstanceArgs($args);
    }

}


<?php
/**
 * Created by PhpStorm.
 * User: Emad Omar <emad2030@gmail.com>
 * Date: 2/25/2016
 * Time: 9:13 PM
 */

namespace TelebotSDK;

use TelebotSDK\JsonMapper\JsonMapper;

class BotApi {

    const PARSE_MODE_MARKDOWN = "Markdown";
    const PARSE_MODE_HTML = "HTML";

    protected $api_url = "https://api.telegram.org/bot{{token}}";

    public function __construct($token) {
        $this->api_url = str_replace('{{token}}', $token, $this->api_url);
    }

    /**
     * @return Response
     * @throws \JsonMapper_Exception
     */
    public function getMe() {
        $botInfo = $this->performMethod('getMe', User::class);

        return $botInfo;
    }

    /**
     * @param $chat_id
     * @param $text
     * @param null $parse_mode
     * @param null $disable_web_page_preview
     * @param null $reply_to_message_id
     * @param ReplyKeyboardMarkup|ReplyKeyboardHide|ForceReply $reply_markup
     *
     * @return Response
     */
    public function sendMessage($chat_id, $text, $parse_mode = null,
                                $disable_web_page_preview = null,
                                $reply_to_message_id = null,
                                $reply_markup = null) {
        $params = [
            'chat_id' => $chat_id,
            'text' => $text,
            'parse_mode' => $parse_mode,
            'disable_web_page_preview' => $disable_web_page_preview,
            'reply_to_message_id' => $reply_to_message_id,
            'reply_markup' => $reply_markup ? ReplyMarkup::encode($reply_markup) : null,
        ];

        $update = $this->performMethod('sendMessage', Message::class, $params);

        return $update;
    }

    /**
     * @param $chat_id
     * @param $from_chat_id
     * @param $message_id
     *
     * @return Response
     */
    public function forwardMessage($chat_id, $from_chat_id, $message_id) {
        $params = [
            'chat_id' => $chat_id,
            'from_chat_id' => $from_chat_id,
            'message_id' => $message_id,
        ];

        $update = $this->performMethod('forwardMessage', Message::class, $params);

        return $update;
    }

    /**
     * @param integer $chat_id
     * @param InputFile|string $photo
     * @param string $caption
     * @param string $reply_to_message_id
     * @param ReplyKeyboardMarkup|ReplyKeyboardHide|ForceReply $reply_markup
     *
     * @return Response
     */
    public function sendPhoto($chat_id, $photo, $caption = null,
                              $reply_to_message_id = null, $reply_markup = null) {
        $params = [
            'chat_id' => $chat_id,
            'photo' => $photo,
            'caption' => $caption,
            'reply_to_message_id' => $reply_to_message_id,
            'reply_markup' => $reply_markup,
        ];

        $update = $this->performMethod('sendPhoto', Message::class, $params);

        return $update;
    }

    /**
     * @param $method
     * @param $resultClass
     * @param array $args
     *
     * @return Response
     * @throws \JsonMapper_Exception
     */
    protected function performMethod($method, $resultClass, $args = []) {
        $end_point = $this->api_url . '/' . $method;

        if (empty($args)) {
            $content_type = 'Content-type: application/x-www-form-urlencoded';
            $content = null;
        } else {
            $multipart_boundary = uniqid('---Telebot_FromBoundary');
            $content_type = 'Content-type: multipart/form-data'
                . '; boundary=' . $multipart_boundary;

            $content = self::renderFormDataContent($args, $multipart_boundary);
        }

        // Setting a custom stream context,
        $context = stream_context_create([
            'http' => [
                // To ignore http errors and return the response result anyway
                'ignore_errors' => true,
                'method' => 'POST',
                'header' => $content_type,
                'content' => $content,
            ],
        ]);

        // Call telegram api, and return the response
        $responseText = file_get_contents($end_point, false, $context);

        $response = JsonMapper::getInstance()->map(
            json_decode($responseText),
            Jsonable::instantiate(Response::class, [$resultClass])
        );

        return $response;
    }

    /**
     * @param array $args
     * @param string $multipart_boundary
     *
     * @return string
     * @throws Telebot_InvalidArgumentException
     */
    protected static function renderFormDataContent($args, $multipart_boundary) {
        $content = '';

        foreach ($args as $name => $value) {
            if ($value == null) {
                continue;
            }

            $content .= '--' . $multipart_boundary . "\r\n";
            $content .= 'Content-Disposition: form-data; name="' . $name . '"';

            if ($value instanceof InputFile) {
                $content .= '; filename="' . $value->getFileName() . '"' . "\r\n";
                $content .= 'Content-Type: ' . $value->getMimeType();
                $value = $value->getFileContent();
            } else if (!is_int($value) && !is_string($value)
                && !is_float($value) && !is_bool($value)
            ) {
                throw new Telebot_InvalidArgumentException(
                    'Argument must be a `string`, `integer`, `float`, `boolean` or'
                    . ' of type`' . InputFile::class . '` only.'
                );
            }

            $content .= "\r\n\r\n" . $value . "\r\n";
        }

        $content .= "--" . $multipart_boundary . "--\r\n";

        return $content;
    }

}


<?php

namespace cakebake\lessphp;

use Yii;

/**
 * AssetConverter supports conversion of less script format into CSS script.
 *
 * It is used by [[AssetManager]] to convert files after they have been published.
 *
 * @author cakebake (Jens A.)
 * @since 2.0
 * @see https://github.com/oyejorge/less.php
 */
class AssetConverter extends \yii\web\AssetConverter
{
    const INPUT_EXT = 'less';

    const OUTPUT_EXT = 'css';

    /**
    * @var bool You can tell less.php to remove comments and whitespace to generate minimized css files.
    */
    public $compress = false;

    /**
    * @var bool less.php will save serialized parser data for each .less file. Faster, but more memory-intense.
    */
    public $useCache = false;

    /**
    * @var string|null is passed to the SetCacheDir() method. By default "cakebake\lessphp\runtime" is used.
    */
    public $cacheDir = null;

    /**
    * @var bool Filename suffix to avoid the browser cache and force recompiling by configuration changes
    */
    public $cacheSuffix = false;

    /**
     * Converts a given LESS assets file into a CSS
     *
     * @param string $asset the asset file path, relative to $basePath
     * @param string $basePath the directory the $asset is relative to.
     * @return string the converted asset file path, relative to $basePath.
     */
    public function convert($asset, $basePath)
    {
        if (($dotPos = strrpos($asset, '.')) === false)
            return $asset;

        if (($ext = substr($asset, $dotPos + 1)) !== self::INPUT_EXT)
            return parent::convert($asset, $basePath);

        $assetFilemtime = @filemtime("$basePath/$asset");
        $result = $this->buildResult($asset, $dotPos, ($this->cacheSuffix === true) ? $assetFilemtime : null);
        $resultFilemtime = @filemtime("$basePath/$result");

        if ($resultFilemtime < $assetFilemtime) {
            $this->parseLess($basePath, $asset, $result);
        }

        return $result;
    }

    /**
    * Builds the result file name
    *
    * @param string $asset the asset file path, relative to $basePath
    * @param int $dotPos the strrpos position of filename-extension dot
    * @param mixed $resultSuffix Suffix result css filename
    * @return string the converted asset file path, relative to $basePath.
    */
    protected function buildResult($asset, $dotPos = null, $resultSuffix = null)
    {
        if ($dotPos === null) {
            if (($dotPos = strrpos($asset, '.')) === false) {
                return $asset;
            }
        }

        $divider = ($this->compress === true) ? '-m' : '-';
        $suffix = ($resultSuffix !== null) ? $divider . (string)$resultSuffix . '.' . self::OUTPUT_EXT : '.' . self::OUTPUT_EXT;

        return substr($asset, 0, $dotPos) . $suffix;
    }

    /**
     * Parsing Less File
     *
     * @param string $basePath asset base path and command working directory
     * @param string $asset the name of the asset file
     * @param string $result the name of the file to be generated by the converter command
     * @return boolean true on success, false on failure. Failures will be logged.
     * @throws Less_Exception_Parser when the command fails
     */
    protected function parseLess($basePath, $asset, $result)
    {
        $parser = new \Less_Parser([
            'compress' => ($this->compress === true) ? true : false,
            'cache_dir' => ($this->useCache === true) ? ($this->cacheDir !== null && is_dir($this->cacheDir)) ? $this->cacheDir : __DIR__ . DIRECTORY_SEPARATOR . 'cache' : false,
        ]);

        $parser->parseFile($basePath . DIRECTORY_SEPARATOR . $asset);

        if ((!$css = $parser->getCss()) || empty($css))
            return false;

        Yii::trace("Converted $asset into $result", __METHOD__);

        return file_put_contents($basePath . DIRECTORY_SEPARATOR . $result, $css, LOCK_EX);
    }
}


<?php

namespace donatj\Pushover;

/**
 * Damn Simple API Interface for Pushover Messages
 *
 * @author Jesse G. Donat <donatj@gmail.com>
 * @license MIT
 */
class Pushover {

	const API_URL = "https://api.pushover.net/1/messages.json";

	/**
	 * @var string
	 */
	private $token;

	/**
	 * @var string
	 */
	private $user;

	/**
	 * @var string
	 */
	private $apiUrl;

	/**
	 * Create a pushover object
	 *
	 * @param string $token The application API token
	 * @param string $user Your user key
	 * @param string $apiUrl Optionally change the API URL
	 */
	public function __construct( $token, $user, $apiUrl = self::API_URL ) {
		$this->token  = $token;
		$this->user   = $user;
		$this->apiUrl = $apiUrl;
	}

	/**
	 * Send the pushover message
	 *
	 * @see    https://pushover.net/api
	 *
	 * @param  string $message The message to send
	 * @param  array  $options Optional configuration settings
	 * @return bool|array       Returns false on failure, or a data array on success
	 */
	public function send( $message, array $options = [] ) {
		$options[Options::TOKEN]   = $this->token;
		$options[Options::USER]    = $this->user;
		$options[Options::MESSAGE] = $message;

		$opts = [ 'http' => [
			'method'  => 'POST',
			'header'  => 'Content-Type: application/x-www-form-urlencoded',
			'content' => http_build_query($options),
		] ];

		$context = stream_context_create($opts);

		if( $result = @file_get_contents($this->apiUrl, false, $context) ) {
			if( $final = @json_decode($result, true) ) {
				return $final;
			}
		}

		return false;
	}
}


<?php
/**
 * Defines the TraderInteractive\Mongo\Queue class.
 */

namespace TraderInteractive\Mongo;

use ArrayObject;
use MongoDB\BSON\ObjectID;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Operation\FindOneAndUpdate;

/**
 * Abstraction of mongo db collection as priority queue.
 *
 * Tied priorities are ordered by time. So you may use a single priority for normal queuing (default args exist for
 * this purpose).  Using a random priority achieves random get()
 */
abstract class AbstractQueue
{
    /**
     * Maximum millisecond value to use for UTCDateTime creation.
     *
     * @var integer
     */
    const MONGO_INT32_MAX = PHP_INT_MAX;

    /**
     * mongo collection to use for queue.
     *
     * @var \MongoDB\Collection
     */
    protected $collection;

    /**
     * @var array
     */
    const FIND_ONE_AND_UPDATE_OPTIONS = [
        'sort' => ['priority' => 1, 'created' => 1],
        'typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array'],
        'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
    ];

    /**
     * @var integer
     */
    const DEFAULT_MAX_NUMBER_OF_MESSAGES = 1;

    /**
     * @var integer
     */
    const DEFAULT_RUNNING_RESET_DURATION = 600000;

    /**
     * @var integer
     */
    const DEFAULT_WAIT_DURATION_IN_MILLISECONDS = 3000;

    /**
     * @var integer
     */
    const DEFAULT_POLL_DURATION_IN_MILLISECONDS = 200;

    /**
     * @var array
     */
    const DEFAULT_GET_OPTIONS = [
       'maxNumberOfMessages' => self::DEFAULT_MAX_NUMBER_OF_MESSAGES,
       'runningResetDuration' => self::DEFAULT_RUNNING_RESET_DURATION,
       'waitDurationInMillis' => self::DEFAULT_WAIT_DURATION_IN_MILLISECONDS,
       'pollDurationInMillis' => self::DEFAULT_POLL_DURATION_IN_MILLISECONDS,
    ];

    /**
     * Ensure an index for the get() method.
     *
     * @param array $beforeSort Fields in get() call to index before the sort field in same format
     *                          as \MongoDB\Collection::ensureIndex()
     * @param array $afterSort  Fields in get() call to index after the sort field in same format as
     *                          \MongoDB\Collection::ensureIndex()
     *
     * @return void
     *
     * @throws \InvalidArgumentException value of $beforeSort or $afterSort is not 1 or -1 for ascending and descending
     * @throws \InvalidArgumentException key in $beforeSort or $afterSort was not a string
     */
    final public function ensureGetIndex(array $beforeSort = [], array $afterSort = [])
    {
        //using general rule: equality, sort, range or more equality tests in that order for index
        $completeFields = ['earliestGet' => 1];

        $this->verifySort($beforeSort, 'beforeSort', $completeFields);

        $completeFields['priority'] = 1;
        $completeFields['created'] = 1;

        $this->verifySort($afterSort, 'afterSort', $completeFields);

        //for the main query in get()
        $this->ensureIndex($completeFields);
    }

    /**
     * Ensure an index for the count() method.
     * Is a no-op if the generated index is a prefix of an existing one. If you have a similar ensureGetIndex call,
     * call it first.
     *
     * @param array $fields fields in count() call to index in same format as \MongoDB\Collection::createIndex()
     * @param bool $includeRunning whether to include the running field in the index
     *
     * @return void
     *
     * @throws \InvalidArgumentException key in $fields was not a string
     * @throws \InvalidArgumentException value of $fields is not 1 or -1 for ascending and descending
     */
    final public function ensureCountIndex(array $fields, bool $includeRunning)
    {
        $completeFields = [];

        if ($includeRunning) {
            $completeFields['earliestGet'] = 1;
        }

        $this->verifySort($fields, 'fields', $completeFields);

        $this->ensureIndex($completeFields);
    }

    /**
     * Get a non running message from the queue.
     *
     * @param array $query in same format as \MongoDB\Collection::find() where top level fields do not contain
     *                     operators. Lower level fields can however. eg: valid {a: {$gt: 1}, "b.c": 3},
     *                     invalid {$and: [{...}, {...}]}
     * @param array $options Associative array of get options.
     *                           runningResetDuration => integer
     *                               The duration (in miiliseconds) that the received messages are hidden from
     *                               subsequent retrieve requests after being retrieved by a get() request.
     *                           waitDurationInMillis => integer
     *                               The duration (in milliseconds) for which the call will wait for a message to
     *                               arrive in the queue before returning. If a message is available, the call will
     *                               return sooner than WaitTimeSeconds.
     *                           pollDurationInMillis => integer
     *                               The millisecond duration to wait between polls.
     *                           maxNumberOfMessages => integer
     *                               The maximum number of messages to return with get(). All of the messages are not
     *                               necessarily returned.
     *
     * @return array Array of messages.
     *
     * @throws \InvalidArgumentException key in $query was not a string
     */
    final public function get(array $query, array $options = []) : array
    {
        $completeQuery = $this->buildPayloadQuery(
            ['earliestGet' => ['$lte' => new UTCDateTime((int)(microtime(true) * 1000))]],
            $query
        );

        $options += self::DEFAULT_GET_OPTIONS;
        $update = ['$set' => ['earliestGet' => $this->calculateEarliestGet($options['runningResetDuration'])]];
        $end = $this->calculateEndTime($options['waitDurationInMillis']);
        $sleepTime = $this->calculateSleepTime($options['pollDurationInMillis']);
        $messages = new ArrayObject();
        while (count($messages) < $options['maxNumberOfMessages']) {
            if ($this->tryFindOneAndUpdate($completeQuery, $update, $messages)) {
                continue;
            }

            if (microtime(true) < $end) {
                usleep($sleepTime);
            }

            break;
        }

        return $messages->getArrayCopy();
    }

    /**
     * Count queue messages.
     *
     * @param array $query in same format as \MongoDB\Collection::find() where top level fields do not contain
     *                     operators. Lower level fields can however. eg: valid {a: {$gt: 1}, "b.c": 3},
     *                     invalid {$and: [{...}, {...}]}
     * @param bool|null $running query a running message or not or all
     *
     * @return int the count
     *
     * @throws \InvalidArgumentException key in $query was not a string
     */
    final public function count(array $query, bool $running = null) : int
    {
        $totalQuery = [];

        if ($running === true || $running === false) {
            $key = $running ? '$gt' : '$lte';
            $totalQuery['earliestGet'] = [$key => new UTCDateTime((int)(microtime(true) * 1000))];
        }

        return $this->collection->count($this->buildPayloadQuery($totalQuery, $query));
    }

    /**
     * Acknowledge a message was processed and remove from queue.
     *
     * @param Message $message message received from get()
     *
     * @return void
     */
    final public function ack(Message $message)
    {
        $this->collection->deleteOne(['_id' => $message->getId()]);
    }

    /**
     * Atomically acknowledge and send a message to the queue.
     *
     * @param Message $message message received from get().
     *
     * @return void
     */
    final public function requeue(Message $message)
    {
        $set = [
            'payload' => $message->getPayload(),
            'earliestGet' => $message->getEarliestGet(),
            'priority' => $message->getPriority(),
            'created' => new UTCDateTime(),
        ];

        $this->collection->updateOne(['_id' => $message->getId()], ['$set' => $set], ['upsert' => true]);
    }

    /**
     * Send a message to the queue.
     *
     * @param Message $message The message to send.
     *
     * @return void
     */
    final public function send(Message $message)
    {
        $document = [
            '_id' => $message->getId(),
            'payload' => $message->getPayload(),
            'earliestGet' => $message->getEarliestGet(),
            'priority' => $message->getPriority(),
            'created' => new UTCDateTime(),
        ];

        $this->collection->insertOne($document);
    }

    /**
     * Ensure index of correct specification and a unique name whether the specification or name already exist or not.
     * Will not create index if $index is a prefix of an existing index
     *
     * @param array $index index to create in same format as \MongoDB\Collection::createIndex()
     *
     * @return void
     *
     * @throws \Exception couldnt create index after 5 attempts
     */
    final private function ensureIndex(array $index)
    {
        if ($this->isIndexIncludedInExistingIndex($index)) {
            return;
        }

        for ($i = 0; $i < 5; ++$i) {
            if ($this->tryCreateIndex($index)) {
                return;
            }
        }

        throw new \Exception('couldnt create index after 5 attempts');
        //@codeCoverageIgnoreEnd
    }

    private function buildPayloadQuery(array $initialQuery, array $payloadQuery)
    {
        foreach ($payloadQuery as $key => $value) {
            if (!is_string($key)) {
                throw new \InvalidArgumentException('key in $query was not a string');
            }

            $initialQuery["payload.{$key}"] = $value;
        }

        return $initialQuery;
    }

    private function calculateSleepTime(int $pollDurationInMillis) : int
    {
        $pollDurationInMillis = max($pollDurationInMillis, 0);
        $sleepTime = $pollDurationInMillis * 1000;
        //ints overflow to floats and already checked $pollDurationInMillis was positive
        return is_int($sleepTime) ? $sleepTime : PHP_INT_MAX;
    }

    private function calculateEarliestGet(int $runningResetDuration) : UTCDateTime
    {
        $resetTimestamp = time() + $runningResetDuration;
        //ints overflow to floats, max at PHP_INT_MAX
        return new UTCDateTime(min(max(0, $resetTimestamp * 1000), self::MONGO_INT32_MAX));
    }

    private function tryFindOneAndUpdate(array $query, array $update, ArrayObject $messages) : bool
    {
        $document = $this->collection->findOneAndUpdate($query, $update, self::FIND_ONE_AND_UPDATE_OPTIONS);
        if ($document === null) {
            return false;
        }

        $messages[] = new Message(
            $document['_id'],
            $document['payload'],
            $document['earliestGet'],
            $document['priority']
        );

        return true;
    }

    private function isIndexIncludedInExistingIndex(array $index) : bool
    {
        //if $index is a prefix of any existing index we are good
        foreach ($this->collection->listIndexes() as $existingIndex) {
            $slice = array_slice($existingIndex['key'], 0, count($index), true);
            if ($slice === $index) {
                return true;
            }
        }

        return false;
    }

    private function tryCreateIndex(array $index) : bool
    {
        for ($name = uniqid(); strlen($name) > 0; $name = substr($name, 0, -1)) {
            if ($this->tryCreateNamedIndex($index, $name)) {
                return true;
            }
        }

        return false;
    }

    private function tryCreateNamedIndex(array $index, string $name) : bool
    {
        //creating an index with same name and different spec does nothing.
        //creating an index with same spec and different name does nothing.
        //so we use any generated name, and then find the right spec after we have called,
        //and just go with that name.
        try {
            $this->collection->createIndex($index, ['name' => $name, 'background' => true]);
        } catch (\MongoDB\Exception\Exception $e) {
            //this happens when the name was too long, let continue
        }

        return $this->indexExists($index);
    }

    private function indexExists(array $index) : bool
    {
        foreach ($this->collection->listIndexes() as $existingIndex) {
            if ($existingIndex['key'] === $index) {
                return true;
            }
        }

        return false;
    }

    /**
     * Helper method to validate keys and values for the given sort array
     *
     * @param array  $sort             The proposed sort for a mongo index.
     * @param string $label            The name of the variable given to the public ensureXIndex method.
     * @param array  &$completedFields The final index array with payload. prefix added to fields.
     *
     * @return void
     */
    final private function verifySort(array $sort, string $label, array &$completeFields)
    {
        foreach ($sort as $key => $value) {
            $this->throwIfTrue(!is_string($key), "key in \${$label} was not a string");
            $this->throwIfTrue(
                $value !== 1 && $value !== -1,
                "value of \${$label} is not 1 or -1 for ascending and descending"
            );

            $completeFields["payload.{$key}"] = $value;
        }
    }

    private function throwIfTrue(
        bool $condition,
        string $message,
        string $exceptionClass = '\\InvalidArgumentException'
    ) {
        if ($condition === true) {
            $reflectionClass = new \ReflectionClass($exceptionClass);
            throw $reflectionClass->newInstanceArgs([$message]);
        }
    }

    private function calculateEndTime(int $waitDurationInMillis) : int
    {
        //ints overflow to floats, should be fine
        return microtime(true) + ($waitDurationInMillis / 1000.0);
    }
}


<?php
/**
 * Created by PhpStorm.
 * User: Emad Omar <emad2030@gmail.com>
 * Date: 2/25/2016
 * Time: 9:40 PM
 */

namespace TelebotSDK;

use TelebotSDK\JsonMapper\JsonMapper;

class Response {

    /**
     * @required
     * @var boolean
     */
    public $ok;

    /**
     * @var integer
     */
    public $error_code;

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

    /**
     * @var mixed
     */
    protected $result;

    /**
     * Fully qualified name of the result type/class
     * @var string
     */
    protected $resultType;

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

    /**
     * A setter for `$result`
     *
     * @param mixed $result
     */
    public function setResult($result) {
        $this->result = JsonMapper::getInstance()->map(
            $result, Jsonable::instantiate($this->resultType)
        );
    }

    /**
     * A getter for `$result`
     *
     * @return mixed
     */
    public function getResult() {
        return $this->result;
    }

}


<?php
namespace CFDIReader\PostValidations;

/**
 * This class is a collection of messages of different types
 */
class Issues implements \IteratorAggregate
{
    /**
     * Messages array
     * @var Messages[]
     */
    private $messages = [];

    /**
     * Short cut to append a message string into the message collection
     * @param string $type
     * @param string $message
     * @return void
     */
    public function add(string $type, string $message)
    {
        $this->messages($type)->add($message);
    }

    /**
     * Return a message collection of the selected type
     * If the type does not exists the object is created
     * @param string $type
     * @return Messages
     */
    public function messages(string $type)
    {
        if ('' === $type) {
            throw new \InvalidArgumentException('The type of messages must be a non-empty string');
        }
        if (! array_key_exists($type, $this->messages)) {
            $this->messages[$type] = new Messages();
        }
        return $this->messages[$type];
    }

    /**
     * The list of current registered types
     * @return string[]
     */
    public function types(): array
    {
        return array_keys($this->messages);
    }

    /**
     * Copy all messages from source to this object
     * @param Issues $issues
     * @return void
     */
    public function import(self $issues)
    {
        foreach ($issues->types() as $type) {
            $source = $issues->messages($type);
            $destination = $this->messages($type);
            foreach ($source as $message) {
                $destination->add($message);
            }
        }
    }

    /**
     * Return an array that contains all messages by type only if they contain a message
     * [
     *     'errors' => [...]
     *     'warnings' => [...]
     * ]
     * @return array
     */
    public function all(): array
    {
        $contents = [];
        foreach ($this->messages as $type => $messages) {
            if ($messages->count()) {
                $contents[$type] = $messages->all();
            }
        }
        return $contents;
    }

    public function getIterator()
    {
        return new \ArrayIterator($this->messages);
    }
}


<?php

/*
 * This file is part of the TomahawkPHP package.
 *
 * (c) Tom Ellis
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Tomahawk\DependencyInjection;

use ReflectionClass;
use ReflectionParameter;
use Tomahawk\DependencyInjection\Exception\BindingResolutionException;
use Tomahawk\DependencyInjection\Exception\InstantiateException;

/**
 * Container main class.
 *
 * @author  Tom Ellis
 *
 * Base on Pimple
 *
 * @author  Fabien Potencier
 */
class Container implements \ArrayAccess, ContainerInterface
{
    /**
     * @var array
     */
    private $serviceTags = [];

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

    /**
     * @var \SplObjectStorage
     */
    private $factories;

    /**
     * @var \SplObjectStorage
     */
    private $protected;

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

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

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

    /**
     * @var array Aliases of something in the DIC
     */
    protected $aliases = [];

    /**
     * Instantiate the container.
     *
     * Objects and parameters can be passed as argument to the constructor.
     *
     * @param array $values The parameters or objects.
     */
    public function __construct(array $values = [])
    {
        $this->factories = new \SplObjectStorage();
        $this->protected = new \SplObjectStorage();

        foreach ($values as $key => $value) {
            $this->offsetSet($key, $value);
        }
    }

    public function build($class, $parameters = [])
    {
        $reflector = new ReflectionClass($class);

        // If the class is not instantiable, error
        if ( ! $reflector->isInstantiable()) {
            throw new InstantiateException(sprintf('Class %s is not instantiable.', $class));
        }

        if ( ! $constructor = $reflector->getConstructor()) {
            return new $class;
        }

        $classes = $constructor->getParameters();

        // Once we have all the constructor's parameters we can create each of the
        // dependency instances and then use the reflection instances to make a
        // new instance of this class, injecting the created dependencies in.
        $deps = array_merge($parameters, $this->getDependencies(
            array_diff_key($classes, $parameters)
        ));

        return $reflector->newInstanceArgs($deps);
    }

    /**
     * Resolve all of the dependencies from the ReflectionParameters.
     *
     * @param  array  $parameters
     * @return array
     */
    protected function getDependencies($parameters)
    {
        $dependencies = [];

        foreach ($parameters as $parameter) {
            // If the class is null, it means the dependency is a string or some other
            // primitive type which we can not resolve since it is not a class and
            // we'll just bomb out with an error since we have no-where to go.
            if ( ! $parameter->getClass()) {
                $dependencies[] = $this->resolveNonClass($parameter);
            }
            else {
                $dependencies[] = $this->resolveClass($parameter);
            }
        }

        return (array)$dependencies;
    }

    /**
     * Resolve a non-class hinted dependency.
     *
     * @param  ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws BindingResolutionException
     */
    protected function resolveNonClass(ReflectionParameter $parameter)
    {
        if ($parameter->isDefaultValueAvailable())
        {
            return $parameter->getDefaultValue();
        }
        else
        {
            $message = "Unresolvable dependency resolving [$parameter].";

            throw new BindingResolutionException($message);
        }
    }

    /**
     * Resolve a class based dependency from the container.
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws BindingResolutionException
     */
    protected function resolveClass(ReflectionParameter $parameter)
    {
        try
        {
            return $this->get($parameter->getClass()->name);
        }

            // If we can not resolve the class instance, we will check to see if the value
            // is optional, and if it is we will return the optional parameter value as
            // the value of the dependency, similarly to how we do this with scalars.
        catch (BindingResolutionException $e)
        {
            if ($parameter->isOptional())
            {
                return $parameter->getDefaultValue();
            }
            else
            {
                throw $e;
            }
        }
    }

    /**
     * @param $id
     * @return bool
     */
    public function has($id)
    {
        return isset($this[$id]);
    }

    /**
     * @param $id
     * @return mixed
     */
    public function get($id)
    {
        return $this[$id];
    }

    /**
     * @param $id
     * @param $value
     * @return $this
     */
    public function set($id, $value)
    {
        $this[$id] = $value;

        return $this;
    }

    /**
     * @param $name
     * @param $id
     * @return $this
     */
    public function addAlias($name, $id)
    {
        $this->aliases[$name] = $id;
        return $this;
    }

    /**
     * @param $name
     * @return $this
     */
    public function getAlias($name)
    {
        return $this->aliases[$name];
    }

    public function hasAlias($name)
    {
        return isset($this->aliases[$name]);
    }

    /**
     * @param $name
     * @return $this
     */
    public function removeAlias($name)
    {
        unset($this->aliases[$name]);
        return $this;
    }

    /**
     * @param $name
     * @return $this
     */
    public function remove($name)
    {
        unset($this[$name]);
        return $this;
    }

    /**
     * Sets a parameter or an object.
     *
     * Objects must be defined as Closures.
     *
     * Allowing any PHP callable leads to difficult to debug problems
     * as function names (strings) are callable (creating a function with
     * the same name as an existing parameter would break your container).
     *
     * @param  string            $id    The unique identifier for the parameter or object
     * @param  mixed             $value The value of the parameter or a closure to define an object
     * @throws \RuntimeException Prevent override of a frozen service
     */
    public function offsetSet($id, $value)
    {
        if (isset($this->frozen[$id])) {
            throw new \RuntimeException(sprintf('Cannot override frozen service "%s".', $id));
        }

        $this->values[$id] = $value;
        $this->keys[$id] = true;
    }

    /**
     * Gets a parameter or an object.
     *
     * @param string $id The unique identifier for the parameter or object
     *
     * @return mixed The value of the parameter or an object
     *
     * @throws \InvalidArgumentException if the identifier is not defined
     */
    public function offsetGet($id)
    {
        if ($this->hasAlias($id)) {
            $id = $this->getAlias($id);
        }

        if (!$this->has($id) && class_exists($id)) {
            return $this->build($id);
        }

        if (!isset($this->keys[$id])) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
        }

        if (
            isset($this->raw[$id])
            || !is_object($this->values[$id])
            || isset($this->protected[$this->values[$id]])
            || !method_exists($this->values[$id], '__invoke')
        ) {
            return $this->values[$id];
        }

        if (isset($this->factories[$this->values[$id]])) {
            return $this->values[$id]($this);
        }

        $raw = $this->values[$id];
        $val = $this->values[$id] = $raw($this);
        $this->raw[$id] = $raw;

        $this->frozen[$id] = true;

        return $val;
    }

    /**
     * Checks if a parameter or an object is set.
     *
     * @param string $id The unique identifier for the parameter or object
     *
     * @return bool
     */
    public function offsetExists($id)
    {
        if ($this->hasAlias($id)) {
            $id = $this->getAlias($id);
        }

        return isset($this->keys[$id]);
    }

    /**
     * Unsets a parameter or an object.
     *
     * @param string $id The unique identifier for the parameter or object
     */
    public function offsetUnset($id)
    {
        if ($this->hasAlias($id)) {
            $alias = $id;
            $id = $this->getAlias($id);
            $this->removeAlias($alias);
        }

        if (isset($this->keys[$id])) {
            if (is_object($this->values[$id])) {
                unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]);
            }

            unset($this->values[$id], $this->frozen[$id], $this->raw[$id], $this->keys[$id]);
        }
    }

    /**
     * Marks a callable as being a factory service.
     *
     * @param callable $callable A service definition to be used as a factory
     *
     * @return callable The passed callable
     *
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
     */
    public function factory($callable)
    {
        if ( ! is_object($callable) || !method_exists($callable, '__invoke')) {
            throw new \InvalidArgumentException('Service definition is not a Closure or invokable object.');
        }

        $this->factories->attach($callable);

        return $callable;
    }

    /**
     * Protects a callable from being interpreted as a service.
     *
     * This is useful when you want to store a callable as a parameter.
     *
     * @param callable $callable A callable to protect from being evaluated
     *
     * @return callable The passed callable
     *
     * @throws \InvalidArgumentException Service definition has to be a closure of an invokable object
     */
    public function protect($callable)
    {
        if ( ! is_object($callable) || ! method_exists($callable, '__invoke')) {
            throw new \InvalidArgumentException('Callable is not a Closure or invokable object.');
        }

        $this->protected->attach($callable);

        return $callable;
    }

    /**
     * Gets a parameter or the closure defining an object.
     *
     * @param string $id The unique identifier for the parameter or object
     *
     * @return mixed The value of the parameter or the closure defining an object
     *
     * @throws \InvalidArgumentException if the identifier is not defined
     */
    public function raw($id)
    {
        if ( ! isset($this->keys[$id])) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
        }

        if (isset($this->raw[$id])) {
            return $this->raw[$id];
        }

        return $this->values[$id];
    }

    /**
     * Extends an object definition.
     *
     * Useful when you want to extend an existing object definition,
     * without necessarily loading that object.
     *
     * @param string   $id       The unique identifier for the object
     * @param callable $callable A service definition to extend the original
     *
     * @return callable The wrapped callable
     *
     * @throws \InvalidArgumentException if the identifier is not defined or not a service definition
     */
    public function extend($id, $callable)
    {
        if ( ! isset($this->keys[$id])) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
        }

        if ( ! is_object($this->values[$id]) || ! method_exists($this->values[$id], '__invoke')) {
            throw new \InvalidArgumentException(sprintf('Identifier "%s" does not contain an object definition.', $id));
        }

        if ( ! is_object($callable) || ! method_exists($callable, '__invoke')) {
            throw new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.');
        }

        $factory = $this->values[$id];

        $extended = function ($c) use ($callable, $factory) {
            /** @var \Closure|string $callable */
            return $callable($factory($c), $c);
        };

        if (isset($this->factories[$factory])) {
            $this->factories->detach($factory);
            $this->factories->attach($extended);
        }

        return $this[$id] = $extended;
    }

    /**
     * Returns all defined value names.
     *
     * @return array An array of value names
     */
    public function keys()
    {
        return array_keys($this->values);
    }

    /**
     * Registers a service provider.
     *
     * @param ServiceProviderInterface $provider A ServiceProviderInterface instance
     * @param array                    $values   An array of values that customizes the provider
     *
     * @return static
     */
    public function register(ServiceProviderInterface $provider, array $values = [])
    {
        $provider->register($this);

        foreach ($values as $key => $value) {
            $this[$key] = $value;
        }

        return $this;
    }

    /**
     * Tag a service
     *
     * @param string $id
     * @param string $tag
     * @return $this
     */
    public function tag($id, $tag)
    {
        if ( ! isset($this->serviceTags[$id])) {
            $this->serviceTags[$id] = [];
        }

        $this->serviceTags[$id][] = $tag;
        return $this;
    }

    /**
     * Find all service ids with a given tag
     *
     * @param string $tag
     * @return array
     */
    public function findTaggedServiceIds($tag)
    {
        $ids = [];

        foreach ($this->serviceTags as $id => $tags) {
            if (in_array($tag, $tags)) {
                $ids[] = $id;
            }
        }

        return $ids;
    }

    /**
     * Get all service tags
     *
     * @return array
     */
    public function getServiceTags()
    {
        return $this->serviceTags;
    }
}