Initial commit

This commit is contained in:
2020-10-07 10:37:15 +02:00
commit ce5f440392
28157 changed files with 4429172 additions and 0 deletions

10
vendor/csa/guzzle-bundle/.styleci.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
preset: symfony
enabled:
- newline_after_open_tag
- ordered_use
- short_array_syntax
disabled:
- unalign_equals

View File

@@ -0,0 +1,76 @@
var Guzzle = Guzzle || {};
Guzzle.addClass = function(el, cssClass) { el.classList.add(cssClass); };
Guzzle.hasClass = function (el, cssClass) { return el.classList.contains(cssClass); };
Guzzle.removeClass = function(el, cssClass) { el.classList.remove(cssClass); };
Guzzle.createTabs = function() {
var tabGroups = document.querySelectorAll('.sf-tabs');
/* create the tab navigation for each group of tabs */
for (var i = 0; i < tabGroups.length; i++) {
var tabs = tabGroups[i].querySelectorAll('.tab');
var tabNavigation = document.createElement('ul');
tabNavigation.className = 'tab-navigation';
for (var j = 0; j < tabs.length; j++) {
var tabId = 'tab-' + i + '-' + j;
var tabTitle = tabs[j].querySelector('.tab-title').innerHTML;
var tabNavigationItem = document.createElement('li');
tabNavigationItem.setAttribute('data-tab-id', tabId);
if (j == 0) {
Guzzle.addClass(tabNavigationItem, 'active');
}
if (Guzzle.hasClass(tabs[j], 'disabled')) {
Guzzle.addClass(tabNavigationItem, 'disabled');
}
tabNavigationItem.innerHTML = tabTitle;
tabNavigation.appendChild(tabNavigationItem);
var tabContent = tabs[j].querySelector('.tab-content');
tabContent.parentElement.setAttribute('id', tabId);
}
tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild);
}
/* display the active tab and add the 'click' event listeners */
for (i = 0; i < tabGroups.length; i++) {
tabNavigation = tabGroups[i].querySelectorAll('.tab-navigation li');
for (j = 0; j < tabNavigation.length; j++) {
tabId = tabNavigation[j].getAttribute('data-tab-id');
document.getElementById(tabId).querySelector('.tab-title').className = 'hidden';
if (Guzzle.hasClass(tabNavigation[j], 'active')) {
document.getElementById(tabId).className = 'block';
} else {
document.getElementById(tabId).className = 'hidden';
}
tabNavigation[j].addEventListener('click', function (e) {
var activeTab = e.target || e.srcElement;
/* needed because when the tab contains HTML contents, user can click */
/* on any of those elements instead of their parent '<li>' element */
while (activeTab.tagName.toLowerCase() !== 'li') {
activeTab = activeTab.parentNode;
}
/* get the full list of tabs through the parent of the active tab element */
var tabNavigation = activeTab.parentNode.children;
for (var k = 0; k < tabNavigation.length; k++) {
var tabId = tabNavigation[k].getAttribute('data-tab-id');
document.getElementById(tabId).className = 'hidden';
Guzzle.removeClass(tabNavigation[k], 'active');
}
Guzzle.addClass(activeTab, 'active');
var activeTabId = activeTab.getAttribute('data-tab-id');
document.getElementById(activeTabId).className = 'block';
});
}
}
};

View File

@@ -0,0 +1,26 @@
var Guzzle = Guzzle || {};
Guzzle.accordion = function () {
var elements = document.querySelectorAll('.accordion .accordion-header');
for (var i = 0, l = elements.length, element; i < l, element = elements[i]; i++) {
element.addEventListener('click', function () {
this
.parentNode
.getElementsByClassName('accordion-content')[0]
.classList
.toggle('expanded')
;
});
var links = element.getElementsByTagName('a');
for (var j = 0, k = links.length, link; j < k, link = links[j]; j++) {
link.addEventListener('click', function (e) {
e.stopPropagation();
});
}
}
};
document.addEventListener('DOMContentLoaded', Guzzle.accordion, false);

View File

@@ -0,0 +1,6 @@
Prism.languages.json = {
'delimiter': /(\{|\}|\[|\])/g,
'boolean': /(true|false)/g,
'string': /("[^"]*")/g,
'number': /(-?\d+(\.\d+)?)/g
}

View File

@@ -0,0 +1 @@
Prism.languages.xml = Prism.languages.extend('markup');

View File

@@ -0,0 +1,36 @@
.tab-navigation
margin: 0 0 1em 0
padding: 0
li
background: #FFF
border: 1px solid #DDD
color: #444
cursor: pointer
display: inline-block
font-size: 16px
margin: 0 0 0 -1px
padding: .5em .75em
z-index: 1
&:hover
background: #EEE
&.disabled
background: #F5F5F5
color: #999
&.active
background: #666
border-color: #666
color: #FAFAFA
z-index: 1100
& > *:first-child
margin-top: 0
.block
display: block
.hidden
display: none

View File

@@ -0,0 +1,6 @@
@import "modules/variables"
@import "modules/mixins"
@import "modules/progressbar"
@import "modules/accordion"
@import "modules/badge"
@import "modules/screen"

View File

@@ -0,0 +1,10 @@
.accordion
.accordion-header
cursor: pointer
.accordion-content
display: none
transition: all 1s ease
&.expanded
display: block

View File

@@ -0,0 +1,20 @@
.badge
padding: 2px 9px
font-size: 0.75rem
font-weight: bold
white-space: nowrap
color: #ffffff
background-color: #999999
-webkit-border-radius: 9px
-moz-border-radius: 9px
border-radius: 9px
&.cache
&.hit
background-color: map-get($status-colors, success)
&.miss
background-color: map-get($status-colors, informational)
@each $status in server-error, client-error, redirection, success, informational, unknown
&.status-code.#{$status}
background-color: map-get($status-colors, $status)

View File

@@ -0,0 +1,2 @@
=make-stripes($color, $angle: 120deg, $width: 10px, $darken: 10%)
background: repeating-linear-gradient($angle, $color, $color 10px, darken($color, $darken) $width, darken($color, $darken) 2 * $width)

View File

@@ -0,0 +1,17 @@
.progress
background-color: $border-color
padding: 1px
height: 20px
.progress-bar
height: 20px
float: left
text-align: center
color: white
@each $level, $color in (warning: $warning, success: $success)
.progress-bar-#{$level}
background: $color
&.progress-bar-striped
@include make-stripes($color)

View File

@@ -0,0 +1,18 @@
.call
border-style: solid
border-color: $border-color
border-width: 1px 1px 1px 1px
margin: 5px
@each $i, $color in $method-colors
.#{$i}
background-color: $color
color: #fff
a
color: #fff
&:hover
background: lighten($color, 10%)
&>*
padding: 5px

View File

@@ -0,0 +1,12 @@
// Method colors
$error: #d9534f
$warning: #f0ad4e
$success: #5cb85c
$redirect: darken(#428bca, 6.5%)
$info: #5bc0de
$border-color: rgb(224, 224, 224)
//$border-color: #888
$method-colors: (get: #0f6ab4, post: #10a54a, put: #c5862b, delete: #a41e22, patch: #a41ee2, link: #C3D448, unlink: #FF8438, other: #000000)
$status-colors: (server-error: $error, client-error: $warning, redirection: $redirect, success: $success, informational: $info, unknown: $error)

6
vendor/csa/guzzle-bundle/bower.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "guzzle-bundle",
"dependencies": {
"prism": "gh-pages"
}
}

3265
vendor/csa/guzzle-bundle/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

61
vendor/csa/guzzle-bundle/gulpfile.js vendored Normal file
View File

@@ -0,0 +1,61 @@
var gulp = require('gulp')
, uglify = require('gulp-uglify')
, uglifycss = require('gulp-uglifycss')
, sass = require('gulp-sass')
, concat = require('gulp-concat')
;
gulp.task('default', ['js', 'css']);
gulp.task('js', ['js-guzzle', 'js-legacy']);
gulp.task('css', ['css-screen', 'css-legacy']);
gulp.task('watch', function () {
gulp.watch('assets-src/js/modules/*.js', ['js']);
gulp.watch('assets-src/js/legacy.js', ['js']);
gulp.watch('assets-src/sass/**/*.sass', ['css']);
gulp.watch('assets-src/sass/*.sass', ['css']);
});
gulp.task('js-legacy', function () {
gulp.src([
'assets-src/js/legacy.js'
])
.pipe(concat('legacy.min.js'))
.pipe(uglify())
.pipe(gulp.dest('src/Resources/public/js'))
});
gulp.task('js-guzzle', function () {
gulp.src([
'bower_components/prism/components/prism-core.js',
'bower_components/prism/components/prism-markup.js',
'bower_components/prism/plugins/line-numbers/prism-line-numbers.js',
'assets-src/js/modules/*.js'
])
.pipe(concat('guzzle.min.js'))
.pipe(uglify())
.pipe(gulp.dest('src/Resources/public/js'))
});
gulp.task('css-legacy', function () {
gulp.src([
'assets-src/sass/legacy.sass'
])
.pipe(sass())
.pipe(concat('legacy.min.css'))
.pipe(uglifycss())
.pipe(gulp.dest('src/Resources/public/css'))
});
gulp.task('css-screen', function () {
gulp.src([
'assets-src/sass/main.sass',
'bower_components/prism/themes/prism-okaidia.css'
])
.pipe(sass())
.pipe(concat('screen.min.css'))
.pipe(uglifycss())
.pipe(gulp.dest('src/Resources/public/css'))
});

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle;
use Csa\Bundle\GuzzleBundle\DependencyInjection\CompilerPass\LoaderPass;
use Csa\Bundle\GuzzleBundle\DependencyInjection\CompilerPass\SubscriberPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Csa Guzzle Bundle.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class CsaGuzzleBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new SubscriberPass());
$container->addCompilerPass(new LoaderPass());
}
}

