I am learning Laravel and have run into a problem handling missing arguments, which has also been explained here. However, the solution(s) focus primarily on named routes, but am using RESTful controllers.
TL;DR Is there some elegant solution to handle missing arguments on all of a given controller's methods without naming each one?
Route
Route::controller('accounts', 'AccountsController');
Controller
Here is a sample controller method...where the "example.com/accounts/profile/1" works, but "example.com/accounts/profile/" [with or without the trailing slash] will throw an exception. "ErrorException: Missing argument 1 for AccountsController::getProfile()"
public function getProfile($id)
{
$account = DB::table('accounts')->where('id', $id)->first();
$this->layout->content = View::make('account.profile', array('account' => $account));
}
One Attempted Solution (Doesn't work)
I've also tried this ↓ , which was a promising-looking suggestion from another question. However, it also does not work.
public function getProfile($id = NULL)
{
if ($id = NULL)
{
return Redirect::to('accounts');
} else {
$account = DB::table('accounts')->where('id', $id)->first();
$this->layout->content = View::make('account.profile', array('account' => $account));
}
}
Other Solution (Works, but is tedious)
I know that using named routes, such as the one below , to cover all of the holes will work, but this seems like such an un-elegant solution! (especially considering the suuuuper tedious process of naming all of the routes for all of the controllers that we are planning on using)
Route::get('/accounts/profile', 'AccountsController#missingMethod');
So....
What do you all think? Is there some elegant solution to handle missing arguments on all of a given controller's methods without naming each one?
The default parameter option should work aside from a minor and very commonly uncaught error in php.. You're using the assignment operator in your conditional. Use
if($id == NULL)
instead of
if($id = NULL)
or if you like
if(is_null($id) )
One of the tragedies of a dynamically typed language. Gets me more frequently than I care to admit.
Related
I have an app running on Laravel 4.2, and I am trying to implement a somewhat complex routing mechanism. I have a route group set up using:
Route::group(['domain' => '{wildcard}.example.com'], $closure);
I need to be able to check the $wildcard parameter in the closure for the group -- meaning before the request gets passed to the controller (I need to defined Route::get() and Route::post() depending on the subdomain).
An example of what I'd like to do is as follows:
Route::group(['domain' => '{wildcard}.example.com', function ($wildcard) {
if ( $wildcard == 'subdomain1' ) {
Route::get('route1', 'Subdomain1Controller#getRoute1');
Route::get('route2', 'Subdomain1Controller#getRoute2');
} else if ( $wildcard == 'subdomain2' ) {
Route::get('route1', 'Subdomain2Controller#getRoute1');
Route::get('route2', 'Subdomain2Controller#getRoute2');
}
}
);
Of course, the above does not work. The only parameter passed into a Route::group() closure is an instance of Router, not the parameters defined in the array. However, there must be a way to access those parameters -- I know for a fact that I've done it before, I just don't remember how (and can't find the solution anywhere online).
I know I can always use PHP's lower-level methods to retrieve the URL, explode() it, and check the subdomain that way. But I've done it before using Laravel's methods, and if possible, I'd prefer to do it that way (to keep things clean and consistent)
Does anyone else know the solution? Thanks in advance!
Use the Route::input() function:
Route::group(['domain' => '{wildcard}.example.com', function ($wildcard) use ($wildcard) {
if ( Route::input('wildcard') === 'subdomain1' ) {
Route::get('route1', 'Subdomain1Controller#getRoute1');
Route::get('route2', 'Subdomain1Controller#getRoute2');
} else {
Route::get('route1', 'Subdomain2Controller#getRoute1');
Route::get('route2', 'Subdomain2Controller#getRoute2');
}
}
);
See "Accessing A Route Parameter Value" in the docs.
I started to explore the world of Symfony 2 now and face with some realy strange problems i would not think they can occure in such a professional framework. I will show you the problems i face one by one:
1) How to get the recent actionName?
I found only this solution which is imho semiprofessional:
$request->attributes->get('_controller');
// will get yourBundle\Controller\yourController::CreateAction
$params = explode('::',$request->attributes->get('_controller'));
// $params[1] = 'createAction';
$actionName = substr($params[1],0,-6);
Is this serious, i have to do some extra-work to get it, why.. Is there a better solution? Creating a base controller class with a method e.g. getActionName(), but why do i have to implement such basic functionality in a framework. Is there a other way?
2) When i forward a request the code in 1) will not work.
$request = $this->container->get('request');
$getParameterList = $request->query->all();
if (!empty($getParameterList['mode'])
&& $getParameterList['mode'] == 1) {
return $this->forward('AcmeDemoBundle:Routing:lawyersearch', array(), $getParameterList);
}
The reason why it will not work is that "AcmeDemoBundle:Routing:lawyersearch" is a other format than when i came directly from a route. Second problem here is that i have to forward the GET-paramters as well(i think POST too). Is there a way that i do not have to care about it?
3) How to use a default template without using this annotation:
/**
* #Template()
*/
public function indexAction()
{
return array();
}
I do not want to have above all my methods this annotation; i know i can put it on the top of the class definition. Is there a way to achieve this? The only solution i see, is to write a BaseController that determines by a method out of the module/controller/action the default template.
4) I found classes that use public attributes e.g. Symfony\Component\Validator\Constraints\Length with e.g. public $max;
How to solve this? Very strange because this is not professional to use public attributes.
I hope someone has easy solutions for this. It would be realy dissapointing if Symfony 2 has so much strange behaviour in so much cases. 4 strange things i 2 days since i began to explore it. It gives me the feeling that there is much more when i continue.
Please confirm that there are no other solution by the framework or which is the solution. Thank you
1) By accessing the '_controller' parameter of the request, you are delving into the internals of Symfony2. They rarely document anything related to this outside of routing. You should use controller actions more definitively, don't try to automate too much on this level.
2) Symfony2 can't account for highly dynamic controllers. You know it is possible to call ->forward more than once, and within the same controller action. This creates a nesting nightmare that the Symfony developers weren't prepared to deal with.
This is one of the reasons $request = $this->container->get('request'); is now deprecated in favour of $stack = $this->container->get('request_stack');. Because forwarding needs to create new internal requests.
3) Also deprecated. Symfony2 best practices now discourages the use of #Template() with empty parameters because of the potentially volatile development of actions/templates. You are supposed to explicitly define which template to use, if you use one at all. This comes in handy when dealing with data-only responses. You wouldn't want your responses to use a template automatically as this would result in unexpected behaviour in your design.
1) Use Constant: __FUNCTION__
http://php.net/manual/en/language.constants.predefined.php
2) Try setMethod on $request:
$this->get('request')->setMethod('POST');
3) I do not know, probably not possible.
4) Symfony\Component\Validator\Constraints\Length is one of constraints:
http://symfony.com/doc/current/book/validation.html#constraints
i'm trying to implement Respect/Rest in my existing CMS.
The Problem:
I would like to create a single magic route to works like: /admin/*/save, calls the * controller...
I would like to make something like this:
$r->any('/admin/*/save/*/', function($controller, $id = null) use ($r) {
return $r->dispatchClass($controller,array($id));
});
Note that i don't know which HTTP method user is using.
Actually I "solved" this problem with something like:
$r->any('/admin/*/save/*/', function($controller, $id = null) use ($tcn) {
$r = new Router;
$r->any('/admin/*/save/*/', $tcn($controller . '_save'), array($id));
return $r->run();
});
$tcn is a named function that returns the full namespace of the controller.
I know it's not a good approach.
EDIT:
This project wants to be Open Source, but it's still being created.
We're trying to transport an old project made on functional paradigm to OOP.
We are trying to learn about OOP while making an useful project.
Actuall state of the files can be found at: https://github.com/dindigital/skeleton
Alganet: The bootstrap for admin routes can be found at: https://github.com/dindigital/skeleton/blob/master/admin_routes.php
A simple controller sample: https://github.com/dindigital/skeleton/blob/master/src/app/admin/controllers/TagController.php
https://github.com/dindigital/skeleton/blob/master/src/app/admin/controllers/TagSaveController.php
I liked the Forwards and also the Factory approach... I could not decide yet.
Tricky question! That depends a lot of why are you making these routes dynamic. Can you show us some sample structure for your controllers so I can improve the answer?
Meanwhile, two native features that can help:
Forwards
You can treat the problem as an internal forward (does not make redirects). It's normally to redirect to other static routes, but you can redirect to a new one as well:
$r->any(
'/admin/*/save/*/',
function ($controller, $id) use ($tcn, $r) {
return $r->any(
"/admin/$controller/save",
$tcn($controller . '_save'),
array($id)
);
}
);
Factory Routes
Also, Respect\Rest implements factory routes. It is experimental but stable in tests:
$r->factoryRoute(
'ANY',
'/admin/*/save/*/',
'MyAbstractController',
function ($method, array $params) use ($tcn) {
return new $tcn($params[0] . '_save');
}
);
Can I do this in a Controller:
$this->User->read(null, $id);
$this->User->find('list');
Is it correct?
Am I using MVC correctly?
Can these easy functions be used in a Controller? Or, do I need to create these functions in the Model? Like Model->getUser(), and have that function use Model->read().
I know that functions it's called by Model, but, when I want pass some parameters, and function makes big, for example:
$this->User->find('all', array(
'conditions' => array(
'User.active' => true,
'User.group_id' => 3,
'User.age >=' => 18
)
));
Can I call this function in Controller, or need create a custom function in Model, to call it? Like... $this->User->findSomeCustomFunction($param1, $param2, $param3)?
TLDR:
It's "ok" to call a find() from your Controller, however best practice is to put any/all find()s in your models.
If you make a habit of putting all your find()s in your models, it will make it much easier to maintain your code in the long run.
Explanation/example:
In this case, as an example, you could start with a seemingly simple function:
//User model
public function getUsers() {
return $this->find('list');
}
But later, maybe you need something more along the lines of:
//User model
public function getUsers($opts = array()) {
$defaults = array(
'findType' => 'all',
'activeOnly' => true,
);
$params = array_merge($defaults, $opts);
$qOpts = array('conditions' => array());
//active only
if(!empty($params['activeOnly'])) $conditions[$this->alias.'.active'] = 1;
return $this->find($params['findType'], $qOpts);
}
(Pardon if there are many ways to make that code better - it was just off the top of my head - It gives you the idea.)
Keeping all your find()s in the Model also keeps you from having to search through each Controller every time you want to write a find() to determine if you've used a similar find() anywhere else. If you're programming as a team, that can be a nightmare, and you're almost guaranteed to be duplicating code.
It is perfectly fine to call Model->find() from a Controller. However, you will also want follow the DRY (Don't Repeat Yourself) principles. That basically means "Don't copy-paste code everywhere."
So, if you find that you need to make this exact Model->find() call from many Controller actions, it is considered good practice to abstract it into a function call against the Model. So yes, your Controllers would then call $this->User->findSomeCustomFunction().
I'm working on a Symfony project (my first) where I have to retrieve, from my Widget class, a set of widgets that belong to a Page. Before returning the results, though, I need to verify--against an external service--that the user is authorized to view each widget. If not, of course, I need to remove the widget from the result set.
Using CakePHP or Rails, I'd use callbacks, but I haven't found anything similar for Symfony. I see events, but those seem more relevant to controllers/actions if I'm reading things correctly (which is always up for discussion). My fallback solution is to override the various retrieval methods in the WidgetPeer class, divert them through a custom method that does the authorization and modifies the result set appropriately. That feels like massive overkill, though, since I'd have to override every selection method to ensure that authorization was done without future developers having to think about it.
It looks like behaviors could be useful for this (especially since it's conceivable that I might need to authorize other class instances in the future), but I can't find any decent documentation on them to make a qualified evaluation.
Am I missing something? It seems like there must be a better way, but I haven't found it.
First of all, I think behavior-based approach is wrong, since it increases model layer coupling level.
There's sfEventDispatcher::filter() method which allows you to, respectively, filter parameters passed to it.
So, draft code will look like:
<somewhere>/actions.class.php
public function executeBlabla(sfWebRequest $request)
{
//...skip...
$widgets = WidgetPeer::getWidgetsYouNeedTo();
$widgets = $this->getEventDispatcher()->filter(new sfEvent($this, 'widgets.filter'), $widgets));
//...skip...
}
apps/<appname>/config/<appname>Configuration.class.php
//...skip...
public function configure()
{
$this->registerHandlers();
}
public function registerHandlers()
{
$this->getEventDispatcher()->connect('widgets.filter', array('WidgetFilter', 'filter'));
}
//...skip
lib/utility/WidgetFilter.class.php
class WidgetFilter
{
public static function filter(sfEvent $evt, $value)
{
$result = array();
foreach ($value as $v)
{
if (!Something::isWrong($v))
{
$result[] = $v;
}
}
}
}
Hope you got an idea.
Here's some documentation on Symfony 1.2 Propel behaviors: http://www.symfony-project.org/cookbook/1_2/en/behaviors.
Why not just have a 'getAllowedWidgets' method on your Page object that does the checks you're looking for? Something like:
public function getAllowedWidgets($criteria = null, PropelPDO $con = null) {
$widgets = $this->getWidgets($criteria, $con);
$allowed = array();
// get access rights from remote service
foreach($widgets as $widget) {
// widget allowed?
$allowed[] = $widget;
}
return $allowed;
}
However, if you always want this check to be performed when selecting a Page's Widgets then Propel's behaviours are your best bet.
Although, at least in theory, I still think that a behavior is the right approach, I can't find a sufficient level of documentation about their implementation in Symfony 1.4.x to give me a warm and fuzzy that it can be accomplished without a lot of heartburn, if at all. Even looking at Propel's own documentation for behaviors, I see no pre- or post-retrieval hook on which to trigger the action I need to take.
As a result, I took my fallback path. After some source code sifting, though, I realized that it wasn't quite as laborious as I'd first thought. Every retrieval method goes through the BasePeer model's doSelect() method, so I just overrode that one in the customizable Peer model:
static public function doSelect( Criteria $criteria, PropelPDO $con = null ) {
$all_widgets = parent::doSelect( $criteria, $con );
$widgets = array();
foreach ( $widgets as $i => $widget ) {
#if( authorized ) {
# array_push( $widgets, $widget );
#}
}
return $widgets;
}
I haven't wired up the service call for authorization yet, but this appears to work as expected for modifying result sets. When and if I have to provide authorization for additional model instances, I'll have to revisit behaviors to remain DRY, but it looks like this will suffice nicely until then.