Codeigniter 4 - REST with Auto Routing improved - php

I have a controller App/Controllers/Api/Orders.php
<?php
namespace App\Controllers\Api;
use CodeIgniter\RESTful\ResourceController;
class Orders extends ResourceController
{
protected $modelName = 'App\Models\Order';
protected $format = 'json';
public function getIndex()
{
return $this->respond($this->model->findAll());
}
public function delete($id = null)
{
return $this->respond(['test' => 123]);
}
}
When I'm trying to access /api/orders/ with GET request it works fine (so auto routing is working).
But when I'm trying to send a DELETE request: /api/orders/15 I get error 404. What am I doing wrong? The main source of confusion for me is that Codeigniter 4 RESTful documentation seems to talk about manual routes or legacy auto routing but not the improved version where you need to specify methodFunction (like getIndex).
Running php spark routes gives me the following:
+--------------+------------------+------+---------------------------------------+----------------+---------------+
| Method | Route | Name | Handler | Before Filters | After Filters |
+--------------+------------------+------+---------------------------------------+----------------+---------------+
| GET(auto) | api/orders | | \App\Controllers\Api\Orders::getIndex | | toolbar |
| DELETE(auto) | api/orders/[/..] | | \App\Controllers\Api\Orders::delete | <unknown> | <unknown> |
CodeIgniter version: 4.3.1
app/Config/Routes.php
$routes->setDefaultNamespace('App\Controllers');
$routes->setDefaultController('Auth');
$routes->setDefaultMethod('index');
$routes->setTranslateURIDashes(false);
$routes->set404Override();
$routes->get('/', 'Home::index');
app/Config/Filters.php
public array $globals = [
'before' => [
// 'honeypot',
// 'csrf',
// 'invalidchars',
],
'after' => [
'toolbar',
// 'honeypot',
// 'secureheaders',
],
];

Solution:
Beyond prefixing the Controller method with an HTTP verb i.e delete, you need to provide a 'Controller method name' as the suffix. I.e: httpVerbMethodName.
Instead of: ❌
public function delete($id = null)
{
// ...
}
Use this: ✅
public function deleteOrder($id = null)
{
// ...
}
Route endpoint:
DELETE: http://localhost:4599/api/orders/order/15
| DELETE(auto) | api/orders/order[/..] | | \App\Controllers\Api\Orders::deleteOrder | | toolbar
Reference:
URI Segments
The segments in the URL, in following with the Model-View-Controller approach, usually represent:
example.com/class/method/ID
The first segment represents the controller class that should be invoked.
The second segment represents the class method that should be called.
The third, and any additional segments, represent the ID and any variables that will be passed to the controller.
Consider this URI:
example.com/index.php/helloworld/hello/1
In the above example, when you send a HTTP request with GET method, Auto Routing would attempt to find a controller named App\Controllers\Helloworld and executes getHello() method with passing '1' as the first argument.
Note
A controller method that will be executed by Auto Routing (Improved) needs HTTP verb (get, post, put, etc.) prefix like getIndex(), postCreate().
See Auto Routing in Controllers for more info.

Related

try make route public when it has auth middleware