View File

@@ -0,0 +1,133 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\DataCollector;
use Csa\Bundle\GuzzleBundle\GuzzleHttp\Subscriber\DebugSubscriber;
use GuzzleHttp\Stream\StreamInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
/**
* Csa Guzzle Collector.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class GuzzleCollector extends DataCollector
{
const MAX_BODY_SIZE = 0x10000;
private $history;
private $maxBodySize;
/**
* Constructor.
*
* @param DebugSubscriber $history the request history subscriber
*/
public function __construct(DebugSubscriber $history, $maxBodySize = self::MAX_BODY_SIZE)
{
$this->history = $history;
$this->maxBodySize = $maxBodySize;
$this->data = [];
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
$data = [];
foreach ($this->history as $transaction) {
$request = $transaction['request'];
$response = $transaction['response'];
$error = $transaction['exception'];
$info = $transaction['info'];
$req = [
'request' => [
'method' => $request->getMethod(),
'version' => $request->getProtocolVersion(),
'headers' => $request->getHeaders(),
'body' => $this->cropContent($request->getBody()),
],
'info' => $info,
'uri' => $request->getUrl(),
];
if ($response) {
$req['response'] = [
'reasonPhrase' => $response->getReasonPhrase(),
'headers' => $response->getHeaders(),
'body' => $this->cropContent($response->getBody()),
];
}
$req['httpCode'] = $response ? $response->getStatusCode() : 0;
if ($error) {
$req['error'] = [
'message' => $error->getMessage(),
'line' => $error->getLine(),
'file' => $error->getFile(),
'code' => $error->getCode(),
'trace' => $error->getTraceAsString(),
];
}
if ($cache = $request->getConfig()->get('cache_lookup')) {
$req['cache'] = $cache;
}
$data[] = $req;
}
$this->data = $data;
}
private function cropContent(StreamInterface $stream = null)
{
if (null === $stream) {
return '';
}
if ($stream->getSize() <= $this->maxBodySize) {
return (string) $stream;
}
$stream->seek(0);
return '(partial content)'.$stream->read($this->maxBodySize).'(...)';
}
public function getErrors()
{
return array_filter($this->data, function ($call) {
return isset($call['httpCode']) && $call['httpCode'] >= 400;
});
}
public function getCalls()
{
return $this->data;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'guzzle';
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Csa Guzzle definition loaders compiler pass.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class LoaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$ids = $container->findTaggedServiceIds('csa_guzzle.description_loader');
if (!count($ids)) {
return;
}
$resolverDefinition = $container->findDefinition('csa_guzzle.description_loader.resolver');
$loaders = [];
foreach ($ids as $id => $options) {
$loaders[] = new Reference($id);
}
$resolverDefinition->setArguments([$loaders]);
}
}

View File

@@ -0,0 +1,163 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\DependencyInjection\CompilerPass;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Csa Guzzle subscriber compiler pass.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class SubscriberPass implements CompilerPassInterface
{
const FACTORY_SERVICE_ID = 'csa_guzzle.client_factory';
const SUBSCRIBER_TAG = 'csa_guzzle.subscriber';
const CLIENT_TAG = 'csa_guzzle.client';
public function process(ContainerBuilder $container)
{
$subscribers = $container->findTaggedServiceIds(self::SUBSCRIBER_TAG);
$this->addSubscribersToClients($container, $subscribers);
if (!count($subscribers)) {
return;
}
$factory = $container->findDefinition(self::FACTORY_SERVICE_ID);
foreach ($subscribers as $subscriber => $options) {
$factory->addMethodCall('registerSubscriber', [
$options[0]['alias'],
new Reference($subscriber),
]);
}
}
/**
* Creates configurator service for each client to add registered subscribers
* to each client's emitter. Essentially it's converting the following XML.
*
* <service id="foo" class="GuzzleHttp\Client">
* <tag name="csa_guzzle.client" subscribers="foo, bar" />
* </service>
*
* to the following code in the container (rough equivalent)
*
* public function getFoo()
* {
* $client = new GuzzleHttp\Client();
* $client->getEmitter()->attach($this->get('foo'));
* $client->getEmitter()->attach($this->get('bar'));
*
* return $client;
* }
*
* @param ContainerBuilder $container
*/
private function addSubscribersToClients(ContainerBuilder $container, array $taggedSubscriberIds)
{
$subscriberIds = $this->mapSubscriberIdsByAlias($taggedSubscriberIds);
foreach ($this->findSubscriberAliasesByClientId($container) as $clientId => $aliases) {
$client = $container->findDefinition($clientId);
$configurator = $this->createConfigurator(array_values($aliases
? array_intersect_key($subscriberIds, array_flip($aliases))
: $subscriberIds
));
// Wraps the previous configurator in case the client already had one.
$configurator->addArgument($client->getConfigurator());
$configuratorId = sprintf('csa_guzzle._configurator.%s', $clientId);
$container->setDefinition($configuratorId, $configurator);
$client->setConfigurator([new Reference($configuratorId), 'configure']);
}
}
/**
* Finds all tagged clients and lists their registered subscribers by client
* ids. Empty arrays are returned for clients specifying no subscribers.
*
* @param ContainerBuilder $container
*
* @return array An array of subscriber ids with their consuming client as a key
*/
private function findSubscriberAliasesByClientId(ContainerBuilder $container)
{
$tagsByClientId = $container->findTaggedServiceIds(self::CLIENT_TAG);
$aliases = [];
foreach ($tagsByClientId as $clientId => $tags) {
$subscribers = [];
foreach ($tags as $tag) {
if (isset($tag['subscribers'])) {
$subscribers = array_merge(
$subscribers,
array_map('trim', explode(',', $tag['subscribers']))
);
}
}
$aliases[$clientId] = $subscribers;
}
return $aliases;
}
/**
* @param array $subscriberIds List of service ids pointing to subscribers
*
* @return Definition
*/
private function createConfigurator(array $subscriberIds)
{
$subscriberRefs = array_map(function ($subscriberId) {
return new Reference($subscriberId);
}, $subscriberIds);
$configurator = new Definition('Csa\Bundle\GuzzleBundle\DependencyInjection\Configurator\ClientConfigurator');
$configurator->setPublic(false);
$configurator->addArgument($subscriberRefs);
return $configurator;
}
/**
* @param array $subscriberIds
*
* @return array Subscriber service ids as values & their aliases as keys
*/
private function mapSubscriberIdsByAlias(array $subscriberIds)
{
$mapped = [];
foreach ($subscriberIds as $subscriberId => $tags) {
foreach ($tags as $tag) {
if (isset($tag['alias'])) {
$mapped[$tag['alias']] = $subscriberId;
}
}
}
return $mapped;
}
}

View File

