I have created a wrapper for the Slim request in my application to have the ability to create some custom methods on the Request object.
class Request extends SlimRequest
{
/**
* Get authorization token.
*
* #return null|string
*/
public function getAuthToken(): ?string
{
return $this->getHeaderLine('FOOBAR-TOKEN');
}
/**
* Retrieves a route parameter.
*
* #param string $name
* #return string|null
*/
public function getRouteParam(string $name): ?string
{
return $this->getRoute()->getArgument($name);
}
/**
* Retrieves the route instance.
*
* #return Route
*/
public function getRoute(): Route
{
return $this->getAttribute('route');
}
}
My problem comes when trying to create unit test for this class. The way I have been testing requests is by using Slims build in environment mocks. The first function I added a header to the request which can be seen below, but I can't figure out how to add a Route object to the request
$request = Request::createFromEnvironment(Environment::mock());
$request = $request->withHeader('FOOBAR-TOKEN', 'superSafeExampleToken');
I tried creating the request with a request options but $this->getAttribute('route'); returns null
$requestOptions = [
'REQUEST_METHOD' => 'POST,
'REQUEST_URI' => '/foo/bar',
'QUERY_STRING' => http_build_query($requestParameters),
];
$environment = Environment::mock($requestOptions);
Okay so the solution was the following
public function testGetRouteParam()
{
$route = $route = new Route('GET', '/foo/{bar}', []);
$route->setArguments(['bar' => 1]);
$request = Request::createFromEnvironment(Environment::mock());
$request = $request->withAttribute('route', $route);
$this->assertEquals(1, $request->getRouteParam('bar'));
$this->assertNull($request->getRouteParam('baz'));
}
Related
Currently I am working on a project where we are trying to create a RESTful API. This API uses some default classes, for example the ResourceController, for basic behaviour that can be overwritten when needed.
Lets say we have an API resource route:
Route::apiResource('posts', 'ResourceController');
This route will make use of the ResourceController:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Repositories\ResourceRepository;
class ResourceController extends Controller
{
/**
* The resource class.
*
* #var string
*/
private $resourceClass = '\\App\\Http\\Resources\\ResourceResource';
/**
* The resource model class.
*
* #var string
*/
private $resourceModelClass;
/**
* The repository.
*
* #var \App\Repositories\ResourceRepository
*/
private $repository;
/**
* ResourceController constructor.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
public function __construct(Request $request)
{
$this->resourceModelClass = $this->getResourceModelClass($request);
$this->repository = new ResourceRepository($this->resourceModelClass);
$exploded = explode('\\', $this->resourceModelClass);
$resourceModelClassName = array_last($exploded);
if (!empty($resourceModelClassName)) {
$resourceClass = '\\App\\Http\\Resources\\' . $resourceModelClassName . 'Resource';
if (class_exists($resourceClass)) {
$this->resourceClass = $resourceClass;
}
}
}
...
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$this->validate($request, $this->getResourceModelRules());
$resource = $this->repository->create($request->all());
$resource = new $this->resourceClass($resource);
return response()->json($resource);
}
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
$resource = $this->repository->show($id);
$resource = new $this->resourceClass($resource);
return response()->json($resource);
}
...
/**
* Get the model class of the specified resource.
*
* #param \Illuminate\Http\Request $request
* #return string
*/
private function getResourceModelClass(Request $request)
{
if (is_null($request->route())) return '';
$uri = $request->route()->uri;
$exploded = explode('/', $uri);
$class = str_singular($exploded[1]);
return '\\App\\Models\\' . ucfirst($class);
}
/**
* Get the model rules of the specified resource.
*
* #param \Illuminate\Http\Request $request
* #return string
*/
private function getResourceModelRules()
{
$rules = [];
if (method_exists($this->resourceModelClass, 'rules')) {
$rules = $this->resourceModelClass::rules();
}
return $rules;
}
}
As you can maybe tell we are not making use of model route binding and we make use of a repository to do our logic.
As you can also see we make use of some dirty logic, getResourceModelClass(), to determine the model class needed to perform logic on/with. This method is not really flexible and puts limits on the directory structure of the application (very nasty).
A solution could be adding some information about the model class when registrating the route. This could look like:
Route::apiResource('posts', 'ResourceController', [
'modelClass' => Post::class
]);
However it looks like this is not possible.
Does anybody have any suggestions on how to make this work or how to make our logic more clean and flexible. Flexibility and easy of use are important factors.
The nicest way would be to refactor the ResourceController into an abstract class and have a separate controller that extends it - for each resource.
I'm pretty sure that there is no way of passing some context information in routes file.
But you could bind different instances of repositories to your controller. This is generally a good practice, but relying on URL to resolve it is very hacky.
You'd have to put all the dependencies in the constructor:
public function __construct(string $modelPath, ResourceRepository $repo // ...)
{
$this->resourceModelClass = $this->modelPath;
$this->repository = $repo;
// ...
}
And do this in a service provider:
use App\Repositories\ResourceRepository;
use App\Http\Controllers\ResourceController;
// ... model imports
// ...
public function boot()
{
if (request()->path() === 'posts') {
$this->app->bind(ResourceRepository::class, function ($app) {
return new ResourceRepository(new Post);
});
$this->app->when(ResourceController::class)
->needs('$modelPath')
->give(Post::class);
} else if (request()->path() === 'somethingelse') {
// ...
}
}
This will give you more flexibility, but again, relying on pure URL paths is hacky.
I just showed an example for binding the model path and binding a Repo instance, but if you go down this road, you'll want to move all the instantiating out of the Controller constructor.
After a lot of searching and diving in the source code of Laravel I found out the getResourceAction method in the ResourceRegistrar handles the option passed to the route.
Further searching led me to this post where someone else already managed to extend this registrar en add some custom functionality.
My custom registrar looks like:
<?php
namespace App\Http\Routing;
use Illuminate\Routing\ResourceRegistrar as IlluResourceRegistrar;
class ResourceRegistrar extends IlluResourceRegistrar
{
/**
* Get the action array for a resource route.
*
* #param string $resource
* #param string $controller
* #param string $method
* #param array $options
* #return array
*/
protected function getResourceAction($resource, $controller, $method, $options)
{
$action = parent::getResourceAction($resource, $controller, $method, $options);
if (isset($options['model'])) {
$action['model'] = $options['model'];
}
return $action;
}
}
Do not forget to bind in the AppServiceProvider:
$registrar = new ResourceRegistrar($this->app['router']);
$this->app->bind('Illuminate\Routing\ResourceRegistrar', function () use ($registrar) {
return $registrar;
});
This custom registrar allows the following:
Route::apiResource('posts', 'ResourceController', [
'model' => Post::class
]);
And finally we are able to get our model class:
$resourceModelClass = $request->route()->getAction('model');
No hacky url parse logic anymore!
How change or add a header to the response in Zend Expressive 2 (with HtmlResponse) ?
class NotModifiedMiddleware implements ServerMiddlewareInterface
{
/**
* Process an incoming server request and return a response, optionally delegating
* to the next middleware component to create the response.
*
* #param ServerRequestInterface $request
* #param DelegateInterface $delegate
*
* #return ResponseInterface
*/
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
}
}
HtmlResponse, recieves as a third param an array of headers to use at initialization.
As an example:
return new HtmlResponse($data, 200, ['Content-Type' => 'application/x-yaml']);
It's easy.
You just need to let the delegate to process the request and get response back, for example:
public function process(ServerRequestInterface $request, DelegateInterface $delegate)
{
$response = $delegate->process($request);
$now = new \DateTime();
return $response->withHeader('Last-Modified', $now->format('c'));
}
I have a symfony2 application. It abstracts a bunch of external APIs, all of them implementing an ExternalApiInterface.
Each ExternalApiInterface has a lot of methods, e.g. fetchFoo and fetchBar.
Now, I want to write a service that measures the time of each method call of an instance of an ExternalApiInterface.
My current thinking is to implement a StopWatchExternalApiDecorator, that wraps each method call. Yet this approach leads, in my understanding, to code duplication.
I think I am going to use the StopWatch component for the time measurement, yet this feels odd:
class StopWatchExternalApiDecorator implements ExternalApiInterface {
public function __construct(ExternalApiInterface $api, Stopwatch $stopWatch)
{
$this->api = $api;
$this->stopWatch = $stopWatch;
}
public function fetchBar() {
$this->stopWatch->start('fetchBar');
$this->api->fetchBar()
$this->stopWatch->stop('fetchBar');
}
public function fetchFoo() {
$this->stopWatch->start('fetchFoo');
$this->api->fetchFoo()
$this->stopWatch->stop('fetchFoo');
}
}
It seems like I am hurting the DNRY (do not repeat yourself) approach. Am I using the right pattern for this kind of problem, or is there something else more fit? More fit in the sense of: One place to do all the measurement, and no code duplication.
I also dislike of having to touch the decorator in case there will be a new method in the interface. In my mind, that should be independent.
i am thinking of some apis i worked on that use one generic function for calls and a method parameter
heres some very basic pseudocode
public function call($method = 'fetchBar',$params=array()){
$this->stopWatch->start($method);
$this->{"$method"}($params);
$this->stopWatch->stop($method);
}
private function fetchBar(){
echo "yo";
}
maybe that helps
I went with the decorator approach, just on a different level.
In my architecture, api service was using an HttpClientInterface, and each request was handled in the end with a call to doRequest. So there, the decorator made most sense without code duplication:
<?php
namespace Kopernikus\BookingService\Component\Http\Client;
use Kopernikus\BookingService\Component\Performance\PerformanceEntry;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* ProfileClientDecorator
**/
class ProfileClientDecorator implements HttpClientInterface
{
/**
* #var Stopwatch
*/
private $stopwatch;
/**
* #var HttpClientInterface
*/
private $client;
/**
* #var LoggerInterface
*/
private $logger;
/**
* ProfileClientDecorator constructor.
* #param HttpClientInterface $client
* #param Stopwatch $stopwatch
* #param LoggerInterface $logger
*/
public function __construct(HttpClientInterface $client, Stopwatch $stopwatch, LoggerInterface $logger)
{
$this->client = $client;
$this->stopwatch = $stopwatch;
$this->logger = $logger;
}
/**
* #param RequestInterface $request
*
* #return ResponseInterface
*/
public function doRequest(RequestInterface $request)
{
$method = $request->getMethod();
$response = $this->doMeasuredRequest($request, $method);
$performance = $this->getPerformance($method);
$this->logPerformance($performance);
return $response;
}
/**
* #param RequestInterface $request
* #param string $method
*
* #return ResponseInterface
*/
protected function doMeasuredRequest(RequestInterface $request, $method)
{
$this->stopwatch->start($method);
$response = $this->client->doRequest($request);
$this->stopwatch->stop($method);
return $response;
}
/**
* #param $method
* #return PerformanceEntry
*/
protected function getPerformance($method)
{
$event = $this->stopwatch->getEvent($method);
$duration = $event->getDuration();
return new PerformanceEntry($duration, $method);
}
/**
* #param PerformanceEntry $performance
*/
protected function logPerformance(PerformanceEntry $performance)
{
$context = [
'performance' => [
'duration_in_ms' => $performance->getDurationInMs(),
'request_name' => $performance->getRequestName(),
],
];
$this->logger->info(
"The request {$performance->getRequestName()} took {$performance->getDurationInMs()} ms",
$context
);
}
}
And in my services.yml:
performance_client_decorator:
class: Kopernikus\Component\Http\Client\ProfileClientDecorator
decorates: http.guzzle_client
arguments:
- #performance_client_decorator.inner
- #stopwatch
- #logger
How can I create group routing in PHP with Closure? I'm creating my own REST API from scratch in PHP for practice and learning.
In bootstrap file i call App class:
$app = new App/App();
$app->import('routes.php');
I have routes.php file with:
$app->group('/api/v1', function() use ($app)
{
$app->group('/users', function() use ($app)
{
$app->get('/', 'User', 'index');
$app->post('/', 'User', 'post');
$app->get('/{id}', 'User', 'get');
$app->put('/{id}', 'User', 'put');
$app->delete('/{id}', 'User', 'delete');
});
});
It needs to create routes like this:
/api/v1/users/
/api/v1/users/
/api/v1/users/{id}
/api/v1/users/{id}
/api/v1/users/{id}
App class:
class App
{
public function group($link, Closure $closure)
{
$closure();
}
}
And it sets routes like this:
/
/
/{id}
/{id}
/{id}
What should I do to prefix urls ? How can I "foreach" these other $app->get(), $app->post() method callings ?
Figured it out! Added DI container to App class which handles Router, Route and RouteGroup classes. PHP SLIM framework was my inspiration - https://github.com/slimphp/Slim/tree/3.x/Slim
First I call group() method from App class with calls pushGroup() method from Router class. Then I invoke RouteGroup class with $group();. After that I cal popGroup() to return only last route group.
When adding groups url to Route, just run processGroups() method in Router class to add prefix links.
App class
/**
* Route groups
*
* #param string $link
* #param Closure $closure
* #return void
*/
public function group($link, Closure $closure)
{
$group = $this->container->get('Router')->pushGroup($link, $closure);
$group();
$this->container->get('Router')->popGroup();
}
Router class
/**
* Process route groups
*
* #return string
*/
private function processGroups()
{
$link = '';
foreach ($this->route_groups as $group) {
$link .= $group->getUrl();
}
return $link;
}
/**
* Add a route group to the array
* #param string $link
* #param Closure $closure
* #return RouteGroup
*/
public function pushGroup($link, Closure $closure)
{
$group = new RouteGroup($link, $closure);
array_push($this->route_groups, $group);
return $group;
}
/**
* Removes the last route group from the array
*
* #return RouteGroup|bool The RouteGroup if successful, else False
*/
public function popGroup()
{
$group = array_pop($this->route_groups);
return ($group instanceof RouteGroup ? $group : false);
}
Route class is basic class with routing parameters - method, url, controller, action and additional parameters so I won't copy it here.
RouteGroup class
/**
* Create a new RouteGroup
*
* #param string $url
* #param Closure $closure
*/
public function __construct($url, $closure)
{
$this->url = $url;
$this->closure = $closure;
}
/**
* Invoke the group to register any Routable objects within it.
*
* #param Slinky $app The App to bind the callable to.
*/
public function __invoke()
{
$closure = $this->closure;
$closure();
}
Currently I have a REST route working for an Event controller (/event). I would like to handle Event SignUps in an EventSignUp controller, and map this controller to a /event/signups route.
The Zend Framework documentation states that the URL /event/signup/:id should map to an Event_SignupController. But this does not work for me.
I set up the default REST route for all controllers in my Bootstrap class:
$front = Zend_Controller_Front::getInstance();
$router = $front->getRouter();
// Specifying all controllers as RESTful:
$restRoute = new Zend_Rest_Route($front);
$router->addRoute('default', $restRoute);
Am I missing something or is the documentation just wrong? If the documentation is wrong, what approach should I take to achieve my desired goal?
As a side note, a lot of existing controllers rely on the default REST route, so it would be nice if there is a solution that does not require to implement new routes for all existing controllers.
Edit: The documentation states that /product/ratings will be translated to the Product_RatingsController, which means the RatingsController in the Products module. Because all my controllers are placed in the default module, my desired behavior is not supported by the Rest Route.
So this changes my question, is it possible to achieve my desired behavior without affecting the existing controllers dependency on the default Rest route? If so, how? And if not, what would be the best approach for me to take?
Based on the comments of Haim Evgi I created a controller plugin that adds Zend_Controller_Router_Route routes based on the request method. This is the code of that controller plugin:
class TW_Webservice_Controller_Plugin_RestRoutes extends Zend_Controller_Plugin_Abstract
{
/**
*
* #var Zend_Controller_Router_Interface
*/
public $router;
/**
* Setup Rest routes that are not handled by the default Zend_Rest_Route object.
*
* #param Zend_Controller_Request_Abstract $request
*/
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$front = Zend_Controller_Front::getInstance();
$this->router = $front->getRouter();
$method = strtolower($request->getMethod());
$restRoutes = array(
'/event/signup' => 'event-signup'
);
$this->addRoutes($method, $restRoutes);
}
/**
*
* #param string $method The request method
* #param array $restRoutes Router pattern => Controller name pairs
*/
public function addRoutes($method, $restRoutes)
{
foreach ($restRoutes as $routePattern => $controllerName) {
switch ($method) {
case "get":
$this->addGetRoutes($routePattern, $controllerName);
break;
case "post":
$this->addPostRoute($routePattern, $controllerName);
break;
case "put":
$this->addPutRoute($routePattern, $controllerName);
break;
case "delete";
$this->addDeleteRoute($routePattern, $controllerName);
break;
}
}
}
/**
*
* #param string $routePattern
* #param string $controllerName
*/
public function addGetRoutes($routePattern, $controllerName)
{
$this->addRestRoute($routePattern, $controllerName, 'index');
$routePattern = $routePattern . '/:id';
$this->addRestRoute($routePattern, $controllerName, 'get');
}
/**
*
* #param string $routePattern
* #param string $controllerName
*/
public function addPostRoute($routePattern, $controllerName)
{
$this->addRestRoute($routePattern, $controllerName, 'post');
}
/**
*
* #param string $routePattern
* #param string $controllerName
*/
public function addPutRoute($routePattern, $controllerName)
{
$routePattern = $routePattern . '/:id';
$this->addRestRoute($routePattern, $controllerName, 'put');
}
/**
*
* #param string $routePattern
* #param string $controllerName
*/
public function addDeleteRoute($routePattern, $controllerName)
{
$routePattern = $routePattern . '/:id';
$this->addRestRoute($routePattern, $controllerName, 'delete');
}
/**
*
* #param string $routePattern
* #param string $controllerName
* #param string $action
*/
public function addRestRoute($routePattern, $controllerName, $action)
{
$route = new Zend_Controller_Router_Route($routePattern, array(
'controller' => $controllerName,
'action' => $action
));
$this->router->addRoute($controllerName . '-' . $action, $route);
}
}
It would be nicer if the $restRoutes array is retrieved from a config file, but for now this works.