hello i got issue with access to few routes when they under restricted from middlewere
my route look like this
Route::group([
'middleware' => ['api','auth:sanctum'],
], function ($router) {
Route::prefix('users')->group(function ($usersRoute) {
Route::post('login', 'Api\Users\UserController#login');
Route::post('logout', 'Api\Users\UserController#logout');
Route::post('refresh', 'Api\Users\UserController#refresh');
Route::post('profile', 'Api\Users\UserController#profile');
});
Route::prefix('leads')->group(function ($leadRoute) {
// Route::get('look' , 'Api\Leads\LeadController#index');
// Route::get('look/{id}' , 'Api\Leads\LeadController#find');
// Route::post('look' , 'Api\Leads\LeadController#create');
// Route::put('look/{id}' , 'Api\Leads\LeadController#update');
// Route::delete('look/{id}' , 'Api\Leads\LeadController#archived');
// Route::put('look/{id}/view' , 'Api\Leads\LeadController#toggleView');
Route::prefix('assets')->group(function ($leadRoute) {
Route::get('/' , 'Api\Leads\AssetController#index');
Route::post('/' , 'Api\Leads\AssetController#create');
Route::get('/show/random' , 'Api\Leads\AssetController#showRandomAsset');
Route::delete('/{id}' , 'Api\Leads\AssetController#archived');
Route::delete('/autoclear' , 'Api\Leads\AssetController#autoClear');
Route::put('/{id}/show' , 'Api\Leads\AssetController#toggleShow');
});
});
});
now i have two controllers that the contractor look like this
class UserController extends BaseAPIController
{
/**
* Create a new AuthController instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth:sanctum', ['except' => ['login']]);
}
when i check the route list
api,auth:sanctum |
| | POST | api/users/login | | App\Http\Controllers\Api\Users\UserController#login | api,auth:sanctum |
| | POST | api/users/logout | | App\Http\Controllers\Api\Users\UserController#logout | api,auth:sanctum |
| | POST | api/users/profile | | App\Http\Controllers\Api\Users\UserController#profile | api,auth:sanctum |
| | POST | api/users/refresh | | App\Http\Controllers\Api\Users\UserController#refresh | api,auth:sanctum
what i try is free login route free for all but i get
{
"message": "Unauthenticated."
}
i kinda lost please help
when I check the doc of laravel, I found that:
$this->middleware('subscribed')->except('store');
the second param for function middleware is using for options, so how about try:
$this->middleware('auth:sanctum')->except('login');
From your code, you're including the middleware twice and that's not right.
You should remove the group middleware in the routes file since you're already injecting the auth middleware in the constructor method.
Remove
Route::group([
'middleware' => ['api','auth:sanctum'],
], function ($router) {
After this your code should work as intended because auth:sanctum middleware would be called only once in your controller every time it is instantiated and will exclude the login route.

add uri parameter to Route name in laravel

I'm working on a Laravel project, and I made a simple CRUD system, but I have a small problem
to generate the URL system in my project, I made a Route::macro
and add it to AppServiceProvider:
Route::macro('crud', function () {
Route::group([
], function () {
// Route::resource('', 'CrudController');
Route::get('{model}', 'CrudController#index');
Route::get('{model}/create', 'CrudController#create');
Route::post('{model}', 'CrudController#store'); /** << post **/
Route::get('{model}/{id}', 'CrudController#show');
Route::get('{model}/{id}/edit', 'CrudController#edit');
Route::match(['PUT', 'PATCH'],'{model}/{id}', 'CrudController#update'); /** << post **/
Route::delete('{model}/{id}', 'CrudController#destroy'); /** << post **/
});
});
this works great so far, but the issue is I need to use ->name() with it, and adding the $model parameter to it!
example of what I'm trying to do:
Route::get('{model}', 'CrudController#index')->name('{model}.index');
is it possible?, Thanks in advance
You can get all models names and loop through them and add the model name to the route name prefix at runtime
loop (model in models)
Route::get("{model}","Atcion")->name("{model}.index")
endloop
I hope my answare helps you in your project
Here in this example you can loop over of some numbers and dynamically create some routes:
for ($i = 0; $i < 5; $i++) {
Route::get('test/' . $i, 'Controller#test_' . $i)->name('test.' . $i);
}
You can check that all added routes with "php artisan route:list".
I don't recommend you to do that, but for your case you can somewhere in routes.php define array like this, and loop over on that:
$models = ['user', 'owner', 'admin'];
foreach ($models as $model) {
Route::get($model, 'CrudController#index')->name($model . '.index');
}
Or you can define that array in configs (for example in "config/app.php") like:
'models' => ['user', 'owner', 'admin'];
And in routes.php you can just retrieve that with this (don't forget to run "php artisan config:cache" after changing app.php):
$models = config('app.models');
// foreach loop
You can prefix the name to a group of routes for the model
From the docs
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Route::macro('crud', function () {
Route::name('{model}.')->group(function () {
Route::get('{model}', 'CrudController#index')->name('index');
Route::get('{model}/create', 'CrudController#create')->name('create');
Route::post('{model}', 'CrudController#store')->name('store');
Route::get('{model}/{id}', 'CrudController#show')->name('show');
Route::get('{model}/{id}/edit', 'CrudController#edit')->name('edit');
Route::match(['PUT', 'PATCH'], '{model}/{id}', 'CrudController#update')->name('update');
Route::delete('{model}/{id}', 'CrudController#destroy')->name('destroy');
});
});
}
}
Result
+-----------+----------------------------+-----------------+------------------------------------------------------------+
| Method | URI | Name | Action |
+-----------+----------------------------+-----------------+------------------------------------------------------------+
| GET|HEAD | {model} | {model}.index | App\Http\Controllers\CrudController#index |
| POST | {model} | {model}.store | App\Http\Controllers\CrudController#store |
| GET|HEAD | {model}/create | {model}.create | App\Http\Controllers\CrudController#create |
| GET|HEAD | {model}/{id} | {model}.show | App\Http\Controllers\CrudController#show |
| PUT|PATCH | {model}/{id} | {model}.update | App\Http\Controllers\CrudController#update |
| DELETE | {model}/{id} | {model}.destroy | App\Http\Controllers\CrudController#destroy |
| GET|HEAD | {model}/{id}/edit | {model}.edit | App\Http\Controllers\CrudController#edit |
+-----------+----------------------------+-----------------+------------------------------------------------------------+
Now you can access example.com/user/create and example.com/product/create and they both take to the same controller method
I hope this helps