@@ -0,0 +1,187 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\DependencyInjection;
use Csa\Bundle\GuzzleBundle\DataCollector\GuzzleCollector;
use GuzzleHttp\Subscriber\Log\Formatter;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This class contains the configuration information for the bundle.
*
* This information is solely responsible for how the different configuration
* sections are normalized, and merged.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('csa_guzzle');
$rootNode
->beforeNormalization()
->ifTrue(function ($v) {
return isset($v['factory_class']);
})
->then(function () {
@trigger_error('The ClientFactory class is deprecated since version 1.3 and will be removed in 2.0. Use the \'csa_guzzle.client\' tag instead', E_USER_DEPRECATED);
})
->end()
->fixXmlConfig('client')
->children()
->arrayNode('profiler')
->canBeEnabled()
->children()
->integerNode('max_body_size')
->info('The maximum size of the body which should be stored in the profiler (in bytes)')
->example('65536')
->defaultValue(GuzzleCollector::MAX_BODY_SIZE)
->end()
->end()
->end()
->arrayNode('logger')
->canBeEnabled()
->children()
->scalarNode('service')->defaultNull()->end()
->scalarNode('format')
->beforeNormalization()
->ifInArray(['clf', 'debug', 'short'])
->then(function ($v) {
return constant('GuzzleHttp\Subscriber\Log\Formatter::'.strtoupper($v));
})
->end()
->defaultValue(Formatter::CLF)
->end()
->end()
->end()
->append($this->createClientsNode())
->scalarNode('factory_class')->defaultValue('GuzzleHttp\Client')->end()
->append($this->createCacheNode())
->end()
;
return $treeBuilder;
}
private function createClientsNode()
{
$treeBuilder = new TreeBuilder();
$node = $treeBuilder->root('clients');
$node
->useAttributeAsKey('name')
->prototype('array')
->children()
->scalarNode('class')->defaultValue('GuzzleHttp\Client')->end()
->variableNode('config')->end()
->arrayNode('subscribers')
->useAttributeAsKey('subscriber_name')
->prototype('boolean')->end()
->end()
->scalarNode('description')
->validate()
->ifTrue(function ($path) {
return !is_readable($path) || is_dir($path);
})
->thenInvalid('File "%s" is not readable description file')
->end()
->validate()
->ifTrue(function () {
return !class_exists('GuzzleHttp\\Command\\Guzzle\\GuzzleClient');
})
->thenInvalid('Class %s is missing. Did you forget to add guzzlehttp/services to your project\'s composer.json?')
->end()
->end()
->scalarNode('alias')->defaultNull()->end()
->end()
->end()
;
return $node;
}
private function createCacheNode()
{
$treeBuilder = new TreeBuilder();
$node = $treeBuilder->root('cache');
$node
->beforeNormalization()
->ifTrue(function ($v) {
return isset($v['service']);
})
->then(function ($v) {
@trigger_error('The csa_guzzle.cache.service configuration key is deprecated since version 1.3 and will be removed in 2.0. Please directly use csa_guzzle.cache.adapter instead', E_USER_DEPRECATED);
return $v;
})
->end()
->validate()
->ifTrue(function ($v) {
return $v['enabled'] && null === $v['service'] && null === $v['adapter']['service'];
})
->thenInvalid('The csa_guzzle.cache.adapter key should be configured.')
->end()
->canBeEnabled()
->children()
->arrayNode('adapter')
->beforeNormalization()
->ifTrue(function ($v) {
return is_array($v) && (isset($v['type']) || isset($v['service']));
})
->then(function ($v) {
@trigger_error('The csa_guzzle.cache.adapter.type and csa_guzzle.cache.adapter.service configuration keys are deprecated since version 1.3 and will be removed in 2.0. Please directly use csa_guzzle.cache.adapter instead', E_USER_DEPRECATED);
return $v;
})
->end()
->beforeNormalization()
->ifString()
->then(function ($v) {
return [
'type' => 'custom',
'service' => $v,
];
})
->end()
->addDefaultsIfNotSet(['type' => 'doctrine'])
->validate()
->ifTrue(function ($v) {
return 'custom' === $v['type'] && null === $v['service'];
})
->thenInvalid('The "service" node is mandatory when using a custom adapter')
->end()
->children()
->scalarNode('type')
->defaultValue('doctrine')
->validate()
->ifNotInArray(['doctrine', 'custom'])
->thenInvalid('Invalid cache adapter')
->end()
->end()
->scalarNode('service')->defaultNull()->end()
->end()
->end()
->scalarNode('service')->defaultNull()->end()
->end()
;
return $node;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\DependencyInjection\Configurator;
use GuzzleHttp\ClientInterface;
class ClientConfigurator
{
/**
* @var array|\Traversable
*/
private $subscribers;
/**
* @var callable|null
*/
private $parentConfigurator;
/**
* @param array|\Traversable $subscribers
* @param callable|null $parentConfigurator
*/
public function __construct($subscribers = [], $parentConfigurator = null)
{
$this->subscribers = $subscribers;
$this->parentConfigurator = $parentConfigurator;
}
/**
* @param ClientInterface $client
*/
public function configure(ClientInterface $client)
{
if ($this->parentConfigurator) {
call_user_func($this->parentConfigurator, $client);
}
foreach ($this->subscribers as $subscriber) {
$client->getEmitter()->attach($subscriber);
}
}
}

View File

@@ -0,0 +1,159 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\DependencyInjection;
use Csa\Bundle\GuzzleBundle\DependencyInjection\CompilerPass\SubscriberPass;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
/**
* Csa Guzzle Extension.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class CsaGuzzleExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$config = $this->processConfiguration(new Configuration(), $configs);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('subscribers.xml');
$loader->load('collector.xml');
$loader->load('twig.xml');
$loader->load('factory.xml');
$loader->load('services.xml');
$descriptionFactory = $container->getDefinition('csa_guzzle.description_factory');
$dataCollector = $container->getDefinition('csa_guzzle.data_collector.guzzle');
$dataCollector->addArgument($config['profiler']['max_body_size']);
if (!$config['profiler']['enabled']) {
$container->removeDefinition('csa_guzzle.subscriber.debug');
$container->removeDefinition('csa_guzzle.subscriber.stopwatch');
$container->removeDefinition('csa_guzzle.data_collector.guzzle');
$container->removeDefinition('csa_guzzle.twig.extension');
}
$loggerDefinition = $container->getDefinition('csa_guzzle.subscriber.logger');
if ($config['logger']['service']) {
$loggerDefinition->replaceArgument(0, new Reference($config['logger']['service']));
}
if ($config['logger']['format']) {
$loggerDefinition->replaceArgument(1, $config['logger']['format']);
}
if (!$config['logger']['enabled']) {
$container->removeDefinition('csa_guzzle.subscriber.logger');
}
$this->processCacheConfiguration($config['cache'], $container);
$definition = $container->getDefinition('csa_guzzle.client_factory');
$definition->replaceArgument(0, $config['factory_class']);
$this->processClientsConfiguration($config, $container, $descriptionFactory);
}
private function processCacheConfiguration(array $config, ContainerBuilder $container)
{
if (!$config['enabled']) {
$container->removeDefinition('csa_guzzle.subscriber.cache');
return;
}
$adapterId = $config['adapter']['service'];
if ('doctrine' === $config['adapter']['type']) {
$adapterId = 'csa_guzzle.cache.adapter.doctrine';
$adapter = $container->getDefinition($adapterId);
$adapter->addArgument(new Reference($config['service']));
}
$container->setAlias('csa_guzzle.default_cache_adapter', $adapterId);
}
private function processClientsConfiguration(array $config, ContainerBuilder $container, Definition $descriptionFactory)
{
foreach ($config['clients'] as $name => $options) {
$client = new Definition($options['class']);
if (isset($options['config'])) {
if (!is_array($options['config'])) {
throw new InvalidArgumentException(sprintf(
'Config for "csa_guzzle.client.%s" should be an array, but got %s',
$name,
gettype($options['config'])
));
}
$client->addArgument($this->buildGuzzleConfig($options['config']));
}
$subscribers = $this->findSubscriberIds($options['subscribers']);
$client->addTag(
SubscriberPass::CLIENT_TAG,
count($subscribers) ? ['subscribers' => implode(',', $subscribers)] : []
);
$clientServiceId = sprintf('csa_guzzle.client.%s', $name);
$container->setDefinition($clientServiceId, $client);
if (isset($options['description'])) {
$descriptionFactory->addMethodCall('addResource', [$name, $options['description']]);
$serviceDefinition = new DefinitionDecorator('csa_guzzle.service.abstract');
$serviceDefinition->addArgument(new Reference($clientServiceId));
$serviceDefinition->addArgument(new Expression(sprintf(
'service("csa_guzzle.description_factory").getDescription("%s")',
$name
)));
$container->setDefinition(sprintf('csa_guzzle.service.%s', $name), $serviceDefinition);
}
if (isset($options['alias'])) {
$container->setAlias($options['alias'], $clientServiceId);
}
}
}
private function findSubscriberIds(array $explicitlyConfiguredIds)
{
return array_filter(array_keys($explicitlyConfiguredIds), function ($key) use ($explicitlyConfiguredIds) {
return isset($explicitlyConfiguredIds[$key]) && $explicitlyConfiguredIds[$key];
});
}
private function buildGuzzleConfig(array $config)
{
foreach (['message_factory', 'fsm', 'adapter', 'handler'] as $service) {
if (isset($config[$service])) {
$config[$service] = new Reference($config[$service]);
}
}
return $config;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Description\CacheWarmer;
use Csa\Bundle\GuzzleBundle\Factory\DescriptionFactory;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer;
/**
* Warms up the service description cache.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class DescriptionCacheWarmer extends CacheWarmer
{
private $factory;
public function __construct(DescriptionFactory $factory)
{
$this->factory = $factory;
}
/**
* {@inheritdoc}
*/
public function warmUp($cacheDir)
{
$this->factory->loadDescriptions();
}
/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Description\Loader;
use Symfony\Component\Config\Loader\Loader;
/**
* Loads descriptions from JSON files.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class JsonLoader extends Loader
{
/**
* {@inheritdoc}
*/
public function load($resource, $type = null)
{
return json_decode(file_get_contents($resource), true);
}
/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return 'json' === pathinfo($resource, PATHINFO_EXTENSION);
}
}

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Factory;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Event\HasEmitterInterface;
use GuzzleHttp\Event\SubscriberInterface;
/**
* Csa Guzzle client compiler pass.
*
* @author Charles Sarrazin <charles@sarraz.in>
*
* @deprecated since version 1.3, to be removed in 2.0
*/
class ClientFactory
{
private $class;
private $subscribers;
/**
* @param string $class The client's class
*/
public function __construct($class)
{
$this->class = $class;
$this->subscribers = [];
}
/**
* Creates a Guzzle client.
*
* @param array $options
* @param array $subscribers
*
* @return ClientInterface
*/
public function create(array $options = [], array $subscribers = [])
{
@trigger_error('The ClientFactory class is deprecated since version 1.3 and will be removed in 2.0. Use the \'csa_guzzle.client\' tag instead', E_USER_DEPRECATED);
$client = new $this->class($options);
if ($client instanceof HasEmitterInterface) {
foreach ($this->subscribers as $name => $subscriber) {
if (empty($subscribers) || (isset($subscribers[$name]) && $subscribers[$name])) {
$client->getEmitter()->attach($subscriber);
}
}
}
return $client;
}
public function registerSubscriber($name, SubscriberInterface $subscriber)
{
@trigger_error('The ClientFactory class is deprecated since version 1.3 and will be removed in 2.0. Use the \'csa_guzzle.client\' tag instead', E_USER_DEPRECATED);
$this->subscribers[$name] = $subscriber;
}
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Factory;
use GuzzleHttp\Command\Guzzle\Description;
use Symfony\Component\Config\ConfigCache;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\FileResource;
class DescriptionFactory
{
private $resources = [];
private $loader;
private $debug;
private $descriptions = [];
private $cacheDir;
public function __construct(LoaderInterface $loader, $cacheDir, $debug = false)
{
$this->loader = $loader;
$this->cacheDir = $cacheDir;
$this->debug = $debug;
}
public function addResource($alias, $resource)
{
$this->resources[$alias] = $resource;
}
public function getResources()
{
return $this->resources;
}
public function getDescription($alias)
{
$this->loadDescriptions();
if (!isset($this->descriptions[$alias])) {
throw new \InvalidArgumentException('Unknown description alias');
}
return new Description($this->descriptions[$alias]);
}
public function loadDescriptions()
{
if (!empty($this->descriptions)) {
return;
}
$class = 'descriptionsMetadata';
$cachePath = $this->cacheDir.'/csa/guzzle/'.$class.'.php';
$resources = [];
$descriptions = [];
$cache = new ConfigCache($cachePath, $this->debug);
if (!$cache->isFresh()) {
foreach ($this->getResources() as $alias => $resource) {
$resources[] = new FileResource($resource);
$descriptions[$alias] = $this->loader->load($resource);
}
$descriptions = var_export($descriptions, true);
$code = "<?php return $descriptions;";
$cache->write($code, $resources);
}
$this->descriptions = require_once $cachePath;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\GuzzleHttp\Cache;
use Doctrine\Common\Cache\Cache;
use GuzzleHttp\Message\MessageFactory;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Message\ResponseInterface;
class DoctrineAdapter implements StorageAdapterInterface
{
private $cache;
private $ttl;
private $messageFactory;
public function __construct(Cache $cache, $ttl = 0)
{
$this->cache = $cache;
$this->ttl = $ttl;
}
public function fetch(RequestInterface $request)
{
$key = $this->getKey($request);
if ($this->cache->contains($key)) {
return $this->getMessageFactory()->fromMessage($this->cache->fetch($key));
}
}
public function save(RequestInterface $request, ResponseInterface $response)
{
$this->cache->save($this->getKey($request), (string) $response, $this->ttl);
}
private function getMessageFactory()
{
if (null === $this->messageFactory) {
$this->messageFactory = new MessageFactory();
}
return $this->messageFactory;
}
private function getKey(RequestInterface $request)
{
return md5(serialize([
'method' => $request->getMethod(),
'uri' => $request->getUrl(),
'headers' => $request->getHeaders(),
'body' => (string) $request->getBody(),
]));
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\GuzzleHttp\Cache;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Message\ResponseInterface;
interface StorageAdapterInterface
{
public function fetch(RequestInterface $request);
public function save(RequestInterface $request, ResponseInterface $response);
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\GuzzleHttp\Subscriber;
use Csa\Bundle\GuzzleBundle\GuzzleHttp\Cache\StorageAdapterInterface;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
/**
* Csa Guzzle Cache integration.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class CacheSubscriber implements SubscriberInterface
{
private $storage;
public function __construct(StorageAdapterInterface $storage)
{
$this->storage = $storage;
}
public function getEvents()
{
return [
'before' => ['onBefore', RequestEvents::LATE],
'complete' => ['onComplete', RequestEvents::EARLY],
];
}
public function onBefore(BeforeEvent $event)
{
$request = $event->getRequest();
if (!$response = $this->storage->fetch($request)) {
$request->getConfig()->set('cache_lookup', 'MISS');
return;
}
$request->getConfig()->set('cache_lookup', 'HIT');
$request->getConfig()->set('cache_hit', true);
$event->intercept($response);
}
public function onComplete(CompleteEvent $event)
{
$this->storage->save($event->getRequest(), $event->getResponse());
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\GuzzleHttp\Subscriber;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\ErrorEvent;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Message\RequestInterface;
use GuzzleHttp\Message\ResponseInterface;
/**
* Csa Guzzle Profiler integration.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class DebugSubscriber implements SubscriberInterface, \IteratorAggregate
{
/**
* @var array an array of guzzle transactions (requests and responses)
*/
private $transactions = [];
public function getEvents()
{
return [
'complete' => ['onComplete', RequestEvents::LATE],
'error' => ['onError', RequestEvents::EARLY],
];
}
public function onComplete(CompleteEvent $event)
{
$this->add($event->getRequest(), $event->getTransferInfo(), $event->getResponse());
}
public function onError(ErrorEvent $event)
{
$this->add($event->getRequest(), $event->getTransferInfo(), $event->getResponse(), $event->getException());
}
/**
* Returns an Iterator that yields associative array values where each
* associative array contains a 'request' and 'response' key.
*
* @return \Iterator
*/
public function getIterator()
{
return new \ArrayIterator($this->transactions);
}
/**
* Add a request to the history.
*
* @param RequestInterface $request request to add
* @param array $info transfer info
* @param ResponseInterface $response response of the request
* @param RequestException $exception the exception thrown during the request, if any
*/
private function add(
RequestInterface $request,
array $info = [],
ResponseInterface $response = null,
RequestException $exception = null
) {
if (isset($this->transactions[$hash = spl_object_hash($request).spl_object_hash($response ?: $exception)])) {
return;
}
$this->transactions[$hash] = [
'request' => $request,
'response' => $response,
'info' => $info,
'exception' => $exception,
];
}
}

View File

@@ -0,0 +1,70 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\GuzzleHttp\Subscriber;
use GuzzleHttp\Event\BeforeEvent;
use GuzzleHttp\Event\CompleteEvent;
use GuzzleHttp\Event\ErrorEvent;
use GuzzleHttp\Event\RequestEvents;
use GuzzleHttp\Event\SubscriberInterface;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Csa Guzzle Stopwatch integration.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class StopwatchSubscriber implements SubscriberInterface
{
private $stopwatch;
public function __construct(Stopwatch $stopwatch)
{
$this->stopwatch = $stopwatch;
}
public function getEvents()
{
return [
'before' => ['onBefore', RequestEvents::EARLY],
'complete' => ['onFinish', RequestEvents::LATE],
'error' => ['onError', RequestEvents::EARLY],
];
}
public function onBefore(BeforeEvent $event)
{
$this->stopwatch->start($event->getRequest()->getUrl(), 'guzzle');
}
public function onFinish(CompleteEvent $event)
{
$url = $event->getRequest()->getUrl();
if (!$this->stopwatch->isStarted($url)) {
return;
}
$this->stopwatch->stop($url);
}
public function onError(ErrorEvent $event)
{
$url = $event->getRequest()->getUrl();
if (!$this->stopwatch->isStarted($url)) {
return;
}
$this->stopwatch->stop($url);
}
}

View File

@@ -0,0 +1,61 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\HttpFoundation;
use GuzzleHttp\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Response;
class StreamResponse extends Response
{
const BUFFER_SIZE = 4096;
private $bufferSize;
public function __construct(ResponseInterface $response, $bufferSize = self::BUFFER_SIZE)
{
parent::__construct(null, $response->getStatusCode(), $response->getHeaders());
$this->content = $response->getBody();
$this->bufferSize = $bufferSize;
}
public function sendContent()
{
$chunked = $this->headers->has('Transfer-Encoding');
$this->content->seek(0);
for (; ;) {
$chunk = $this->content->read($this->bufferSize);
if ($chunked) {
echo sprintf("%x\r\n", strlen($chunk));
}
echo $chunk;
if ($chunked) {
echo "\r\n";
}
flush();
if (!$chunk) {
return;
}
}
}
public function getContent()
{
return false;
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="csa_guzzle.data_collector.guzzle" class="Csa\Bundle\GuzzleBundle\DataCollector\GuzzleCollector">
<argument type="service" id="csa_guzzle.subscriber.debug" />
<tag name="data_collector" template="CsaGuzzleBundle:Collector:guzzle" id="guzzle" />
</service>
</services>
</container>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="csa_guzzle.client_factory" class="Csa\Bundle\GuzzleBundle\Factory\ClientFactory">
<argument />
<argument type="collection" />
</service>
</services>
</container>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="csa_guzzle.description_loader.json" class="Csa\Bundle\GuzzleBundle\Description\Loader\JsonLoader" public="false">
<tag name="csa_guzzle.description_loader" />
</service>
<service id="csa_guzzle.description_loader" class="Symfony\Component\Config\Loader\DelegatingLoader">
<argument type="service" id="csa_guzzle.description_loader.resolver" />
</service>
<service id="csa_guzzle.description_loader.resolver" class="Symfony\Component\Config\Loader\LoaderResolver" />
<service id="csa_guzzle.description_factory" class="Csa\Bundle\GuzzleBundle\Factory\DescriptionFactory">
<argument type="service" id="csa_guzzle.description_loader" />
<argument>%kernel.cache_dir%</argument>
<argument>%kernel.debug%</argument>
</service>
<service id="csa_guzzle.cache_warmer.description" class="Csa\Bundle\GuzzleBundle\Description\CacheWarmer\DescriptionCacheWarmer" public="false">
<argument type="service" id="csa_guzzle.description_factory" />
<tag name="kernel.cache_warmer" />
</service>
<service id="csa_guzzle.service.abstract" class="GuzzleHttp\Command\Guzzle\GuzzleClient" abstract="true" />
</services>
</container>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="csa_guzzle.subscriber.stopwatch" class="Csa\Bundle\GuzzleBundle\GuzzleHttp\Subscriber\StopwatchSubscriber">
<argument type="service" id="debug.stopwatch" />
<tag name="csa_guzzle.subscriber" alias="stopwatch" />
</service>
<service id="csa_guzzle.subscriber.debug" class="Csa\Bundle\GuzzleBundle\GuzzleHttp\Subscriber\DebugSubscriber">
<tag name="csa_guzzle.subscriber" alias="debug" />
</service>
<service id="csa_guzzle.subscriber.logger" class="GuzzleHttp\Subscriber\Log\LogSubscriber">
<argument type="service" id="logger" on-invalid="null" />
<argument />
<tag name="csa_guzzle.subscriber" alias="logger" />
</service>
<service id="csa_guzzle.subscriber.cache" class="Csa\Bundle\GuzzleBundle\GuzzleHttp\Subscriber\CacheSubscriber">
<argument type="service" id="csa_guzzle.default_cache_adapter" />
<tag name="csa_guzzle.subscriber" alias="cache" />
</service>
<service id="csa_guzzle.cache.adapter.doctrine" class="Csa\Bundle\GuzzleBundle\GuzzleHttp\Cache\DoctrineAdapter" />
</services>
</container>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="csa_guzzle.twig.extension" class="Csa\Bundle\GuzzleBundle\Twig\Extension\GuzzleExtension">
<tag name="twig.extension" />
</service>
</services>
</container>

View File

@@ -0,0 +1,19 @@
Copyright (c) 2014-2015 Charles Sarrazin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1 @@
.tab-navigation{margin:0 0 1em 0;padding:0}.tab-navigation li{background:#FFF;border:1px solid #DDD;color:#444;cursor:pointer;display:inline-block;font-size:16px;margin:0 0 0 -1px;padding:.5em .75em;z-index:1}.tab-navigation li:hover{background:#EEE}.tab-navigation li.disabled{background:#f5f5f5;color:#999}.tab-navigation li.active{background:#666;border-color:#666;color:#fafafa;z-index:1100}.tab-navigation>*:first-child{margin-top:0}.block{display:block}.hidden{display:none}

View File

@@ -0,0 +1 @@
.progress{background-color:#e0e0e0;padding:1px;height:20px}.progress .progress-bar{height:20px;float:left;text-align:center;color:white}.progress .progress-bar-warning{background:#f0ad4e}.progress .progress-bar-warning.progress-bar-striped{background:repeating-linear-gradient(120deg,#f0ad4e,#f0ad4e 10px,#ec971f 10px,#ec971f 20px)}.progress .progress-bar-success{background:#5cb85c}.progress .progress-bar-success.progress-bar-striped{background:repeating-linear-gradient(120deg,#5cb85c,#5cb85c 10px,#449d44 10px,#449d44 20px)}.accordion .accordion-header{cursor:pointer}.accordion .accordion-content{display:none;transition:all 1s ease}.accordion .accordion-content.expanded{display:block}.badge{padding:2px 9px;font-size:.75rem;font-weight:bold;white-space:nowrap;color:#fff;background-color:#999;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.badge.cache.hit{background-color:#5cb85c}.badge.cache.miss{background-color:#5bc0de}.badge.status-code.server-error{background-color:#d9534f}.badge.status-code.client-error{background-color:#f0ad4e}.badge.status-code.redirection{background-color:#337ab7}.badge.status-code.success{background-color:#5cb85c}.badge.status-code.informational{background-color:#5bc0de}.badge.status-code.unknown{background-color:#d9534f}.call{border-style:solid;border-color:#e0e0e0;border-width:1px 1px 1px 1px;margin:5px}.call .get{background-color:#0f6ab4;color:#fff}.call .get a{color:#fff}.call .get:hover{background:#1386e3}.call .post{background-color:#10a54a;color:#fff}.call .post a{color:#fff}.call .post:hover{background:#15d35f}.call .put{background-color:#c5862b;color:#fff}.call .put a{color:#fff}.call .put:hover{background:#d89e4b}.call .delete{background-color:#a41e22;color:#fff}.call .delete a{color:#fff}.call .delete:hover{background:#cf262b}.call .patch{background-color:#a41ee2;color:#fff}.call .patch a{color:#fff}.call .patch:hover{background:#b64be8}.call .link{background-color:#c3d448;color:#fff}.call .link a{color:#fff}.call .link:hover{background:#d1de71}.call .unlink{background-color:#ff8438;color:#fff}.call .unlink a{color:#fff}.call .unlink:hover{background:#ffa46b}.call .other{background-color:#000;color:#fff}.call .other a{color:#fff}.call .other:hover{background:#1a1a1a}.call>*{padding:5px}code[class*="language-"],pre[class*="language-"]{color:#f8f8f2;text-shadow:0 1px rgba(0,0,0,0.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*="language-"]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*="language-"],pre[class*="language-"]{background:#272822}:not(pre)>code[class*="language-"]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:slategray}.token.punctuation{color:#f8f8f2}.namespace{opacity:.7}.token.property,.token.tag,.token.constant,.token.symbol,.token.deleted{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.inserted{color:#a6e22e}.token.operator,.token.entity,.token.url,.language-css .token.string,.style .token.string,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.regex,.token.important{color:#fd971f}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
var Guzzle=Guzzle||{};Guzzle.addClass=function(e,t){e.classList.add(t)},Guzzle.hasClass=function(e,t){return e.classList.contains(t)},Guzzle.removeClass=function(e,t){e.classList.remove(t)},Guzzle.createTabs=function(){for(var e=document.querySelectorAll(".sf-tabs"),t=0;t<e.length;t++){var a=e[t].querySelectorAll(".tab"),l=document.createElement("ul");l.className="tab-navigation";for(var n=0;n<a.length;n++){var s="tab-"+t+"-"+n,r=a[n].querySelector(".tab-title").innerHTML,d=document.createElement("li");d.setAttribute("data-tab-id",s),0==n&&Guzzle.addClass(d,"active"),Guzzle.hasClass(a[n],"disabled")&&Guzzle.addClass(d,"disabled"),d.innerHTML=r,l.appendChild(d);var i=a[n].querySelector(".tab-content");i.parentElement.setAttribute("id",s)}e[t].insertBefore(l,e[t].firstChild)}for(t=0;t<e.length;t++)for(l=e[t].querySelectorAll(".tab-navigation li"),n=0;n<l.length;n++)s=l[n].getAttribute("data-tab-id"),document.getElementById(s).querySelector(".tab-title").className="hidden",Guzzle.hasClass(l[n],"active")?document.getElementById(s).className="block":document.getElementById(s).className="hidden",l[n].addEventListener("click",function(e){for(var t=e.target||e.srcElement;"li"!==t.tagName.toLowerCase();)t=t.parentNode;for(var a=t.parentNode.children,l=0;l<a.length;l++){var n=a[l].getAttribute("data-tab-id");document.getElementById(n).className="hidden",Guzzle.removeClass(a[l],"active")}Guzzle.addClass(t,"active");var s=t.getAttribute("data-tab-id");document.getElementById(s).className="block"})};

View File

@@ -0,0 +1,43 @@
{% import '@CsaGuzzle/Calls/macros.html.twig' as macros %}
<div class="accordion">
{% for call in calls %}
<section class="call">
<header class="accordion-header {{ call.request.method|lower }}">
<span class="method-name">{{ call.request.method }}</span>
{% if call.request.method == 'GET' %}
<a href="{{ call.uri }}" target="_blank" class="path">{{ call.uri }}</a>
{% else %}
<span class="path">{{ call.uri }}</span>
{% endif %}
{% set statusCode = call.httpCode %}
<span class="badge status-code {{ statusCode|csa_guzzle_status_code_class }}">
{{ statusCode }} - {{ call.response is defined ? call.response.reasonPhrase : 'Unknown error' }}
</span>
{% if call.cache is defined %}<span class="badge cache {{ call.cache|lower }}">Cache {{ call.cache }}</span>{% endif %}
</header>
<div class="accordion-content{{ loop.first ? ' expanded': '' }}">
<div class="sf-tabs">
<div class="tab">
<h3 class="tab-title">Request</h3>
<div class="tab-content">
{{ macros.render_infos(call.info) }}
{{ macros.render_headers(call.request.headers, call.uri) }}
{{ macros.render_body(call.request.body) }}
</div>
</div>
{% if call.response is defined %}
<div class="tab">
<h3 class="tab-title">Response</h3>
<div class="tab-content">
{{ macros.render_headers(call.response.headers, call.uri) }}
{{ macros.render_body(call.response.body) }}
</div>
</div>
{% endif %}
</div>
</div>
</section>
{% endfor %}
</div>

View File

@@ -0,0 +1,90 @@
{% macro render_headers(headers, uri) %}
<h4>Headers</h4>
<table>
<thead>
<tr>
<th scope="col" class="key">Header</th>
<th scope="col">Value</th>
</tr>
</thead>
{% for header, values in headers %}
<tr>
<td>{{ header }}</td>
<td>
{% if values|length > 1 %}
<ul>
{% for value in values %}
<li>{{ value }}</li>
{% endfor %}
</ul>
{% else %}
{% if header == 'X-Debug-Token-Link' %}
<a href="{{ uri|csa_guzzle_short_uri }}{{ values.0}}" target="_blank">{{ values.0 }}</a>
{% else %}
{{ values.0 }}
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endmacro %}
{% macro render_body(body) %}
{% if body is not empty %}
<h4>Content</h4>
{% set lang = csa_guzzle_detect_lang(body) %}
<pre><code class="language-{{ lang }}">{{ body|csa_guzzle_pretty_print(lang) }}</code></pre>
{% endif %}
{% endmacro %}
{% macro render_infos(info) %}
{% if info and info.total_time %}
<h4>Time</h4>
{% set wait_time = info.namelookup_time + info.connect_time + info.redirect_time %}
{% set process_time = info.total_time - wait_time %}
<table>
<thead>
<tr>
<th>Measure</th>
<th>Duration</th>
</tr>
</thead>
<tr>
<td>Total</td>
<td>{{ info.total_time|csa_guzzle_format_duration }}</td>
</tr>
<tr>
<td>Name lookup</td>
<td>{{ info.namelookup_time|csa_guzzle_format_duration }}</td>
</tr>
<tr>
<td>Connection</td>
<td>{{ info.connect_time|csa_guzzle_format_duration }}</td>
</tr>
{% if info.redirect_time %}
<tr>
<td>Redirect</td>
<td>{{ info.redirect_time|csa_guzzle_format_duration }}</td>
</tr>
{% endif %}
<tr>
<td>Process</td>
<td>{{ process_time | csa_guzzle_format_duration }}</td>
</tr>
</table>
<div class="progress">
<div class="progress-bar progress-bar-warning" style="width: {{ wait_time/info.total_time * 100 }}%">
<span class="sr-only">Wait</span>
</div>
<div class="progress-bar progress-bar-success" style="width: {{ process_time/info.total_time * 100 }}%">
<span class="sr-only">Process</span>
</div>
</div>
{% endif %}
{% endmacro %}

View File

@@ -0,0 +1,76 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block toolbar %}
{% set profiler_markup_version = profiler_markup_version|default(1) %}
{% set callCount = collector.calls|length %}
{% set errorCount = collector.errors|length %}
{% if callCount == 0 %}
{% set color_code = '' %}
{% elseif errorCount > 0 %}
{% set color_code = 'red' %}
{% else %}
{% set color_code = 'green' %}
{% endif %}
{% set icon %}
{% if profiler_markup_version == 1 %}
{{ include('@CsaGuzzle/Icon/guzzle.svg', { height: 28, color: '#3F3F3F' }) }}
<span class="sf-toolbar-status sf-toolbar-status-{{ color_code }}">{{ callCount }}</span>
{% else %}
{{ include('@CsaGuzzle/Icon/guzzle.svg') }}
<span class="sf-toolbar-value">{{ callCount }}</span>
{% endif %}
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>Success</b>
<span class="sf-toolbar-status sf-toolbar-status-green">{{ callCount - errorCount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Errors</b>
<span class="sf-toolbar-status sf-toolbar-status-red">{{ errorCount }}</span>
</div>
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: 'guzzle', status: color_code }) }}
{% endblock %}
{% block head %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('bundles/csaguzzle/css/screen.min.css') }}">
<script src="{{ asset('bundles/csaguzzle/js/guzzle.min.js') }}"></script>
{% set profiler_markup_version = profiler_markup_version|default(1) %}
{% if profiler_markup_version == 1 %}
<link rel="stylesheet" href="{{ asset('bundles/csaguzzle/css/legacy.min.css') }}">
<script src="{{ asset('bundles/csaguzzle/js/legacy.min.js') }}"></script>
<script type="text/javascript">window.addEventListener('load', Guzzle.createTabs, false);</script>
{% endif %}
{% endblock %}
{% block menu %}
<span class="label {% if collector.errors|length > 0 %}label-status-error{% endif %}">
<span class="icon">
{% set profiler_markup_version = profiler_markup_version|default(1) %}
{% if profiler_markup_version == 1 %}
{{ include('@CsaGuzzle/Icon/guzzle.svg', { height: 30, color: '#3F3F3F' }) }}
{% else %}
{{ include('@CsaGuzzle/Icon/guzzle.svg')}}
{% endif %}
</span>
<strong>{{ collector.name|capitalize|default('HTTP calls') }}</strong>
<span class="count">
{% if collector.calls is not empty %}
<span>{{ collector.calls|length }}</span>
{% endif %}
</span>
</span>
{% endblock %}
{% block panel %}
<h2>{{ (collector.name|capitalize)|default('HTTP calls') }}</h2>
{{ include('@CsaGuzzle/Calls/list.html.twig', { calls: collector.calls }) }}
{% endblock %}

View File

@@ -0,0 +1,3 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" height="{{ height|default(24) }}" viewBox="0 20 70 110" xml:space="preserve">
<path fill="{{ color|default('#AAAAAA') }}" d="m31.7 59c0 1.2-0.8 2-2 2l-22 0c-1.2 0-2-0.8-2-2l0-19c0-1.2 0.8-2 2-2l22 0c1.2 0 2 0.8 2 2zm26.6 2.4c-2.8-0.6-3.9-2.1-3.9-3.4l0-4.9c0 0 1.9 0.4 2.3 0.5 0.9 0.2 1.5 1.7 1.5 2.9 0 1.2 0 4.9 0 4.9zm5.4 29.7c0 0-1-5.9-1-5.9l0-27.6c0-3-1-5.5-3-7.5l-12-13c-0.9 0-3 3-3 3 0 0.9 6 7 6 7l0 11c1.2 5.5 3.7 6.9 8 7l0 20 1 7 4 21c0.1 3.4-3.6 6-6 6-2.2 0-6-2.6-6-5l0-36c0-4.4-2.6-7-7-7l-7 0c0-9.3 0-21.7 0-32 0-4.3-1.7-6-6-6l-26 0c-4.3 0-6 1.7-6 6l0 84 38 0 0-48 7 0c1.6 0 3 1.4 3 3l0 36c0 4.9 4.8 9 10 9 4.9 0 10-3.8 10-9-0.1-1.2-4-23-4-23z" />
</svg>

After

Width:  |  Height:  |  Size: 738 B

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Tests\DataCollector;
use Csa\Bundle\GuzzleBundle\DataCollector\GuzzleCollector;
use Csa\Bundle\GuzzleBundle\GuzzleHttp\Subscriber\DebugSubscriber;
use GuzzleHttp\Client;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Subscriber\Mock;
use Symfony\Component\HttpFoundation\Request;
/**
* @covers \Csa\Bundle\GuzzleBundle\DataCollector\GuzzleCollector
*/
class GuzzleCollectorTest extends \PHPUnit_Framework_TestCase
{
public function testCollect()
{
$mocks = array_fill(0, 3, new Response(204));
$mockSubscriber = new Mock($mocks);
$client = new Client();
$client->getEmitter()->attach($mockSubscriber);
$debugSubscriber = new DebugSubscriber();
$client->getEmitter()->attach($debugSubscriber);
$collector = new GuzzleCollector($debugSubscriber);
$request = Request::createFromGlobals();
$response = $this->getMock('Symfony\Component\HttpFoundation\Response');
$collector->collect($request, $response, new \Exception());
$this->assertCount(0, $collector->getCalls());
$client->get('http://foo.bar');
$collector->collect($request, $response, new \Exception());
$this->assertCount(1, $collector->getCalls());
$client->get('http://foo.bar');
$collector->collect($request, $response, new \Exception());
$this->assertCount(2, $collector->getCalls());
}
}

View File

@@ -0,0 +1,134 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Tests\DependencyInjection\CompilerPass;
use Csa\Bundle\GuzzleBundle\DependencyInjection\CompilerPass\SubscriberPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class SubscriberPassTest extends \PHPUnit_Framework_TestCase
{
public function testSubscriberRegisteredToFactory()
{
$container = $this->createContainer();
$container->setDefinition('sub', $this->createSubscriber('my_sub'));
$pass = new SubscriberPass();
$pass->process($container);
$calls = $container
->findDefinition(SubscriberPass::FACTORY_SERVICE_ID)
->getMethodCalls()
;
$this->assertCount(1, $calls);
$methodName = $calls[0][0];
$methodArgs = $calls[0][1];
$this->assertEquals('registerSubscriber', $methodName);
$this->assertEquals('my_sub', $methodArgs[0]);
$this->assertEquals('sub', (string) $methodArgs[1]);
}
public function testAllSubscribersAddedToTaggedClientsByDefault()
{
$container = $this->createContainer();
$container->setDefinition('client', $client = $this->createClient());
$container->setDefinition('sub', $this->createSubscriber('my_sub'));
$pass = new SubscriberPass();
$pass->process($container);
$this->assertNotNull($callback = $client->getConfigurator());
$this->assertEquals('configure', $callback[1]);
$configurator = $container->findDefinition($callback[0]);
$this->assertEquals([new Reference('sub')], $configurator->getArgument(0));
}
public function testSpecificSubscribersAddedToClient()
{
$client = $this->createClient($expected = ['foo', 'bar']);
$container = $this->createContainer();
$container->setDefinition('client', $client);
foreach (['foo', 'bar', 'qux'] as $alias) {
$container->setDefinition($alias, $this->createSubscriber($alias));
}
$pass = new SubscriberPass();
$pass->process($container);
$references = $container
->findDefinition($client->getConfigurator()[0])
->getArgument(0)
;
$subscribers = array_map(function ($reference) {
return (string) $reference;
}, $references);
$this->assertEquals(['foo', 'bar'], $subscribers, 'Only the specified subscribers must be added.');
}
public function testPreviousConfiguratorWrapped()
{
$client = $this->createClient();
$client->setConfigurator($parent = [new Reference('foo'), 'configure']);
$container = $this->createContainer();
$container->setDefinition('client', $client);
$container->setDefinition('sub', $this->createSubscriber('my_sub'));
$pass = new SubscriberPass();
$pass->process($container);
$callback = $client->getConfigurator();
$this->assertNotSame($parent, $callback, 'Subscriber pass should have replaced the configurator.');
$configurator = $container->findDefinition($callback[0]);
$this->assertCount(2, $configurator->getArguments(), 'The parent configurator should have been passed as the 2nd argument.');
$this->assertSame($parent, $configurator->getArgument(1));
}
private function createSubscriber($alias)
{
$subscriber = new Definition();
$subscriber->addTag(SubscriberPass::SUBSCRIBER_TAG, ['alias' => $alias]);
return $subscriber;
}
private function createClient(array $subscribers = null)
{
$client = new Definition();
$client->addTag(
SubscriberPass::CLIENT_TAG,
$subscribers ? ['subscribers' => implode(', ', $subscribers)] : []
);
return $client;
}
private function createContainer()
{
$container = new ContainerBuilder();
$container->setDefinition(SubscriberPass::FACTORY_SERVICE_ID, new Definition());
return $container;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Tests\DependencyInjection\Configurator;
use Csa\Bundle\GuzzleBundle\DependencyInjection\Configurator\ClientConfigurator;
use GuzzleHttp\ClientInterface;
class ClientConfiguratorTest extends \PHPUnit_Framework_TestCase
{
public function testSubscribersAttachedToEmitter()
{
$subscriber = $this->getMockSubscriber();
$emitter = $this->getMockEmitter();
$emitter
->expects($this->once())
->method('attach')
->with($this->identicalTo($subscriber))
;
$client = $this->getMockClient();
$client->method('getEmitter')->willReturn($emitter);
$configurator = new ClientConfigurator([$subscriber]);
$configurator->configure($client);
}
public function testParentConfiguratorCalled()
{
$parentCalled = false;
$parent = function (ClientInterface $client) use (&$parentCalled) {
$parentCalled = true;
};
$configurator = new ClientConfigurator([], $parent);
$configurator->configure($this->getMockClient());
$this->assertTrue($parentCalled, 'Parent configuration must be called');
}
private function getMockClient()
{
return $this->getMock('GuzzleHttp\ClientInterface');
}
private function getMockEmitter()
{
return $this->getMock('GuzzleHttp\Event\EmitterInterface');
}
private function getMockSubscriber()
{
return $this->getMock('GuzzleHttp\Event\SubscriberInterface');
}
}

View File

@@ -0,0 +1,304 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Tests\DependencyInjection;
use Csa\Bundle\GuzzleBundle\DependencyInjection\CompilerPass\SubscriberPass;
use Csa\Bundle\GuzzleBundle\DependencyInjection\CsaGuzzleExtension;
use GuzzleHttp\Subscriber\Log\Formatter;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\Yaml\Parser;
class CsaGuzzleExtensionTest extends \PHPUnit_Framework_TestCase
{
public function testClientCreated()
{
$yaml = <<<'YAML'
profiler:
enabled: false
clients:
foo:
config: { base_url: example.com }
YAML;
$container = $this->createContainer($yaml);
$this->assertTrue($container->hasDefinition('csa_guzzle.client.foo'), 'Client must be created.');
$client = $container->getDefinition('csa_guzzle.client.foo');
$this->assertEquals(
[SubscriberPass::CLIENT_TAG => [[]]],
$client->getTags(),
'Clients must be tagged.'
);
$this->assertEquals(
['base_url' => 'example.com'],
$client->getArgument(0),
'Config must be passed to client constructor.'
);
}
public function testClientAliasing()
{
$yaml = <<<'YAML'
profiler:
enabled: false
clients:
foo:
alias: bar
YAML;
$container = $this->createContainer($yaml);
$this->assertTrue($container->hasDefinition('csa_guzzle.client.foo'), 'Client must be created.');
$this->assertSame($container->findDefinition('bar'), $container->getDefinition('csa_guzzle.client.foo'));
}
public function testClientClassOverride()
{
$yaml = <<<YAML
clients:
foo:
class: AppBundle\Client
YAML;
$container = $this->createContainer($yaml);
$client = $container->getDefinition('csa_guzzle.client.foo');
$this->assertEquals('AppBundle\Client', $client->getClass());
}
/**
* @dataProvider clientConfigInstance
* @covers \CsaGuzzleExtension::buildGuzzleConfig
*/
public function testClientConfigInstanceOverride($instanceKey, $serviceId)
{
$yaml = <<<YAML
clients:
foo:
config:
{$instanceKey}: {$serviceId}
YAML;
$container = $this->createContainer($yaml);
$config = $container->getDefinition('csa_guzzle.client.foo')->getArgument(0);
$this->assertInstanceOf(
'Symfony\Component\DependencyInjection\Reference',
$config[$instanceKey]
);
$this->assertSame(
$serviceId,
(string) $config[$instanceKey]
);
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage Config for "csa_guzzle.client.bar" should be an array, but got string
*/
public function testInvalidClientConfig()
{
$yaml = <<<'YAML'
clients:
foo:
config: ~ # legacy mode
bar:
config: invalid # exception
YAML;
$this->createContainer($yaml);
}
public function testClientWithDescription()
{
$yaml = <<<'YAML'
clients:
foo:
config: { base_url: example.com }
description: %s
YAML;
$container = $this->createContainer(sprintf($yaml, realpath(__DIR__.'/../Fixtures/github.description.json')));
$this->assertTrue($container->hasDefinition('csa_guzzle.service.foo'));
$this->assertSame('csa_guzzle.client.foo', (string) $container->getDefinition('csa_guzzle.service.foo')->getArgument(0));
$this->assertSame('service("csa_guzzle.description_factory").getDescription("foo")', (string) $container->getDefinition('csa_guzzle.service.foo')->getArgument(1));
}
public function testSubscribersAddedToClient()
{
$yaml = <<<'YAML'
logger: true
profiler: true
clients:
foo:
subscribers:
stopwatch: false
debug: true
logger: true
YAML;
$container = $this->createContainer($yaml);
$this->assertTrue($container->hasDefinition('csa_guzzle.client.foo'), 'Client must be created.');
$client = $container->getDefinition('csa_guzzle.client.foo');
$this->assertEquals(
[SubscriberPass::CLIENT_TAG => [['subscribers' => 'debug,logger']]],
$client->getTags(),
'Only explicitly disabled subscribers shouldn\'t be added.'
);
}
public function testCustomSubscribersAddedToClient()
{
$yaml = <<<'YAML'
logger: true
profiler: true
clients:
foo:
subscribers:
stopwatch: false
debug: true
logger: true
foo: true
YAML;
$container = $this->createContainer($yaml);
$definition = new Definition();
$definition->addTag('csa_guzzle.subscriber', ['alias' => 'foo']);
$container->setDefinition('my.service.foo', $definition);
$this->assertTrue($container->hasDefinition('csa_guzzle.client.foo'), 'Client must be created.');
$client = $container->getDefinition('csa_guzzle.client.foo');
$this->assertEquals(
[SubscriberPass::CLIENT_TAG => [['subscribers' => 'debug,logger,foo']]],
$client->getTags(),
'Only explicitly disabled subscribers shouldn\'t be added.'
);
}
public function testLoggerConfiguration()
{
$yaml = <<<'YAML'
logger:
enabled: true
service: monolog.logger
format: %s
YAML;
$formats = ['clf' => Formatter::CLF, 'debug' => Formatter::DEBUG, 'short' => Formatter::SHORT];
foreach ($formats as $alias => $format) {
$container = $this->createContainer(sprintf($yaml, $alias));
$this->assertSame($format, $container->getDefinition('csa_guzzle.subscriber.logger')->getArgument(1));
$this->assertSame('monolog.logger', (string) $container->getDefinition('csa_guzzle.subscriber.logger')->getArgument(0));
}
$yaml = <<<'YAML'
logger: false
YAML;
$container = $this->createContainer($yaml);
$this->assertFalse($container->hasDefinition('csa_guzzle.subscriber.logger'));
}
public function testCacheConfiguration()
{
$yaml = <<<'YAML'
cache: false
YAML;
$container = $this->createContainer($yaml);
$this->assertFalse($container->hasDefinition('csa_guzzle.subscriber.cache'));
$yaml = <<<'YAML'
cache:
enabled: true
adapter: my.adapter.id
YAML;
$container = $this->createContainer($yaml);
$container->setDefinition('my.adapter.id', new Definition());
$alias = $container->getAlias('csa_guzzle.default_cache_adapter');
$this->assertSame('my.adapter.id', (string) $alias);
}
public function testLegacyCacheConfiguration()
{
$yaml = <<<'YAML'
cache:
enabled: true
service: my.service.id
YAML;
$container = $this->createContainer($yaml);
$container->setDefinition('my.service.id', new Definition(null, [null, null]));
$alias = $container->getAlias('csa_guzzle.default_cache_adapter');
$this->assertSame('my.service.id', (string) $container->getDefinition((string) $alias)->getArgument(0));
}
public function testLegacyFactoryConfiguration()
{
$yaml = <<<YAML
factory_class: GuzzleHttp\Client
YAML;
$container = $this->createContainer($yaml);
$factory = $container->getDefinition('csa_guzzle.client_factory');
$this->assertSame('GuzzleHttp\Client', $factory->getArgument(0));
}
/**
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
* @expectedExceptionMessage Invalid configuration for path "csa_guzzle.cache.adapter.type": Invalid cache adapter
*/
public function testLegacyWrongCacheAdapterTypeThrowsException()
{
$yaml = <<<'YAML'
cache:
enabled: true
adapter:
type: foo
YAML;
$this->createContainer($yaml);
}
private function createContainer($yaml)
{
$parser = new Parser();
$container = new ContainerBuilder();
$loader = new CsaGuzzleExtension();
$loader->load([$parser->parse($yaml)], $container);
return $container;
}
public function clientConfigInstance()
{
return [
['message_factory', 'my.message.factory.id'],
['fsm', 'my.fsm.id'],
['adapter', 'my.adapter.id'],
['handler', 'my.handler.id'],
];
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Tests\Factory;
use Csa\Bundle\GuzzleBundle\Factory\ClientFactory;
/**
* ClientTest.
*
* @group legacy
*/
class ClientFactoryTest extends \PHPUnit_Framework_TestCase
{
public function testCreateClient()
{
$factory = new ClientFactory('GuzzleHttp\Client');
$this->assertInstanceOf('GuzzleHttp\Client', $factory->create());
}
}

View File

@@ -0,0 +1,216 @@
{
"name": "Github API",
"description": "basic use of the github API",
"operations": {
"getUser": {
"httpMethod": "GET",
"uri": "/users/{username}",
"responseModel": "jsonResponse",
"parameters": {
"username": {
"required": true,
"type": "string",
"location": "uri"
}
}
},
"getUsers": {
"httpMethod": "GET",
"uri": "\/users",
"responseModel": "jsonResponse",
"parameters": {
"since": {
"type": "integer",
"location": "query"
}
}
},
"getUserRepositories": {
"httpMethod": "GET",
"uri": "/users/{username}/repos",
"responseModel": "jsonResponse",
"parameters": {
"username": {
"required": true,
"type": "string",
"location": "uri"
},
"type": {
"type": "string",
"location": "query"
},
"sort": {
"type": "string",
"location": "query"
},
"direction": {
"type": "string",
"location": "query"
}
}
},
"getOrgRepositories": {
"httpMethod": "GET",
"uri": "/orgs/{org}/repos",
"responseModel": "jsonResponse",
"parameters": {
"org": {
"required": true,
"type": "string",
"location": "uri"
},
"type": {
"type": "string",
"location": "query"
}
}
},
"getRepositories": {
"httpMethod": "GET",
"uri": "\/repositories",
"responseModel": "jsonResponse",
"parameters": {
"since": {
"type": "integer",
"location": "query"
}
}
},
"getRepository": {
"httpMethod": "GET",
"uri": "/repos/{owner}/{repo}",
"responseModel": "jsonResponse",
"parameters": {
"owner": {
"required": true,
"type": "string",
"location": "uri"
},
"repo": {
"required": true,
"type": "string",
"location": "uri"
}
}
},
"getRepositoryContributors": {
"httpMethod": "GET",
"uri": "/repos/{owner}/{repo}/contributors",
"responseModel": "jsonResponse",
"parameters": {
"owner": {
"required": true,
"type": "string",
"location": "uri"
},
"repo": {
"required": true,
"type": "string",
"location": "uri"
},
"anon": {
"type": "boolean",
"location": "query"
}
}
},
"getRepositoryLanguages": {
"httpMethod": "GET",
"uri": "/repos/{owner}/{repo}/languages",
"responseModel": "jsonResponse",
"parameters": {
"owner": {
"required": true,
"type": "string",
"location": "uri"
},
"repo": {
"required": true,
"type": "string",
"location": "uri"
}
}
},
"getRepositoryTeams": {
"httpMethod": "GET",
"uri": "/repos/{owner}/{repo}/teams",
"responseModel": "jsonResponse",
"parameters": {
"owner": {
"required": true,
"type": "string",
"location": "uri"
},
"repo": {
"required": true,
"type": "string",
"location": "uri"
}
}
},
"getRepositoryTags": {
"httpMethod": "GET",
"uri": "/repos/{owner}/{repo}/tags",
"responseModel": "jsonResponse",
"parameters": {
"owner": {
"required": true,
"type": "string",
"location": "uri"
},
"repo": {
"required": true,
"type": "string",
"location": "uri"
}
}
},
"getRepositoryBranches": {
"httpMethod": "GET",
"uri": "/repos/{owner}/{repo}/branches",
"responseModel": "jsonResponse",
"parameters": {
"owner": {
"required": true,
"type": "string",
"location": "uri"
},
"repo": {
"required": true,
"type": "string",
"location": "uri"
}
}
},
"getRepositoryBranch": {
"httpMethod": "GET",
"uri": "/repos/{owner}/{repo}/branches/{branch}",
"responseModel": "jsonResponse",
"parameters": {
"owner": {
"required": true,
"type": "string",
"location": "uri"
},
"repo": {
"required": true,
"type": "string",
"location": "uri"
},
"branch": {
"required": true,
"type": "string",
"location": "uri"
}
}
}
},
"models": {
"jsonResponse": {
"type": "object",
"additionalProperties": {
"location": "json"
}
}
}
}

View File

@@ -0,0 +1,14 @@
HTTP/1.1 302 Found
Cache-Control: private
Content-Length: 258
Content-Type: text/html; charset=UTF-8
Date: Tue, 08 Sep 2015 09:53:23 GMT
Location: http://www.google.fr/?gfe_rd=cr&ei=E7DuVbCGOYju8wfz-6OgDg
Server: GFE/2.0
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.fr/?gfe_rd=cr&amp;ei=E7DuVbCGOYju8wfz-6OgDg">here</A>.
</BODY></HTML>

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Tests\GuzzleHttp\Cache;
use Csa\Bundle\GuzzleBundle\GuzzleHttp\Cache\DoctrineAdapter;
use GuzzleHttp\Message\MessageParser;
use GuzzleHttp\Message\Request;
class DoctrineAdapterTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$cache = $this->getMock('Doctrine\Common\Cache\Cache');
new DoctrineAdapter($cache, 0);
}
public function testFetch()
{
$httpResponse = file_get_contents(__DIR__.'/../../Fixtures/response.txt');
$parser = new MessageParser();
$cache = $this->getMock('Doctrine\Common\Cache\Cache');
$cache
->expects($this->at(0))
->method('contains')
->willReturn(false)
;
$cache
->expects($this->at(1))
->method('contains')
->willReturn(true)
;
$cache
->expects($this->at(2))
->method('fetch')
->willReturn($httpResponse)
;
$adapter = new DoctrineAdapter($cache, 0);
$request = $this->getRequestMock();
$this->assertNull($adapter->fetch($request));
$response = $adapter->fetch($request);
$data = $parser->parseResponse($httpResponse);
$this->assertInstanceOf('GuzzleHttp\Message\ResponseInterface', $response);
$this->assertEquals($data['code'], $response->getStatusCode());
$this->assertSame($data['body'], (string) $response->getBody());
foreach ($response->getHeaders() as $header => $value) {
$this->assertSame($value[0], $data['headers'][$header]);
}
}
public function testSave()
{
$cache = $this->getMock('Doctrine\Common\Cache\Cache');
$cache
->expects($this->at(0))
->method('save')
->with(
$this->isType('string'),
$this->isType('string'),
10
);
$adapter = new DoctrineAdapter($cache, 10);
$this->assertNull($adapter->save($this->getRequestMock(), $this->getMock('GuzzleHttp\Message\ResponseInterface')));
}
private function getRequestMock()
{
return new Request('GET', 'http://google.com/', ['Accept' => 'text/html'], $this->getMock('GuzzleHttp\Stream\StreamInterface'));
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Tests\GuzzleHttp\Subscriber;
use Csa\Bundle\GuzzleBundle\GuzzleHttp\Subscriber\CacheSubscriber;
use GuzzleHttp\Client;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Subscriber\Mock;
class CacheSubscriberTest extends \PHPUnit_Framework_TestCase
{
public function testFetch()
{
$response = new Response(204);
$mocks = array_fill(0, 2, $response);
$mockSubscriber = new Mock($mocks);
$adapter = $this->getMock('Csa\Bundle\GuzzleBundle\GuzzleHttp\Cache\StorageAdapterInterface');
$adapter
->expects($this->at(0))
->method('fetch')
->with($this->isInstanceOf('GuzzleHttp\Message\Request'))
->willReturn(false)
;
$adapter
->expects($this->at(1))
->method('save')
->with(
$this->isInstanceOf('GuzzleHttp\Message\RequestInterface'),
$this->equalTo($response)
)
;
$adapter
->expects($this->at(2))
->method('fetch')
->with($this->isInstanceOf('GuzzleHttp\Message\RequestInterface'))
->willReturn($response)
;
$cacheSubscriber = new CacheSubscriber($adapter);
$client = new Client();
$client->getEmitter()->attach($mockSubscriber);
$client->getEmitter()->attach($cacheSubscriber);
$client->get('http://foo.bar');
$client->get('http://foo.bar');
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Tests\HttpFoundation;
use Csa\Bundle\GuzzleBundle\HttpFoundation\StreamResponse;
use GuzzleHttp\Message\Response;
use GuzzleHttp\Stream\Stream;
class StreamResponseTest extends \PHPUnit_Framework_TestCase
{
public function testNormalOutput()
{
$this->expectOutputString('this should not be streamed');
$mock = new Response(200, [], Stream::factory('this should not be streamed'));
$response = new StreamResponse($mock);
$response->send();
}
public function testChunkedOutput()
{
$this->expectOutputString("a\r\nthis shoul\r\na\r\nd not be s\r\n7\r\ntreamed\r\n0\r\n\r\n");
$mock = new Response(200, ['Transfer-Encoding' => 'chunked'], Stream::factory('this should not be streamed'));
$response = new StreamResponse($mock, 10);
$response->send();
}
}

View File

@@ -0,0 +1,117 @@
<?php
/*
* This file is part of the CsaGuzzleBundle package
*
* (c) Charles Sarrazin <charles@sarraz.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code
*/
namespace Csa\Bundle\GuzzleBundle\Twig\Extension;
/**
* Csa Guzzle Collector.
*
* @author Charles Sarrazin <charles@sarraz.in>
*/
class GuzzleExtension extends \Twig_Extension
{
public function getFilters()
{
return [
new \Twig_SimpleFilter('csa_guzzle_pretty_print', [$this, 'prettyPrint']),
new \Twig_SimpleFilter('csa_guzzle_status_code_class', [$this, 'statusCodeClass']),
new \Twig_SimpleFilter('csa_guzzle_format_duration', [$this, 'formatDuration']),
new \Twig_SimpleFilter('csa_guzzle_short_uri', [$this, 'shortenUri']),
];
}
public function getFunctions()
{
return [
new \Twig_SimpleFunction('csa_guzzle_detect_lang', [$this, 'detectLang']),
];
}
public function detectLang($body)
{
switch (true) {
case 0 === strpos($body, '<?xml'):
return 'xml';
case 0 === strpos($body, '{'):
case 0 === strpos($body, '['):
return 'json';
default:
return 'markup';
}
}
public function prettyPrint($code, $lang)
{
switch ($lang) {
case 'json':
return json_encode(json_decode($code), JSON_PRETTY_PRINT);
case 'xml':
$xml = new \DomDocument('1.0');
$xml->preserveWhiteSpace = false;
$xml->formatOutput = true;
$xml->loadXml($code);
return $xml->saveXml();
default:
return $code;
}
}
public function statusCodeClass($statusCode)
{
switch (true) {
case $statusCode >= 500:
return 'server-error';
case $statusCode >= 400:
return 'client-error';
case $statusCode >= 300:
return 'redirection';
case $statusCode >= 200:
return 'success';
case $statusCode >= 100:
return 'informational';
default:
return 'unknown';
}
}
public function formatDuration($seconds)
{
$formats = ['%.2f s', '%d ms', '%d µs'];
while ($format = array_shift($formats)) {
if ($seconds > 1) {
break;
}
$seconds *= 1000;
}
return sprintf($format, $seconds);
}
public function shortenUri($uri)
{
$parts = parse_url($uri);
return sprintf(
'%s://%s%s',
isset($parts['scheme']) ? $parts['scheme'] : 'http',
$parts['host'],
isset($parts['port']) ? (':'.$parts['port']) : ''
);
}
public function getName()
{
return 'csa_guzzle';
}
}