I'm trying to profile the requests made to an API server from a PHP client using Guzzle (v 6).
In the Guzzle 5.3 there is this complete and before event handling.
class GuzzleProfiler implements SubscriberInterface
{
public function getEvents()
{
return [
'before' => ['onBefore'],
'complete' => ['onComplete']
];
}
public function onBefore(BeforeEvent $event, $name)
{
start_profiling();
}
public function onComplete(CompleteEvent $event, $name)
{
end_profiling();
}
}
But how do I do this in v6?
Just found it using Middleware. Here's the code.
class Profiler {
/**
* #return callable
*/
public static function profile() {
return function(callable $handler) {
return function(\Psr\Http\Message\RequestInterface $request, array $options) use ($handler) {
start_profiling();
return $handler($request, $options)->then(function(\Psr\Http\Message\ResponseInterface $response) use ($token) {
end_profiling();
return $response;
});
};
};
}
}
And then attach the profiler like this.
$stack = \GuzzleHttp\HandlerStack::create();
$stack->push(Profiler::profile());
$client = new \GuzzleHttp\Client([
'handler' => $stack
]);
Related
I am looking for advice in passing parameters in my router's routes.
This is my router...
<?php namespace Framework;
class Router
{
private array $routes = [];
private Request $request;
public function __construct(Request $request, Response $response)
{
$this->request = $request;
$this->response = $response;
}
public function get(string $path, string|callable|array $callback): void
{
$this->routes['GET'][$path] = $callback;
}
public function post(string $path, string|callable|array $callback): void
{
$this->routes['POST'][$path] = $callback;
}
public function dispatch(): mixed
{
$method = $this->request->method();
$path = $this->request->path();
$callback = $this->routes[$method][$path];
if(is_null($callback))
{
throw new Exception\NotFoundException;
}
if(is_string($callback))
{
return new View($callback);
}
if(is_array($callback))
{
$callback[0] = new $callback[0];
}
return call_user_func($callback);
}
}
And my routes look like this...
return function (App $app)
{
$app->get('/', [Controllers\HomeController::class, 'index']);
$app->get('/about', [Controllers\HomeController::class, 'about']);
$app->get('/contact', function() {
var_dump(App::load()->request()->body());
});
$app->get('/blog', [Controllers\BlogController::class, 'index']);
$app->get('/blog/{id}', [Controllers\BlogController::class, 'index']);
};
I want to be able to pass a parameter (for example on the last blog route). And I am sort of at a loss about how to go about it.
I can't find an explanation of why a return response() inside a catch is not stopping the execution, i guess im missing something.
In this example the request have an error so the try-catch on the service receive ValidationException. This goes to ValidationErrorResponder and here is where the execution should finish and return a json with the errors. But it continues and return the json error response through $this->updateUserResponder->respond()
I have a defined route which execute the __invoke() method on UpdateUserAction
class UpdateUserAction
{
protected $updateUserService;
protected $updateUserResponder;
public function __construct(UpdateUserService $updateUserService, UpdateUserResponder $updateUserResponder)
{
$this->updateUserService = $updateUserService;
$this->updateUserResponder = $updateUserResponder;
}
public function __invoke(Request $request, $userId)
{
$serviceData = [
'id' => $userId,
'commandPayload' => $request->only('name')
];
return $this->updateUserResponder->respond($this->updateUserService->execute($serviceData));
}
}
class UpdateUserService extends BaseService
{
public function execute(array $data = [])
{
try {
$this->bus->addHandler(UpdateUserCommand::class, UpdateUserHandler::class);
return $this->bus->dispatch(UpdateUserCommand::class, $data, [UpdateUserValidator::class]);
} catch (ValidationException $e) {
return $this->validationErrorResponder->respond($e);
}
}
}
class UpdateUserValidator implements Middleware
{
protected $rules = [
'id' => 'uuid',
'commandPayload.name' => 'max:256'
];
protected $messages = [];
public function execute($command, callable $next)
{
$validator = Validator::make((array) $command, $this->rules, $this->messages);
if ($validator->fails()) {
throw new ValidationException($validator);
}
return $next($command);
}
}
This shoudl return the final response wiht the errors in a JSON but
class ValidationErrorResponder
{
public function respond($validator)
{
$messages = $validator->getValidator()->getMessageBag()->messages();
return response()->json(['errors' => $messages], 422);
}
}
Maybe the error it's another and the catch does not working because only are catching ValidationException.
So try catching all exception to see what happens:
class UpdateUserService extends BaseService
{
public function execute(array $data = [])
{
try {
$this->bus->addHandler(UpdateUserCommand::class, UpdateUserHandler::class);
return $this->bus->dispatch(UpdateUserCommand::class, $data, [UpdateUserValidator::class]);
} catch (\Exception $e) {
return $this->validationErrorResponder->respond($e);
}
}
}
I have a class social which has:
protected $id;
public function __construct($request, $id)
{
Log::info('Processing...', ['request' => $request, 'id' => $id]);
try {
$client = new Client();
$url = sprintf($request);
$response = $client->get($url);
$json = json_decode((string) $response->getBody(), true);
return $json;
} catch (ClientException $exception) {
$responseBody = $exception->getResponse()->getBody(true);
Log::error($responseBody, ['entity_id' => $id]);
}
}
public function wikipedia($wikipedia_url, $id)
{
dd($json);
try {
$wikipedia_array = $json['parse']['text'];
$wikipedia_array = array_slice($wikipedia_array, 0, 9);
$wikipedia_array = implode($wikipedia_array, ',');
Log::info('Processed Wikipedia for', ['entity_id' => $id]);
return $wikipedia_array;
} catch (Exception $e) {
Log::error('Wikipedia:', ['message' => $e->getMessage(), 'entity_id' => $id]);
}
}
In another function I am calling a facade like this:
$id = $entity->id;
$wikipedia_id = $entity->wikipedia;
if (!empty($wikipedia_id)) {
$wikipedia_url = 'http://en.wikipedia.org/w/api.php?action=parse&prop=text§ion=0&disablelimitreport=1&format=json&page='.$wikipedia_id;
$wikipedia_html = Social::wikipedia($wikipedia_url, $id);
Log::info('Wikipedia ok for', ['entity_id' => $id]);
}
However I get this:
Type error: Too few arguments to function App\Helpers\Social::__construct(), 0 passed in /home/vagrant/liveandnow/app/Providers/SocialServiceProvider.php on line 35 and exactly 2 expected
Can anyone explain to me how to call a method, pass parameters to it but also pass them along to construct?
Here's my facade:
<?php
namespace App\Facade;
use Illuminate\Support\Facades\Facade;
class Social extends Facade
{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return 'social';
}
}
and service provider:
public function register()
{
$this->app->bind('social', function ($app) {
return new Social;
});
}
the error lies in the service provider.
You define a constructor with 2 parameters,
public function __construct($request, $id)
but in your service provider you call it like this:
public function register()
{
$this->app->bind('social', function ($app) {
return new Social;
});
}
You need to add both arguments when instantiating the Social class, for example like
return new Social("http://xyz.de", 1);
Hope this helps.
I would like to set a cookie in a listener, if the query parameter "source" is set. I tried the following but the cookie does not exist.
How can I set the cookie correct?
class DispatchListener extends AbstractListenerAggregate {
public function attach(EventManagerInterface $eventManager) {
$this->listeners[] = $eventManager->getSharedManager()->attach(
'Zend\Stdlib\DispatchableInterface',
MvcEvent::EVENT_DISPATCH,
array($this, 'setCookie'),
-80
);
}
/**
* #var \Zend\Stdlib\RequestInterface
*/
protected $request;
/**
* #param \Zend\Stdlib\RequestInterface $request
*/
public function __construct(RequestInterface $request) {
$this->request = $request;
}
public function setCookie(EventInterface $event) {
if ($source = $this->request->getQuery('source')) {
$this->request->setCookies([
'source' => $source
]);
}
}
}
--------------------------------- UPDATE -----------------------------------
class Module implements ConfigProviderInterface, BootstrapListenerInterface {
public function onBootstrap(EventInterface $event) {
$target = $event->getTarget();
$serviceManager = $target->getServiceManager();
$eventManager = $target->getEventManager();
$eventManager->attach($serviceManager->get('Application\Listener\Dispatch'));
}
}
Seems like there are a few issues with your code, which we can rule out first.
You haven't provided code for how you are attaching your listener
Not sure why you are setting the cookie on the request, do you want to do this on the response?
Attaching the event listener:
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$app = $e->getApplication();
$em = $app->getEventManager();
// Attach event to attach listener after routing when query will be populated
$em->attach(MvcEvent::EVENT_ROUTE, function($e) use ($eventManager) {
$request = $e->getRequest();
// attach our listener
$eventManager->attach(new DispatchListener($request));
});
}
Updated setCookie method:
public function setCookie(EventInterface $event) {
if ($source = $this->request->getQuery('source')) {
$this->request->setCookies([
'source' => $source
]);
$request = $this->request;
$cookieData = $request->getCookie('someCookie', 'default');
var_dump($cookieData);
}
}
The var_dump prints the following:
object(Zend\Http\Header\Cookie)[274]
protected 'encodeValue' => boolean true
private 'storage' (ArrayObject) =>
array (size=1)
'source' => string 'test' (length=4)
Do you want a cookie in your request object or do you want to create a cookie for the response. You probably want to create a cookie and set it on the response object.
Check for example this answer.
public function setCookie(EventInterface $event) {
if ($source = $this->request->getQuery('source')) {
$cookie = new \Zend\Http\Header\SetCookie('source', $source);
$headers = $this->getResponse()->getHeaders();
$headers->addHeader($cookie);
}
}
Is there any way I can send multiple requests with guzzle using a middleware and according to certain parameters?
To be more specific I need:
class MyMiddleware
{
/**
* #param callable $handler
* #return \Closure
*/
public function __invoke(callable $handler)
{
return function (RequestInterface $request, array $options) use ($handler) {
if(!isset($options['token']){
//request the token from a url
}
//after the token is aquired proceed with the original request
return $handler($request, $options);
};
}
}
Is this similar to what you are inquiring about? I have done similar to it.
$handler = GuzzleHttp\HandlerStack::create();
$client = new GuzzleHttp\Client([
'handler' = $handler,
// other options
])
$handler->push(GuzzleHttp\Middleware::mapRequest(
function (Psr\Http\Message\RequestInterface $request) use ($client) {
if ('POST' !== $request->getMethod()) {
return $request;
}
if($RequestIsOkAsIs) {
return $request;
}
$response = $client->get($someUrl, $requestOptions);
// Play with the response to modify the body
$newBody = 'results of stuff';
return new GuzzleHttp\Psr7\Request(
$request->getMethod(),
$request->getUri(),
$request->getHeaders(),
$newBody,
$request->getProtocolVersion()
);
}
);