Laravel 4.2 : testing with assertRedirectedToRoute fails

I was wondering if anyone else came across this problem. I'm going through Jeffrey Way's book on testing in Laravel and I'm on the chapter which explains how to test controllers.
When I follow the examples from the book - I get the message:
Failed asserting that Illuminate\Http\Response Object (...) is an
instance of class "Illuminate\Http\RedirectResponse".
My test is as follow:
public function testStoreFails()
{
$input = ['title' => ''];
$this->mock
->shouldReceive('create')
->once()
->with($input);
$this->app->instance('Post', $this->mock);
$this->post('posts', $input);
$this->assertRedirectedToRoute('posts.create');
$this->assertSessionHasErrors(['title']);
}
And the very simple method in the controller (just to test this specific scenario):
public function create()
{
$input = Input::all();
$validator = Validator::make($input, ['title' => 'required']);
if ($validator->fails()) {
return Redirect::route('posts.create')
->withInput()
->withErrors($validator->messages());
}
$this->post->create($input);
return Redirect::route('posts.index')
->with('flash', 'Your post has been created!');
}
From what I can see the AssertionsTrait::assertRedirectedTo checks for instance of Illuminate\Http\RedirectResponse
/**
* Assert whether the client was redirected to a given URI.
*
* #param string $uri
* #param array $with
* #return void
*/
public function assertRedirectedTo($uri, $with = array())
{
$response = $this->client->getResponse();
$this->assertInstanceOf('Illuminate\Http\RedirectResponse', $response);
$this->assertEquals($this->app['url']->to($uri), $response->headers->get('Location'));
$this->assertSessionHasAll($with);
}
/**
* Assert whether the client was redirected to a given route.
*
* #param string $name
* #param array $parameters
* #param array $with
* #return void
*/
public function assertRedirectedToRoute($name, $parameters = array(), $with = array())
{
$this->assertRedirectedTo($this->app['url']->route($name, $parameters), $with);
}
which should work just fine as the Redirect facade resolves to the Illuminate\Routing\Redirector and its route() method calls createRedirect(), which returns the instance of the Illuminate\Http\RedirectResponse - so not quite sure what's causing it.
UPDATE:
Just checked the code again and it looks like the problem is within AssertionsTrait::assertRedirectedTo() method. The call to $this->client->getResponse() returns instance of Illuminate\Http\Response instead of Illuminate\Http\RedirectResponse - hence the $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $response) call fails. But I'm still not sure why - I'm extending the TestCase which is suppose to take care of all environment setup etc. Any idea?
Posting comment as answer:
Since you're working with a resourceful object, Laravel automatically creates some routes for you, namely:
+-----------------------------+---------------+-------------------------+
| URI | Name | Action |
+-----------------------------+---------------+-------------------------+
| GET|HEAD posts | posts.index | PostsController#index |
| GET|HEAD posts/create | posts.create | PostsController#create |
| POST posts | posts.store | PostsController#store |
| GET|HEAD posts/{posts} | posts.show | PostsController#show |
| GET|HEAD posts/{posts}/edit | posts.edit | PostsController#edit |
| PUT posts/{posts} | posts.update | PostsController#update |
| PATCH posts/{posts} | | PostsController#update |
| DELETE posts/{posts} | posts.destroy | PostsController#destroy |
+-----------------------------+---------------+-------------------------+
As you can see, a POST request to /posts (as you do it in your test) triggers the store() method on your PostsController, not the create() method which you assumed to be under test.
create and store as well as edit and update can be confusing sometimes. Here's the difference:
create() - show the form for creating a new resource
store() - actually create the resource from the posted data
edit($id) - show the form for editing the specified resource
update($id) - actually update the resource with the posted data

