I'm using Slim Framework version 3.
I've followed the tutorial on creating an app. Part of this involves setting up a classes directory where you can put your own PHP classes.
What I can't understand is how you can access Slim inside those. For example if I have a class src/classes/Users.php and wanted to use the Slim Response code e.g.
return $response->withStatus(302)->withHeader('Location', 'login');
Obviously, $response, is not accessible at that point. It only seems to be in index.php where each callback recieves it as an argument.
Do I have to pass something to every constructor of my own classes, or use or require statements in my classes?
I'd say when domain layer components need to access application level components - this is a code smell. So, consider doing things otherwise, request object describes request. Request contains some data, and that data should be passed to your User class, not request object itself.
If you still wish to use Request object in Users class, simply pass it as argument, like this:
// in your routes
$app->post('users', function($request, $response) {
$user = new User;
$user->hydrateAndPersist($request); // there you pass it as argument
});
// in your user class
class User
{
public function hydrateAndPersist(
\Psr\Http\Message\ServerRequestInterface $request,
\Psr\Http\Message\ResponseInterface $response // yes, you force yourself into injecting response object
) {
// request is here, let's extract data from it
$submittedData = $request->getParams();
// terrible indeed, but this is just an example:
foreach ($submittedData as $prop => $value) {
$this->prop = $value;
}
$result = $this->save();
return $response->withJson($result);
}
}
However, in this case your User class is coupled heavily with PSR-7 request and response objects. Sometimes coupling is not a problem, but in your case User class belongs to domain layer (since it describes User entity), while $request and $response are components of application layer.
Try to reduce coupling:
$app->post('users', function($request, $response) {
$submittedData = $request->getParams();
$user = new User;
$result = $user->hydrateAndPersist($submittedData);
// response is already declared in this scope, let's "put" result of the operation into it
$response = $response->withJson($result);
return $response;
});
class User
{
public function hydrateAndPersist(array $data) : bool
{
$result = false;
foreach ($submittedData as $prop => $value) {
$this->prop = $value;
}
$result = $this->save();
return $result;
}
}
See the benefit? User::hydrateAndPersist now accepts array as argument, it has no knowledge of $request and $response. So, it is not tied to HTTP (PSR-7 describes HTTP messages), it can work with anything. Classes separated, layers separated, ease of maintenance.
To sum up: you can access $request object in your User class by simply passing $request to one of User methods. However, this is poor design that will reduce maintainability of your code.
Related
I'm using Laravel 5.6 and Instagram API library.
To work with this Instagram API I need to create object $ig = new \InstagramAPI\Instagram(). And then for getting any user's information I must use $ig->login('username', 'password') every time.
So I don't want to use this function all the time. The first I want to create a global variable which will contain $ig = new \InstagramAPI\Instagram(). However, I don't know how to correctly do it.
I tried to use singleton:
$this->app->singleton(Instagram::class, function ($app) {
Instagram::$allowDangerousWebUsageAtMyOwnRisk = true; // As wiki says
return new Instagram();
});
When I called $ig->login('name', 'pass') in any method all user profile's information changed in this object, but then if I call dd($ig = app(Instagram::class)) in another Controller method I see that previous data did not save. "WTF?" - I said.
Someone tells me that singleton just promise me that there won't be created the same object, but it does not save any changes.
I tried to use sessions:
However, when I tried to set variable with object as value anything did not happen.
$ig = new \InstagramAPI\Instagram();
session(['ig' => $ig]);
I think it's because of I tried to put a large object. And from the other hand it's not secure method!
Just let me know:
How can I create an object which I could use in every method with saving change for the next actions?
When I called $ig->login('name', 'pass') in any method all user profile's information changed in this object, but then if I call dd($ig = app(Instagram::class)) in another Controller method I see that previous data did not save.
That is the correct behavior. When a new request is sent to Laravel, a new instance of the Instagram is created. I'm not sure if you understand the meaning of a singleton but in terms of Laravel, there is one instance per HTTP request.
Since the Instagram API you're using does not contain functionality to relogin, I created a class (that would be place in the app/Classes folder).
<?php
namespace App\Classes;
use InstagramAPI\Instagram;
use InstagramAPI\Response\LoginResponse;
class CustomInstagram extends Instagram {
public function relogin(LoginResponse $response) {
$appRefreshInterval = 1800;
$this->_updateLoginState($response);
$this->_sendLoginFlow(true, $appRefreshInterval);
return $this;
}
}
Change the singleton instance so it uses the App\Classes\CustomInstagram class.
$this->app->singleton(Instagram::class, function ($app) {
Instagram::$allowDangerousWebUsageAtMyOwnRisk = true; // As wiki says
return new App\Classes\CustomInstagram();
});
In order to use the Instagram object with an authenticated user, the login information will need to be persisted some how. This would be placed where the login is occurring.
try {
$response = app(Instagram::class)->login($username, $password);
if ($response->isTwoFactorRequired()) {
// Need to handle if 2fa is needed (we're not completely logged in yet)
}
// Can use session to persist \InstagramAPI\Response\LoginResponse but I'd recommend the database.
session(['igLoginResponse' => serialize($response)]);
} catch (\Exception $e) {
// Login failed
}
Then create a middleware to relogin the user to Instagram (if the login response exists). You need to register this as described here. Then, the Instagram singleton can be used in your controller.
namespace App\Http\Middleware;
use Closure;
use InstagramAPI\Instagram;
class InstagramLogin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$responseSerialized = session('igLoginResponse');
if (!is_null($responseSerialized)) {
$ig = app(Instagram::class);
$response = unserialize($responseSerialized);
$ig->relogin($response);
}
return $next($request);
}
}
Is it possible get the value of a route placeholder within a Slim container? I know I can access the placeholder by adding a third parameter to the request but I'd like to have it injected so I'm not assigning it on each request.
I've tried $app->getContainer('router') but I can't seem to find a method to actually pull the placeholder value.
Example:
$app = new Slim\App;
$c = $app->getContainer();
$c['Controller'] = function() {
$userId = // how do I get the route placeholder userId?
return new Controller($userId);
};
$app->get('/user/{userId}','Controller:getUserId');
class Controller {
public function __construct($userId) {
$this->userId = $userId;
}
public function getUserId($request,$response) {
return $response->withJson($this->userId);
}
}
Without some 'hacky' things this will not work because we have no access on the request object build by slim, while the controller get constructed. So I recommend you to just use the 3rd parameter and get your userid from there.
The 'hacky' thing would be todo the same, what slim does when you execute $app->run(), but if you really want todo this, here you'll go:
$c['Controller'] = function($c) {
$routeInfo = $c['router']->dispatch($c['request']);
$args = $routeInfo[2];
$userId = $args['userId'];
return new Controller($userId);
};
Note: slim3 also urldecoded this values so may do this as well urldecode($args['userId']) Source
create a container wrapper and a maincontroller then extend all your controller from your maincontroller, then you have access to the container.
here is how i solved this problem:
https://gist.github.com/boscho87/d5834ac1ba42aa3da02e905aa346ee30
I am using the Slim Framework for a simple crud-style application. My index.php file has become quite long and unwieldy with all the different routes. How can I clean up / refactor this code? For example I have code like the following for all the different routes and GET, POST, PUT, DELETE etc.
$app->get("/login", function() use ($app)
{//code here.....});
What I like to do is group routes, and for each group, I create a new file under a subdir called routes. To illustrate with some example code from the Slim docs:
index.php:
$app = new \Slim\Slim();
$routeFiles = (array) glob(__DIR__ . DIRECTORY_SEPARATOR . 'routes' . DIRECTORY_SEPARATOR . '*.php');
foreach($routeFiles as $routeFile) {
require_once $routeFile;
}
$app->run();
routes/api.php:
// API group
$app->group('/api', function () use ($app) {
// Library group
$app->group('/library', function () use ($app) {
// Get book with ID
$app->get('/books/:id', function ($id) {
});
// Update book with ID
$app->put('/books/:id', function ($id) {
});
// Delete book with ID
$app->delete('/books/:id', function ($id) {
});
});
});
You can even do this for multiple levels, just be sure you don't overcomplicate yourself for this.
You can also do this for hooks off course.
You can for example move code the inner code to class:
$app->get("/login", function() use ($app)
{
$user = new User();
$user->login();
});
or even create your own class that will handle routing
class Router {
public function __construct($app) {
$this->app = $app;
}
public function createRoutes() {
$this->app->get("/login", function() use ($this->app)
{
$user = new User();
$user->login();
});
// other routes, you may divide routes to class methods
}
}
and then in your index.php
$router = new Router($app);
$router->createRoutes();
You can move index.php content into diferent files and just include them. For example:
index.php:
$app = new \Slim\Slim();
...
require_once 'path_to_your_dir/routes.php';
...
$app->run();
routes.php:
$app->get('/hello/:name', function ($name) {
echo "Hello, $name";
});
...
Or you can even create different files for different routes types:
index.php:
$app = new \Slim\Slim();
...
require_once 'path_to_your_dir/routes.php';
require_once 'path_to_your_dir/admin_routes.php';
require_once 'path_to_your_dir/some_other_routes.php';
...
$app->run();
Same approach is also ok for DI services initialization etc. (everything from your index.php)
This is how i use Slim. I do have a single file with the routes but i use a three layer approach.
index.php - i don't handle any logic here. i just register the routes and consume the api methods (post, put, delete, etc).
$app->post('/bible/comment/', function() use($ioc) {
$ioc['commentApi']->post();
});
The API layers inherit a base class where it's inject the slim app object. From there i use helper methods to extract the data from requests using arrays with the required fields, optional fields, etc. I don't do validation here. This is also where i clean the requests for xss.
Most important, i handle the exceptions here. Invalid requests throws exceptions, which are caught and transformed in a error response.
class CommentApi extends BaseApi {
public function post() {
$fields = array(array('message', 'bookId', 'chapter', 'verseFrom', 'verseTo')):
$dtoModel = new Models\CreateComment();
$data = $this->extractFormData();
Utils::transformDto($dtoModel, $data, $fields):
try {
$result = $this->commentService->create($this->getUserId(), $dtoModel);
$response->success("You've added a book to the bible."); // helper from BaseApi to set the response 200
$response->setResult($result);
}
catch(\Exceptions\CommentRepeatedException $ex) {
$response->invalid('The foo already exist. Try a new one');
}
catch(\Exceptions\CommentsClosedException $ex) {
UtilsExceptions::invalidRequest($dtoModel, $ex);
$response->invalid('Invalid request. Check the error list for more info.');
}
$this->respond($response); // encode the response in json, set the content type, etc
}
This layer class consume the business layers which will use repositories and others resources. I test the projects against the business layers, the api layers just extract the data, create the dto models and handle the response.
The request/response models implements interfaces to return a status code, erros messages to be consumed in for the clients (respect is cool to automate this).
class CommentBusiness {
public function create($userId, Models\CreateComment $model) {
// Validate the request object
// Assert all logic requirements
$dataRes = $this->repository->create('message' => $model->getMessage(), 'bookId' => $model->getUserId(), 'chapter' => $model->getChapter(), 'verseFrom' => $mode->getVerseFrom(), 'verseTo' => $model->getVerseTo());
if($dataRes->isInvalid()) {
throw new \Exceptions\DataException($dataRes->getExModel());
}
return $dataRes;
}
}
I like to use classes as route callbacks, and combine that with grouping of routes. Doing that, your index.php file (or wherever you choose to define your routes) will probably become very "Slim":
$app->group('/user', function() use ($app) {
$app->map('/find/:id', '\User:search')->via('GET');
$app->map('/insert', '\User:create')->via('POST');
// ...
});
In this example, the /user/find/:id route will call the search method (passing the value of :id as the first parameter) of the User class. So your callback class might look a bit like this:
class User {
public function search($userId) {
// ...
}
}
There are a number of advantages to this approach, especially if you are writing a CRUD-style application (that needs database access). For example, you can contain all your database logic in a base class and use inherited child classes for the route callbacks. You can also encapsulate all route-group-related logic into separate classes, e.g. having a User class that handles all the /user routes, and so on.
I want to redirect admins to /admin and members to /member when users are identified but get to the home page (/).
The controller looks like this :
public function indexAction()
{
if ($this->get('security.context')->isGranted('ROLE_ADMIN'))
{
return new RedirectResponse($this->generateUrl('app_admin_homepage'));
}
else if ($this->get('security.context')->isGranted('ROLE_USER'))
{
return new RedirectResponse($this->generateUrl('app_member_homepage'));
}
return $this->forward('AppHomeBundle:Default:home');
}
If my users are logged in, it works well, no problem. But if they are not, my i18n switch makes me get a nice exception :
The merge filter only works with arrays or hashes in
"AppHomeBundle:Default:home.html.twig".
Line that crashes :
{{ path(app.request.get('_route'), app.request.get('_route_params')|merge({'_locale': 'fr'})) }}
If I look at the app.request.get('_route_params'), it is empty, as well as app.request.get('_route').
Of course, I can solve my problem by replacing return $this->forward('AppHomeBundle:Default:home'); by return $this->homeAction();, but I don't get the point.
Are the internal requests overwritting the user request?
Note: I'm using Symfony version 2.2.1 - app/dev/debug
Edit
Looking at the Symfony's source code, when using forward, a subrequest is created and we are not in the same scope anymore.
/**
* Forwards the request to another controller.
*
* #param string $controller The controller name (a string like BlogBundle:Post:index)
* #param array $path An array of path parameters
* #param array $query An array of query parameters
*
* #return Response A Response instance
*/
public function forward($controller, array $path = array(), array $query = array())
{
$path['_controller'] = $controller;
$subRequest = $this->container->get('request')->duplicate($query, null, $path);
return $this->container->get('http_kernel')->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
By looking at the Symfony2's scopes documentation, they tell about why request is a scope itself and how to deal with it. But they don't tell about why sub-requests are created when forwarding.
Some more googling put me on the event listeners, where I learnt that the subrequests can be handled (details). Ok, for the sub-request type, but this still does not explain why user request is just removed.
My question becomes :
Why user request is removed and not copied when forwarding?
So, controller actions are separated part of logic. This functions doesn't know anything about each other. My answer is - single action handle kind of specific request (e.g. with specific uri prarams).
From SF2 docs (http://symfony.com/doc/current/book/controller.html#requests-controller-response-lifecycle):
2 The Router reads information from the request (e.g. the URI), finds a
route that matches that information, and reads the _controller
parameter from the route;
3 The controller from the matched route is
executed and the code inside the controller creates and returns a
Response object;
If your request is for path / and you wanna inside action (lets say indexAction()) handling this route, execute another controller action (e.g. fancyAction()) you should prepare fancyAction() for that. I mean about using (e.g.):
public function fancyAction($name, $color)
{
// ... create and return a Response object
}
instead:
public function fancyAction()
{
$name = $this->getRequest()->get('name');
$color = $this->getRequest()->get('color');
// ... create and return a Response object
}
Example from sf2 dosc:
public function indexAction($name)
{
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array(
'name' => $name,
'color' => 'green',
));
// ... further modify the response or return it directly
return $response;
}
Please notice further modify the response.
If you really need request object, you can try:
public function indexAction()
{
// prepare $request for fancyAction
$response = $this->forward('AcmeHelloBundle:Hello:fancy', array('request' => $request));
// ... further modify the response or return it directly
return $response;
}
public function fancyAction(Request $request)
{
// use $request
}
I've built a first-run web service on Zend Framework (1.10), and now I'm looking at ways to refactor some of the logic in my Action Controllers so that it will be easier for me and the rest of my team to expand and maintain the service.
I can see where there are opportunities for refactoring, but I'm not clear on the best strategies on how. The best documentation and tutorials on controllers only talk about small scale applications, and don't really discuss how to abstract the more repetitive code that creeps into larger scales.
The basic structure for our action controllers are:
Extract XML message from the request body - This includes validation against an action-specific relaxNG schema
Prepare the XML response
Validate the data in the request message (invalid data throws an exception - a message is added to the response which is sent immediately)
Perform database action (select/insert/update/delete)
Return success or failure of action, with required information
A simple example is this action which returns a list of vendors based on a flexible set of criteria:
class Api_VendorController extends Lib_Controller_Action
{
public function getDetailsAction()
{
try {
$request = new Lib_XML_Request('1.0');
$request->load($this->getRequest()->getRawBody(), dirname(__FILE__) . '/../resources/xml/relaxng/vendor/getDetails.xml');
} catch (Lib_XML_Request_Exception $e) {
// Log exception, if logger available
if ($log = $this->getLog()) {
$log->warn('API/Vendor/getDetails: Error validating incoming request message', $e);
}
// Elevate as general error
throw new Zend_Controller_Action_Exception($e->getMessage(), 400);
}
$response = new Lib_XML_Response('API/vendor/getDetails');
try {
$criteria = array();
$fields = $request->getElementsByTagName('field');
for ($i = 0; $i < $fields->length; $i++) {
$name = trim($fields->item($i)->attributes->getNamedItem('name')->nodeValue);
if (!isset($criteria[$name])) {
$criteria[$name] = array();
}
$criteria[$name][] = trim($fields->item($i)->childNodes->item(0)->nodeValue);
}
$vendors = $this->_mappers['vendor']->find($criteria);
if (count($vendors) < 1) {
throw new Api_VendorController_Exception('Could not find any vendors matching your criteria');
}
$response->append('success');
foreach ($vendors as $vendor) {
$v = $vendor->toArray();
$response->append('vendor', $v);
}
} catch (Api_VendorController_Exception $e) {
// Send failure message
$error = $response->append('error');
$response->appendChild($error, 'message', $e->getMessage());
// Log exception, if logger available
if ($log = $this->getLog()) {
$log->warn('API/Account/GetDetails: ' . $e->getMessage(), $e);
}
}
echo $response->save();
}
}
So - knowing where the commonalities are in my controllers, what's the best strategy for refactoring while keeping it Zend-like and also testable with PHPUnit?
I did think about abstracting more of the controller logic into a parent class (Lib_Controller_Action), but this makes unit testing more complicated in a way that seems to me to be wrong.
Two ideas (just creating an answer from the comments above):
Push commonality down into service/repository classes? Such classes would be testable, would be usable across controllers, and could make controller code more compact.
Gather commonality into action helpers.
Since you have to do this step every time a request is made, you could store that receive, parse and validate the received request in a Zend_Controller_Plugin which would be run every PreDispatch of all controllers. (Only do-able if your XML request are standardized) (If you use XMLRPC, REST or some standard way to build requests to your service, you could look forward those modules built in ZF)
The validation of the data (action specific) could be done in a controller method (which would then be called by the action(s) needing it) (if your parametters are specific to one or many actions of that controller) or you could do it with the patterns Factory and Builder in the case that you have a lot of shared params between controllers/actions
// call to the factory
$filteredRequest = My_Param_Factory::factory($controller, $action, $paramsArray) // call the right builder based on the action/controller combination
// the actual Factory
class My_Param_Factory
{
public static function factory($controller, $action, $params)
{
$builderClass = "My_Param_Builder_" . ucfirst($controller) . '_' . ucfirst($action);
$builder = new $builderClass($params);
return $builder->build();
}
}
Your builder would then call specific parameters validating classes based on that specific builder needs (which would improve re-usability)
In your controller, if every required params are valid, you pass the processing to the right method of the right model
$userModel->getUserInfo($id) // for example
Which would remove all of the dataprocessing operations from the controllers which would only have to check if input is ok and then dispatch accordingly.
Store the results (error or succes) in a variable that will be sent to the view
Process the data (format and escape (replace < with < if they are to be included in the response for example)), send to a view helper to build XML then print (echo) the data in the view (which will be the response for your user).
public function getDetailsAction()
{
if ($areRequestParamsValid === true) {
// process data
} else {
// Build specific error message (or call action helper or controller method if error is general)
}
$this->view->data = $result
}