Say I have the following route:
Route::any('/door1/{options?}', array(
'as' => 'path',
'after' => 'somefilter',
'uses' => 'DoorController#path'
))
where my after filter has the job to increment some value on a DB Table. The filter would look something like this:
Route::filter('somefilter', function($route, $request, $response, $action)
{
try
{
//...
IncrementDBTable();
//...
}
catch(Exception $e)
{
return Redirect::home();
}
});
As for the controller,
if the options parameter is valid then all is fine and a specific view is called, otherwise the options are "corrected" and a redirect to the same route is performed:
public function path($options='option1,option2')
{
if( !this->isValidOptions($options) ) {
$options = this->correctOptions($options);
return Redirect::route( 'path', array(
'options' => $options
) );
}
// everything fine:
return View::make( 'door' );
}
My problem is, and for the case the options are wrong, the after filter is called twice, meaning the increment on the DB table is performed also twice. How can I "ignore" the after filter for the case the options are wrong? One possible solution of course is to also validate the options inside the filter function, but I wouldn't like to replicate code (this is just a simple example but things would get ugly if the validate options function would involve a lot more of code and possibly a bunch of db queries).
Looking at your code, you can skip the Redirect after correcting the options and just continue down the function instead. I don't see a reason for redirecting after correcting the options. That will also solve your problem about the after filter being called twice.
Related
I have a question regarding Events with Laravel 4.2...
I currently have an event listener on "auth.login"... some code lines are executed when user logins on web version... however I would like to execute a different action if the user logged via the API controller, example: ApiController#postLogin (my mobile version).
Code in my home controller:
if (Auth::attempt(['email' => Input::get('login'), 'password' => Input::get('password')]) OR Auth::attempt(['username' => Input::get('login'), 'password' => Input::get('password')]))
{
return Redirect::intended(URL::route('dashboard.index'));
}
else
{
return Redirect::action('HomeController#getIndex')->with('poplogin', true)->with('badcredentials',true)->withInput();
}
Code in global.php (event listener)
Event::listen('auth.login', function($user)
{
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
Session::flash('justlogged',true);
//other code that I didnt include..........
});
Code in my ApiController
public function getRefreshData() {
//check the token
$token = Input::get('token');
$username = Input::get('username');
$user = User::where('api_token', $token)
->where('username', $username)
->first();
if(!$user || !$token) {
return Response::json([
'error' => true,
'message' => 'Invalid Token, please re login',
'code' => 401],
401
);
}
Auth::login($user);
//5 last Timesheets + tslines, for pre-load at log-in in phone memory
//Not inserting possible creation dates between, to keep phone app 100% independent
$timesheets = $user->timesheets()->orderBy('startdate', 'DESC')->take(10)->with('tslines')->get();
//Other code that I didnt include
);
return $response;
}
I cannot control the execution of the event "auth.login" myself.. firing it manually with parameter would just double-fire the event (i think?)
Is there a way to detect where the event got fired from in the Event:listen and do not insert a "log-in attemp" (my code in event listener) each time I use the getRefreshData() function in my API? Yes, I need to log the user in my API function (for other code that isn't included)
Edit: It seems to me that the most straightforward way to handle this is to check for the token in the Event listener.
Event::listen('auth.login', function($user)
{
if (Input::has('token') && Input::has('username')) {
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
Session::flash('justlogged',true);
//other code that I didnt include..........
}
});
I really would suggest, long term, looking at using the functionality demonstrated in the docs under Accessing the Logged In User, it's just going to make life easier.
Original response: It might be helpful if you posted more code, because I feel like maybe this is an instance where if we zoom out a little bit maybe there is a better way to deal with this situation. Possibly you need multiple actions, different listeners, etc.
For solving this issue though, it's easy, just pass in whatever additional data you need to via a parameter:
$response = Event::fire('auth.login', array($user, 'source' => 'ApiController#postLogin', 'mobile' => true));
Then you can set those parameters to the $event object that is passed to your listener.
Let me know if you have any further questions!
After some research, I found how I could 'bypass' the execution of the event listener when the event is fired from the ApiController, using the Request::is() function
From L4.2 Docs: http://laravel.com/docs/4.2/requests#request-information )..
My routes.php file is like so:
Route::controller('api/v1', 'ApiV1Controller');
And in my global.php (where I declare my event listener)
Event::listen('auth.login', function($user)
{
if (!Request::is('api/*'))
{
//Code that is always executed at firing of event, except when from my API controllers
//Put Login_attemp in Database for Last activity, etc
$user->login_attemp()->create(['login_ip'=>$_SERVER['REMOTE_ADDR'],'login_time'=> date('Y-m-d H:i:s',time())]);
$user->last_logged = date('Y-m-d H:i:s',time());
$user->save();
}
}
Im curious to know if it is possible to prevent users who don't have a role of owner or administrator from accessing certain controllers in a laravel application?
Yes you can. You can do this with a route filter.
routes.php
Route::group(['prefix' => 'admin', 'before' => 'auth.admin'), function()
{
// Your routes
}
]);
and in filters.php
Route::filter('auth.admin', function()
{
// logic to set $isAdmin to true or false
if(!$isAdmin)
{
return Redirect::to('login')->with('flash_message', 'Please Login with your admin credentials');
}
});
Route filters have already been proposed but since your filter should be Controller specific you might want to try controller filters.
First off, lets add this your controller(s)
public function __construct()
{
$this->beforeFilter(function()
{
// check permissions
});
}
This function gets called before a controller action is executed.
In there it depends on you what you want to do. I'm just guessing now, because I don't know your exact architecture but I suppose you want to do something like this:
$user = Auth::user();
$role = $user->role->identifier;
if($role !== 'admin' && $role !== 'other-role-that-has-access'){
App::abort(401); // Throw an unauthorized error
}
Instead of throwing an error you could also make a redirect, render a view or do basically whatever you want. Just do something that stops further execution so your controller action doesn't get called.
Edit
Instead of using Closure function, you can use predefined filters (from the routes.php or filters.php)
$this->beforeFilter('filter-name', array('only' => array('fooAction', 'barAction')));
For more information, check out the documentation
I have a form where someone searches for something. Based on this form, I validate if the input is correct:
$validator = Validator::make(Input::all() , array(
'address' =>'required',
));
if($validator->fails()) {
return Redirect::to('/')->withErrors($validator);
}
After this, I want to validate something else (that a result object isn't empty), which is completely unrelated to the search. In other words, it's NOT input from a form.
1) Do I create another validator to validate this? Or
2) Is there a better way to simply check this value and spawn an object that can be returned with "withErrors"?
UPDATE
This isn't working for me:
$validator = Validator::make(
array(
'searches' => sizeof($search)
) ,
array(
'searches' => 'required|min:1'
)
);
if($validator->fails()) {
return Redirect::to('/')->withErrors($validator);
}
It's not working because for some reason it's picking up that the "searches" item should only be validated "sometimes"
you have two ways. one is custom validator
or there is a simpler way,
suppose,
private function foo()
{
$data = ''; //retrieved the data error here with whatever call you want to make
return !empty($data) ? true : false;
}
in the controller,
public function bar()
{
if(!$this->foo())
{
$messages = new \Illuminate\Support\MessageBag;
// you should use interface here. i directly made the object call for the sake of simplicity.
$messages->add('custom', 'custom error');
return Redirect::back()->withErrors($messages)->withInput();
}
}
in the view:
#if($errors->has('custom'))
<p>custom error output.</p>
#endif
it is just the outline to give you the idea.
I am trying to capture a wildcard from URL and then first pass it to a filter then route to controller. I am not sure how to plot the question exactly but here is what I've tried so far.
Route::get('test/(:any?)', array('as' => 'testroute', 'uses' => 'test#result', 'before' => "test_filter:$1"));
Route::filter('test_filter', function($id = NULL)
{
if($id)
echo "This id is " . $id; // Prints "This id is $1"
});
and
Route::get('test/(:any?)', array('as' => 'testroute', function($id = NULL)
{
if($id)
echo "this id is " . $id; // Does not do anything
}, 'uses' => 'test#result'));
Basically, I want to check if there is an id appended to the URL and set a cookie if there is one. But regardless of the case, I want this route to be handled by a controller no matter if there is any id appended or not.
I have to do the same thing with so many routes so I'd prefer something like a filter rather than modifying the controller's codes.
I know that I can directly pass the wildcard element to a closure, or I can feed this as a parameter to any controller but in that case I'll have to modify the controller codes, which I can't at the moment.
Can I do it through filters ? or any other way in which i wont have to modify the controller codes ?
Try passing an anonymous right after the before
Route::get('test/(:any?)',
array(
'as' => 'testroute',
'uses' => 'test#result',
'before' => "test_filter",
function($my_cookie_value)
{
// Set cookie here
}
)
);
Taken from here
I'd use a middleware http://laravel.com/docs/5.1/middleware (or filter for older Laravel versions) and add the route/s into a group which has the middleware as you can see in here http://laravel.com/docs/5.1/routing#route-group-middleware.
The middleware will be executed before the route code, where you can add a logic to manage your cookie.
I am just starting to get my head into caching as a whole. I have a simple indexAction() that fetches all given Datasets. My approach is:
check for existing key 'controllername-index-index'
if existing: return the value of the key
if not existing, do the normal action and add the key
The value inside the key should be the ViewModel that will be generated and populated with my data.
Here's what i have done so far:
<?php
public function indexAction()
{
$sl = $this->getServiceLocator();
// $cache = $sl->get('cache');
// $key = 'kennzahlen-index-index';
//
// if ($cache->hasItem($key)) {
// return $cache->getItem($key);
// }
$viewModel = new ViewModel();
$viewModel->setTemplate('kennzahlen/index/index');
$entityService = $sl->get('kennzahlen_referenzwert_service');
$viewModel->setVariable('entities', $entityService->findAll());
// $cache->setItem($key, $viewModel);
return $viewModel;
}
The Caching parts are commented out for testing purposes, but basically this is all that i am doing. The Caching config/service looks like the following:
<?php
'cache' => function () {
return \Zend\Cache\StorageFactory::factory(array(
'adapter' => array(
'name' => 'filesystem',
'options' => array(
'cache_dir' => __DIR__ . '/../../data/cache',
'ttl' => 100
),
),
'plugins' => array(
array(
'name' => 'serializer',
'options' => array(
)
)
)
));
},
The serialization and caching works quite well, but i am surprised by the missing results. Going by what the ZendDevelopersToolbar tells me, the times WITHOUT caching range between 1.8s to 2.5s. Having the caching parts uncommented (enabled) doesn't really improve the loading time of my page at all.
So my question is: Is this approach completely wrong? Are there different, more speedy parts, that can be saved with some neat configuration tricks?
I Feel that a 2 second load time of a page is DEFINITELY too slow. 1s to me is the maximum given a huge amount of data, but certainly not anything more than that :S
All help/hints/suggestions will be greatly appreciated. Thanks in advance!
One option would be to cache the complete output of your page, for example based on the route match. You need to listen between routing and dispatching which route has been found as match and then act accordingly:
namespace MyModule;
use Zend\Mvc\MvcEvent;
class Module
{
public function onBootstrap(MvcEvent $e)
{
// A list of routes to be cached
$routes = array('foo/bar', 'foo/baz');
$app = $e->getApplication();
$em = $app->getEventManager();
$sm = $app->getServiceManager();
$em->attach(MvcEvent::EVENT_ROUTE, function($e) use ($sm) {
$route = $e->getRouteMatch()->getMatchedRouteName();
$cache = $sm->get('cache-service');
$key = 'route-cache-' . $route;
if ($cache->hasItem($key)) {
// Handle response
$content = $cache->getItem($key);
$response = $e->getResponse();
$response->setContent($content);
return $response;
}
}, -1000); // Low, then routing has happened
$em->attach(MvcEvent::EVENT_RENDER, function($e) use ($sm, $routes) {
$route = $e->getRouteMatch()->getMatchedRouteName();
if (!in_array($route, $routes)) {
return;
}
$response = $e->getResponse();
$content = $response->getContent();
$cache = $sm->get('cache-service');
$key = 'route-cache-' . $route;
$cache->setItem($key, $content);
}, -1000); // Late, then rendering has happened
}
}
The second listener checks at the render event. If that happens, the result of the response will be cached.
This system (perhaps not with 100% copy/paste, but the concept) works because if you return a Response during the route or dispatch event, the application will short circuit the application flow and stop further triggering listeners. It will then serve this response as it is.
Bear in mind it will be the complete page (including layout). If you don't want that (only the controller), move the logic to the controller. The first event (now route) will be dispatch of the controller. Listen to that early, so the normal execution of the action will be omitted. To cache the result, check the render event for the view layer to listen to.
/update: I wrote a small module to use this DRY in your app: SlmCache