laravel 4 Route::controller() method returns NotFoundHttpException

I am trying to route to a RESTful controller using the following in app/routes.php:
Route::controller('register', 'RegisterController');
Route::get('/', 'HomeController#showWelcome');
In my app/controllers/RegisterController.php file I have added the following:
<?php
class RegisterController extends BaseController
{
public function getRegister()
{
return View::make('registration');
}
public function postRegister()
{
$data = Input::all();
$rules = array(
'first_name' => array('alpha', 'min:3'),
'last_name' => array('alpha', 'min:3'),
'company_name' => array('alpha_num'),
'phone_number' => 'regex:[0-9()\-]'
);
$validator = Validator::make($data, $rules);
if ($validator->passes()) {
return 'Data was saved.';
}
return Redirect::to('register')->withErrors($validator);
}
}
I am getting the following error:
Symfony \ Component \ HttpKernel \ Exception \ NotFoundHttpException
When I run php artisan routes in terminal I get:
+--------+--------------------------------------------------+------+----------------------------+----------------+---------------+
| Domain | URI | Name | Action | Before Filters | After Filters |
+--------+--------------------------------------------------+------+----------------------------+----------------+---------------+
| | GET /register/register/{v1}/{v2}/{v3}/{v4}/{v5} | | Register#getRegister | | |
| | POST /register/register/{v1}/{v2}/{v3}/{v4}/{v5} | | Register#postRegister | | |
| | GET /register/{_missing} | | Register#missingMethod | | |
| | GET / | | HomeController#showWelcome | | |
+--------+--------------------------------------------------+------+----------------------------+----------------+---------------+
I don't understand why register is showing twice in the URI and the second GET action is missing and why I am getting this error.
If you are using RESTful API, the best way is in your route,
Route::resource('register', 'RegisterController');
And change your public function getRegister() to public function index() and public function postRegister() to public function store()
Then the index() can be access using GET http://localhost/laravel/register and the store() using POST http://localhost/laravel/register
Chaneg the http://localhost/laravel/ with yours.
And the same way the update($id) is used for update and destroy($id) is used for delete
Route::controller('register', 'RegisterController');
This will also work if you change it
Route::controller('/', 'RegisterController');

Passing parameter to controller from route in laravel

THIS IS A QUESTION FOR LARAVEL 3
Given the following route
Route::get('groups/(:any)', array('as' => 'group', 'uses' => 'groups#show'));
And the URL I would like to use,
http://www.example.com/groups/1
I would like to be able to use the (:any) value in my controller.
My controller looks like
class Groups_Controller extends Base_Controller {
public $restful = true;
public function get_show($groupID) {
return 'I am group id ' . $groupID;
}
}
How is this possible to do? I have tried a few things including the following
Route::get('groups/(:any)', array('as' => 'group', 'uses' => 'groups#show((:1))'));
but it did not work.
UPDATE
Anytime I try to pass in the arguments as show above i get a 404 error.
Thanks for the help!
You don't need anything special for adding paramaters. Just like you had it.
Route::get('groups/(:any)', array('as' => 'group', 'uses' => 'groups#show'));
class Groups_Controller extends Base_Controller {
public $restful = true;
public function get_show($groupID) {
return 'I am group id ' . $groupID;
}
}
This is what you need in 1 line of code.
Route::get('/groups/{groupId}', 'GroupsController#getShow');
Suggestion: Use CamelCase as opposed to underscores, try & follow PSR-* guidelines.
Hope it helps.
You can add them like this
Route::get('company/{name}', 'PublicareaController#companydetails');
$ php artisan route:list
+--------+--------------------------------+----------------------------+-- -----------------+----------------------------------------------------+--------- ---+
| Domain | Method | URI | Name | Action | Middleware |
+--------+--------------------------------+----------------------------+-------------------+----------------------------------------------------+------------+
| | GET|HEAD | / |
| | GET | campaign/showtakeup/{id} | showtakeup | App\Http\Controllers\campaignController#showtakeup | auth | |
routes.php
Route::get('campaign/showtakeup/{id}', ['uses' =>'campaignController#showtakeup'])->name('showtakeup');
campaign.showtakeup.blade.php
#foreach($campaign as $campaigns)
//route parameters; you may pass them as the second argument to the method:
{{ $campaigns->name }}
#endforeach
Hope this solves your problem.
Thanks

Categories