CakePHP: RESTful prefixed action - rest

I have troubles to do REST application in CakePHP, requesting GET /admin/quote_authors/1.json sends me to the 'view' action, not 'admin_view'.
route.php:
Router::parseExtensions('json');
Router::mapResources(array(':controller'), array('prefix' => '/admin/'));
QuoteAuthorsController.php:
public $components = array('RequestHandler');
public function admin_view($id) {
var_dump('admin view');
}
public function view($id) {
var_dump('view');
}
Thanks.

Answering because I can't comment.
You seem to be missing the action part of the request /admin/quote_authors/view/1.json
So for other request it would be like /admin/:controller/:action/:params in general.
And, of course, like thaJeztah said, remove the slashes of the prefix (that's why it's giving you that error, it's considering the parameter "1" as the action it has to execute)

Related

How to make morre readable URIs using unique-id-plus-redundant-information (UPRI) using PHP with Laravel

I would like to know the best (and most consistent) way to add redundant bits to my restful uris so that they are more readable while remaining unchanging when some things such as username change.
I read of the concept at this excellent blog post and it is something like what stack overflow does /users/3836923/inkalimeva where the last segment of the URI is redundant and may change but makes the URI more readable and SEO friendly.
Currently I am using Laravel's Route::resource() but that creates routes with only the id segment.
You can use eloquent-sluggable to create slugs for your users. That way the slug will change when they update their username. You can also simply call their username in the url method, though this will result in uglier urls.
This method still requires that you drop Route::resource() and write your routes explicitly.
Here is the code, tested and working:
ROUTES.PHP (don't mind the route details)
Route::get('route-name/{id}/{slugOrUsernameAsYouPlease}', [
'as' => 'admin-confirm-detach-admin',
'uses' => 'AdminController#confirmDetachAdmin'
]);
IN YOUR VIEW
Click me!
OR
Click me!
URL RESULT (My users name here is Fnup. Just for testing)
With Username: http://website.local/route-name/8/Fnup
With Slug: http://website.local/route-name/8/fnup
A quick final note
I just changed fnup's username to fnupper and here is the result:
http://website.local/route-name/8/Fnupper
However the slug didn't change automatically. You have to add that code yourself to the user update method. Otherwise the slug stays as what it was the first time the resource was made. Here is my code when using eloquent-sluggable
public function update(UpdateUserRequest $request)
{
$user = \Auth::user();
$user->name = $request->name;
$user->email = $request->email;
$user->resluggify();
$user->save();
session()->flash('message', 'Din profil er opdateret!');
return redirect()->route('user-show');
}
Which result in: http://website.local/route-name/8/fnupper
New edit per request: Controller method example
Here is my confirmDetachAdmin() method in AdminController.php. Just to clarify, the methods job is to show a "confirm" view before modifying a users status. Just like edit/update & create/store, I made up confirm to accompany destroy (since I'd like a javascript free confirmation option should javascript be disabled).
public function confirmAttachAdmin($id)
{
$user = User::findOrFail($id);
/* Prevent error if user already has role */
if ( $user->hasRole('admin')) {
return redirect()->back();
}
return view('admin.confirmAttachAdmin', compact('user'));
}
You can add your slug/username as a second parameter if you want to, but I don't see a reason, as you can access it from $user when you find them by id.
As opposed to #MartinJH's answer, I don't think you should store your slugs in database if you don't rely only on them in your URIs. A simple link() method on your model, and an explicit route is enough.
App\User
class User extends \Illuminate\Database\Eloquent\Model {
public function link()
{
return route('user-profile', [ $this->id, Str::slug($this->username) ]);
}
}
routes.php
Route::get('{id}/{username}', [ 'as' => 'user-profile', 'uses' => 'UserController#profile' ])
->where('id', '\d+')
->where('username', '[a-zA-Z0-9\-\_]+');
App\Http\Controllers\UserController
...
public function profile($id, $username)
{
$user = \App\User::findOrFail($id);
return view('profile')->with('user', $user);
}
...

Laravel REST redirect from GET to POST method in SAME controller not working

I am trying to support the use of EITHER GET or POST methods in my REST controller in laravel.
So, I would like to redirect ANY get requests sent to our REST controller to the POST method in the SAME controller instead.
I have tried many things, and now have returned back to basics as follows:
routes.php
Route::resource('user', 'userController');
userController.php
class userController extends \BaseController {
public function index() {
return Redirect::action('userController#store');
}
public function store() {
echo 'yeeha!';
}
}
Performing a POST on the page works and outputs:
yeeha!
Performing a GET on the page produces:
Could not get any response
This seems to be like an error connecting to https://www.test.com/user. The response status was 0.
Check out the W3C XMLHttpRequest Level 2 spec for more details about when this happens.
I have tried many different redirects and none are successful.
The correct way is to do it is to use the routes file and just define it;
Routes.php
Route::get('/user', array ('as' => 'user.index', 'uses' => userController#store))
Route::post('/user', array ('as' => 'user.create', 'uses' => userController#store))
Controller
class userController extends \BaseController {
public function store() {
echo 'yeeha!';
}
}

Spring MVC REST

I'm using Spring MVC and I have a controller mapped to a url lets call it example. I also have a method called show that allows me to view one of my examples based on an id.
#RequestMapping("/example")
#RequestMapping(value = "/{id}", produces = "text/html")
public String show(#PathVariable("id") String id, Model model) {
//Do some stuff and return a view
}
The problem is that the id is a URI and it has forward slashes. (e.g. test/case/version/sample might be an id so the resulting url is example/test/case/version/sample) so as a result my application gives me an error "Requested resource not found". I can't easily change the format of these ids. It's a list given to me that I have to work with. Is there a way around this? Thanks in Advance.
You can try using Regular expressions on the #PathVariable.
Like this from the Spring Docs:
#RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(#PathVariable String version, #PathVariable String extension) {
// ...
}
}
You'll just have to think on a regular expression that matches the "example/test/case/version/sample" that is your expression.
See the title: "URI Template Patterns with Regular Expressions"
on this page: http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/mvc.html for more information

Optional route parameters and action selection

I use the default route definition:
{controller}/{action}/{id}
where id = UrlParameter.Optional. As much as I understand it this means when id is not being part of the URL this route value will not exists in the RouteValues dictionary.
So this also seems perfectly possible (both GET):
public ActionResult Index() { ... } // handle URLs: controller/action
public ActionResult Index(int id) { ... } // handle URLs: controller/action/id
When id is missing the first action would be executed, but when id is present, the second one would execute. Fine, but it doesn't work. It can't resolve actions.
How can I accomplish this?
I'm thinking of writing a custom action method selector attribute like:
[RequiresRouteValue(string valueName)]
This would make it possible to use this kind of action methods. But is this the only way of doing it?
Is there something built-in I can hang on to?
Use either:
[HttpGet]
public ActionResult Index() { ... } // handle URLs: controller/action
[HttpPost]
public ActionResult Index(int id) { ... } // handle URLs: controller/action/id
Or just have one with a nullable param:
public ActionResult Index(int? id) { ... } // handles both instances
EDIT:
Would something like this work?
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/", // URL with parameters
new { controller = "Login", action = "Index" } // Parameter defaults
);
routes.MapRoute(
"DefaultWithValue", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Login", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Well from the exception that action can't be determines is pretty clear that actions are resolved first then data binder comes into play and examines action's parameters and tries to data bind values to them. Makes perfect sense.
This makes perfect sense. There would be no point in first trying to data bind values to all possible types and see what we get and then look for an appropriate action. That would be next to impossible.
So. Since action selection is the problem here I guess the best (and only) way to solve this (if I don't want to use a multifaceted single action method) is to write a custom action method selector attribute.
You can read all the details and get the code on my blog:
Improving Asp.net MVC maintainability and RESTful conformance

Symfony 1.4: Custom error message for CSRF in forms

Can anyone tell me where/how to customise the CSRF token error message for forms in Symfony 1.4. I'm using sfDoctrineGuard for logins and in this form particularly, whenever a session runs out and you still have the page open, it throws a very user-unfriendly error: "CSRF attack detected". Something like "This session has expired. Please return to the home page and try again" sounds better.
What's the right way to do this in the form class?
Thanks.
The only way seems to be to overwrite sfForm::addCSRFProtection().
In /lib/form/BaseForm.class.php you can add this piece of code:
class BaseForm extends sfFormSymfony
{
public function addCSRFProtection($secret = null)
{
parent::addCSRFProtection($secret);
if (array_key_exists(self::$CSRFFieldName, $this->getValidatorSchema())) {
$this->getValidator(self::$CSRFFieldName)->setMessage('csrf_attack', 'This session has expired. Please return to the home page and try again.');
}
}
}
After calling the parent method, you retrieve the validator associated with the CSRF field and change the message for the code csrf_attack.
Edit: You also need to check whether or not the validator exists. Some forms might have their CSRF protection disabled!
Hope this helps!
None of these answers explain how to remove the "CSRF token:" label that prefixes the error message in a non-hackish way (e.g. changing the token name is a bad idea!).
The only sound way of removing the label is to extend the CSRF validator to throw a global error. While we do this, we can also change the error message.
class myValidatorCSRFToken extends sfValidatorCSRFToken
{
protected function configure($options = array(), $messages = array())
{
parent::configure($options, $messages);
$this->addMessage('csrf_attack', 'Your session has expired. Please return to the home page and try again.');
}
protected function doClean($value)
{
try {
return parent::doClean($value);
} catch (sfValidatorError $e) {
throw new sfValidatorErrorSchema($this, array($e));
}
}
}
Now, let's set our forms to use this validator by overriding sfForm::addCSRFProtection in BaseForm:
public function addCSRFProtection($secret = null)
{
parent::addCSRFProtection($secret);
if (isset($this->validatorSchema[self::$CSRFFieldName])) //addCSRFProtection doesn't always add a validator
{
$this->validatorSchema[self::$CSRFFieldName] = new myValidatorCSRFToken(array(
'token' => $this->validatorSchema[self::$CSRFFieldName]->getOption('token')
));
}
}
In 1.4.4 I had to modify naag's code like so...
public function addCSRFProtection($secret = null)
{
parent::addCSRFProtection($secret);
if (isset($this->validatorSchema[self::$CSRFFieldName])) {
$this->validatorSchema[self::$CSRFFieldName]->setMessage('csrf_attack', 'This session has expired. Please refresh and try again.');
}
}
This got it working, the 'csrf token:' bit still appears in the error message though.
Improving on previous answers, here is the code I use:
public function addCSRFProtection($secret = null)
{
parent::addCSRFProtection($secret);
if (isset($this->validatorSchema[self::$CSRFFieldName])) {
$this->validatorSchema[self::$CSRFFieldName]->setMessage('csrf_attack', 'This session has expired. Please refresh and try again.');
$this->getWidgetSchema()->getFormFormatter()->setNamedErrorRowFormatInARow(" <li>%error%</li>\n");
}
}
The default value for the NamedErrorRowFormatInARow is "<li>%name%: %error%</li>\n" adding the name and the colon.
Be careful because it changes the value for all forms and all global errors.
You can also change the field by creating a custom form formatter and using it in the forms you want. You can go look at the documentation here for more info on this.
Use event dispatcher. Check this out http://bluehorn.co.nz/2010/07/15/how-to-change-csrf-attack-message-in-symfony-1-2/
I wrote it for Symfony 1.2, but using event dispatcher, so it still might work for Symfony 1.4.
I suppose the "csrf token:" prefix can be removed or customized by setting the label of the CSRF token field, globally of course.