version 1.0

This commit is contained in:
Maxime Renou 2021-07-07 10:24:05 +02:00
commit 941a149097
17 changed files with 1069 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea/
vendor/
.DS_Store

View File

@ -0,0 +1,175 @@
<?php
namespace Bluesquare\StorageBundle\Adaptors;
use Aws\S3\S3Client;
use Bluesquare\StorageBundle\Exceptions\InvalidStorageConfiguration;
use Symfony\Component\Config\Definition\Exception\Exception;
/**
* Interface de manipulation du stockage S3
* Usage: $storage = new S3Storage('my_storage_name', $config);
* @author Maxime Renou
*/
class S3Storage implements StorageAdaptor
{
const MODE_PRIVATE = 'private';
const MODE_PUBLIC = 'public-read';
protected $client;
protected $bucket;
protected $region;
public function __construct ($storage_name, $config)
{
if (!($this->configIsNormed($config)))
throw new InvalidStorageConfiguration("Invalid $storage_name storage configuration");
$this->config = $config;
$this->bucket = $config['bucket'];
$this->bucket_url = $config['bucket_url'];
$this->client = new S3Client([
'version' => isset($config['version']) ? $config['version'] : 'latest',
'region' => $config['region'],
'endpoint' => $config['endpoint'],
'credentials' => [
'key' => $config['credentials']['key'],
'secret' => $config['credentials']['secret']
]
]);
}
public function getClient()
{
return $this->client;
}
public function mode($name)
{
if ($name == 'public') return self::MODE_PUBLIC;
return self::MODE_PRIVATE;
}
private function configIsNormed($config)
{
return (
isset($config['type']) &&
isset($config['bucket']) &&
isset($config['bucket_url']) &&
isset($config['region']) &&
isset($config['endpoint']) &&
isset($config['credentials']) &&
isset($config['credentials']['key']) &&
isset($config['credentials']['secret'])
);
}
protected function getPrefix($prefix = null)
{
$ret = '';
if (isset($this->config['path']) && !empty($this->config['path']))
{
$ret = trim($this->config['path'], '/').'/';
if (trim($ret) == '/') $ret = '';
}
if (!is_null($prefix)) {
$ret = ltrim($prefix, '/');
}
return $ret;
}
public function index($prefix = null)
{
return ($this->client->listObjectsV2([
'Bucket' => $this->bucket,
'Prefix' => $this->getPrefix($prefix)
]));
}
/**
* Permet d'obtenir l'URL vers une ressource S3
* Cette ressource n'est accessible que si le fichier a été stocké en mode public
* @param $target_path
* @return string
*/
public function url ($target_path)
{
return rtrim($this->bucket_url, '/').'/'.$this->getPrefix().ltrim($target_path, '/');
}
/**
* Permet de stocker un fichier dans S3
* @param $source_path
* @param $target_path
* @param string $permissions
* @return \Aws\Result
*/
public function store ($source_path, $target_path, $permissions = self::MODE_PRIVATE)
{
return $this->client->putObject([
'Bucket' => $this->bucket,
'Path' => $this->getPrefix().$target_path,
'Key' => $this->getPrefix().$target_path,
'SourceFile' => $source_path, // Fix memory allocation
//'Body' => file_get_contents($source_path),
'ACL' => $permissions
]);
}
/**
* Permet de récupérer un fichier dans S3 pour le stocker en local
* @param $distant_path
* @param $local_path
*/
public function retrieve ($distant_path, $local_path)
{
$file_stream = fopen($local_path, 'w');
$aws_stream = $this->client->getObject([
'Bucket' => $this->bucket,
'Key' => $this->getPrefix().$distant_path,
'Path' => $this->getPrefix().$distant_path,
])->get('Body')->detach();
stream_copy_to_stream($aws_stream, $file_stream);
fclose($file_stream);
}
/**
* Ouvre le stream d'un fichier stocké dans S3
* @param $distant_path
* @param $target_stream
*/
public function getStream ($distant_path)
{
return $this->client->getObject([
'Bucket' => $this->bucket,
'Key' => $this->getPrefix().$distant_path,
'Path' => $this->getPrefix().$distant_path,
])->get('Body')->detach();
}
/**
* Permet de stream un fichier stocké dans S3
* @param $distant_path
* @param $target_stream
*/
public function stream ($distant_path, $target_stream)
{
stream_copy_to_stream($this->getStream($distant_path), $target_stream);
}
/**
* Permet de supprimer un fichier stocké dans S3
* @param $distant_path
*/
public function delete ($distant_path)
{
$this->client->deleteObject([
'Bucket' => $this->bucket,
'Path' => $this->getPrefix().$distant_path,
'Key' => $this->getPrefix().$distant_path,
]);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Bluesquare\StorageBundle\Adaptors;
interface StorageAdaptor
{
public function index();
public function mode($name);
public function store($source_path, $target_path);
public function retrieve($distant_path, $local_path);
public function stream($distant_path, $target_stream);
public function delete($distant_path);
}

View File

@ -0,0 +1,31 @@
<?php
namespace Bluesquare\StorageBundle\Annotations;
use Doctrine\ORM\Mapping as ORM;
/**
* @Annotation
*/
class Storage implements ORM\Annotation
{
/**
* @var string
*/
public $name;
/**
* @var mixed
*/
public $prefix = null;
/**
* @var mixed
*/
public $mode = null;
/**
* @var mixed
*/
public $mime = null;
}

View File

@ -0,0 +1,41 @@
<?php
namespace Bluesquare\StorageBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree builder.
*
* @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('storage');
$root = $treeBuilder->root('storage');
$root->useAttributeAsKey('storage_name')
->prototype('array')
->children()
->scalarNode('type')->isRequired()->cannotBeEmpty()->end()
->scalarNode('bucket')->end()
->scalarNode('bucket_url')->end()
->scalarNode('region')->end()
->scalarNode('endpoint')->end()
->arrayNode('credentials')
->children()
->scalarNode('key')->end()
->scalarNode('secret')->end()
->end()
->end()
->scalarNode('version')->end()
->scalarNode('path')->end()
->end()
->end()
->end();
return ($treeBuilder);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Bluesquare\StorageBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
class StorageExtension extends Extension
{
/**
* Loads a specific configuration.
*
* @throws \InvalidArgumentException When provided tag is not defined in this extension
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Ressources/config'));
$loader->load('services.yaml');
$configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs);
$definition = $container->getDefinition('bluesquare.storage');
$definition->setArgument(0, $config);
return $config;
}
public function getAlias()
{
// return ('bluesquare\\storage');
return parent::getAlias(); // TODO: Change the autogenerated stub
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Bluesquare\StorageBundle\Exceptions;
class InvalidFileException extends \Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace Bluesquare\StorageBundle\Exceptions;
class InvalidStorageConfiguration extends \Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace Bluesquare\StorageBundle\Exceptions;
class MimeTypeException extends \Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace Bluesquare\StorageBundle\Exceptions;
class MissingStorageAnnotation extends \Exception
{
}

View File

@ -0,0 +1,8 @@
<?php
namespace Bluesquare\StorageBundle\Exceptions;
class UnknownStorage extends \Exception
{
}

View File

@ -0,0 +1,2 @@
imports:
- { ressource: '%kernel.root_dir%/config/packages/bluesquare/storage.yaml' }

View File

@ -0,0 +1,7 @@
services:
bluesquare.storage:
class: Bluesquare\StorageBundle\Storage
autowire: true
public: true
arguments: ['user_storage']
Bluesquare\StorageBundle\Storage: '@bluesquare.storage'

166
StorageBundle/Storage.php Normal file
View File

@ -0,0 +1,166 @@
<?php
namespace Bluesquare\StorageBundle;
use Aws\S3\S3Client;
use Bluesquare\StorageBundle\Adaptors\S3Storage;
use Bluesquare\StorageBundle\Exceptions\InvalidFileException;
use Bluesquare\StorageBundle\Exceptions\MimeTypeException;
use Bluesquare\StorageBundle\Exceptions\MissingStorageAnnotation;
use Bluesquare\StorageBundle\Exceptions\UnknownStorage;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Bluesquare\StorageBundle\Annotations\Storage as StorageAnnotation;
use Symfony\Component\DependencyInjection\Container;
use Doctrine\Common\Annotations\AnnotationReader;
/**
* Interface de manipulation des stockages préconfigurés
* Usage par injection
*/
class Storage
{
private $config_storage = [];
public function __construct(array $user_config = [])
{
// dump($user_config); die;
$this->config_storage = $user_config;
}
public function get($storage_name)
{
if (array_key_exists($storage_name, $this->config_storage))
{
$config = $this->config_storage[$storage_name];
switch ($config['type'])
{
case 's3': return (new S3Storage($storage_name, $config));
}
}
return (null);
}
protected function getStorageAnnotation($entity, $attribute)
{
$reflection = new \ReflectionProperty($entity, $attribute);
$reader = new AnnotationReader();
$annotations = $reader->getPropertyAnnotations($reflection);
$storage_annotation = null;
foreach ($annotations as $annotation)
{
if ($annotation instanceof StorageAnnotation) {
$storage_annotation = $annotation;
}
}
if (is_null($storage_annotation))
{
throw new MissingStorageAnnotation("Missing Storage annotation for $attribute in ".get_class($entity));
}
return $storage_annotation;
}
public function getStorageFor($entity, $attribute)
{
$annotation = $this->getStorageAnnotation($entity, $attribute);
return $annotation->name;
}
public function url($entity, $attribute, $class = null)
{
$annotation = $this->getStorageAnnotation(!is_null($class) ? $class : $entity, $attribute);
$storage = $this->get($annotation->name);
$prefix = is_null($annotation->prefix) || empty($annotation->prefix) ? '' : trim($annotation->prefix, '/').'/';
$camel = ucfirst(Container::camelize($attribute));
return $storage->url("$prefix{$entity->{"get".$camel}()}");
}
public function delete($entity, $attribute)
{
$annotation = $this->getStorageAnnotation($entity, $attribute);
$storage = $this->get($annotation->name);
$prefix = is_null($annotation->prefix) || empty($annotation->prefix) ? '' : trim($annotation->prefix, '/').'/';
$camel = ucfirst(Container::camelize($attribute));
return $storage->delete("$prefix{$entity->{"get".$camel}()}");
}
public function retrieve($entity, $attribute, $local_path)
{
$annotation = $this->getStorageAnnotation($entity, $attribute);
$storage = $this->get($annotation->name);
$prefix = is_null($annotation->prefix) || empty($annotation->prefix) ? '' : trim($annotation->prefix, '/').'/';
$camel = ucfirst(Container::camelize($attribute));
return $storage->retrieve("$prefix{$entity->{"get".$camel}()}", $local_path);
}
public function stream($entity, $attribute, $target_stream = null)
{
$annotation = $this->getStorageAnnotation($entity, $attribute);
$storage = $this->get($annotation->name);
$prefix = is_null($annotation->prefix) || empty($annotation->prefix) ? '' : trim($annotation->prefix, '/').'/';
$camel = ucfirst(Container::camelize($attribute));
if (is_null($target_stream)) {
return $storage->getStream("$prefix{$entity->{"get".$camel}()}");
}
return $storage->stream("$prefix{$entity->{"get".$camel}()}", $target_stream);
}
public function store($entity, $attribute, $file)
{
$storage_annotation = $this->getStorageAnnotation($entity, $attribute);
$file_hash = hash('sha256', time().$attribute.uniqid());
$storage = $this->get($storage_annotation->name);
if (is_null($storage))
{
throw new UnknownStorage("Unknown storage {$storage_annotation->name} for $attribute in ".get_class($entity));
}
$prefix = is_null($storage_annotation->prefix) || empty($storage_annotation->prefix) ? '' : trim($storage_annotation->prefix, '/').'/';
$mode = $storage->mode($storage_annotation->mode);
$camel = ucfirst(Container::camelize($attribute));
if ($file instanceof UploadedFile) {
$file_hash .= strlen($file->getClientOriginalExtension()) > 0 ? '.'.$file->getClientOriginalExtension() : '';
if (!is_null($storage_annotation->mime))
{
$valid = true;
if (count(explode('/', $storage_annotation->mime)) > 1) {
$valid = strtolower($storage_annotation->mime) == $file->getMimeType();
}
else {
$valid = strtolower($storage_annotation->mime) == explode('/', $file->getMimeType())[0];
}
if (!$valid) {
throw new MimeTypeException("Invalid mime type");
}
}
$storage->store($file->getRealPath(), "$prefix$file_hash", $mode);
$previous_file_hash = $entity->{"get$camel"}();
if (!is_null($previous_file_hash) && !empty($previous_file_hash)) {
$storage->delete("$prefix$previous_file_hash");
}
}
elseif (is_string($file) && file_exists($file)) {
$storage->store($file, "$prefix$file_hash", $mode);
}
else {
throw new InvalidFileException("Invalid file argument");
}
$entity->{"set$camel"}($file_hash);
return $file_hash;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Bluesquare\StorageBundle;
use Bluesquare\StorageBundle\DependencyInjection\StorageExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class StorageBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
}
public function getContainerExtension()
{
if (null === $this->extension)
$this->extension = new StorageExtension();
return $this->extension;
}
}

23
composer.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "bluesquare-packages/symfony-storage",
"description": "Cryptor created by Bluesquare Computing",
"keywords": ["template", "composer", "package"],
"license": "proprietary",
"authors": [
{
"name": "RENOU Maxime",
"email": "maxime@bluesquare.io"
}
],
"type": "symfony-bundle",
"require": {
"php": ">=7.1",
"aws/aws-sdk-php": "^3.81",
"doctrine/event-manager": "*"
},
"autoload": {
"psr-4": {
"Bluesquare\\StorageBundle\\": "StorageBundle/"
}
}
}

504
composer.lock generated Normal file
View File

@ -0,0 +1,504 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e0fd4d7a1648530618008debe3b39eb2",
"packages": [
{
"name": "aws/aws-sdk-php",
"version": "3.87.15",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "21a4dd314e2a3c44b5538dd56d3770733a3d03e6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/21a4dd314e2a3c44b5538dd56d3770733a3d03e6",
"reference": "21a4dd314e2a3c44b5538dd56d3770733a3d03e6",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
"guzzlehttp/guzzle": "^5.3.3|^6.2.1",
"guzzlehttp/promises": "~1.0",
"guzzlehttp/psr7": "^1.4.1",
"mtdowling/jmespath.php": "~2.2",
"php": ">=5.5"
},
"require-dev": {
"andrewsville/php-token-reflection": "^1.4",
"aws/aws-php-sns-message-validator": "~1.0",
"behat/behat": "~3.0",
"doctrine/cache": "~1.4",
"ext-dom": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-sockets": "*",
"nette/neon": "^2.3",
"phpunit/phpunit": "^4.8.35|^5.4.3",
"psr/cache": "^1.0"
},
"suggest": {
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
"doctrine/cache": "To use the DoctrineCacheAdapter",
"ext-curl": "To send requests using cURL",
"ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages",
"ext-sockets": "To use client-side monitoring"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Aws\\": "src/"
},
"files": [
"src/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Amazon Web Services",
"homepage": "http://aws.amazon.com"
}
],
"description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
"homepage": "http://aws.amazon.com/sdkforphp",
"keywords": [
"amazon",
"aws",
"cloud",
"dynamodb",
"ec2",
"glacier",
"s3",
"sdk"
],
"time": "2019-02-20T19:11:08+00:00"
},
{
"name": "doctrine/event-manager",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/event-manager.git",
"reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/event-manager/zipball/a520bc093a0170feeb6b14e9d83f3a14452e64b3",
"reference": "a520bc093a0170feeb6b14e9d83f3a14452e64b3",
"shasum": ""
},
"require": {
"php": "^7.1"
},
"conflict": {
"doctrine/common": "<2.9@dev"
},
"require-dev": {
"doctrine/coding-standard": "^4.0",
"phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\": "lib/Doctrine/Common"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
},
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
}
],
"description": "Doctrine Event Manager component",
"homepage": "https://www.doctrine-project.org/projects/event-manager.html",
"keywords": [
"event",
"eventdispatcher",
"eventmanager"
],
"time": "2018-06-11T11:59:03+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "6.3.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"shasum": ""
},
"require": {
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.4",
"php": ">=5.5"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.0"
},
"suggest": {
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.3-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle is a PHP HTTP client library",
"homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"rest",
"web service"
],
"time": "2018-04-22T15:46:56+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "v1.3.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
"shasum": ""
},
"require": {
"php": ">=5.5.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"time": "2016-12-20T10:07:11+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "1.5.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "9f83dded91781a01c63574e387eaa769be769115"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
"reference": "9f83dded91781a01c63574e387eaa769be769115",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0",
"ralouphie/getallheaders": "^2.0.5"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.5-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"time": "2018-12-04T20:46:45+00:00"
},
{
"name": "mtdowling/jmespath.php",
"version": "2.4.0",
"source": {
"type": "git",
"url": "https://github.com/jmespath/jmespath.php.git",
"reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/adcc9531682cf87dfda21e1fd5d0e7a41d292fac",
"reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"bin": [
"bin/jp.php"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"autoload": {
"psr-4": {
"JmesPath\\": "src/"
},
"files": [
"src/JmesPath.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Declaratively specify how to extract elements from a JSON document",
"keywords": [
"json",
"jsonpath"
],
"time": "2016-12-03T22:08:25+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "ralouphie/getallheaders",
"version": "2.0.5",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
"reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "~3.7.0",
"satooshi/php-coveralls": ">=1.0"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"time": "2016-02-11T07:05:27+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.1"
},
"platform-dev": []
}