i followed this tutorial to setup a CRUD example. But i can't get it to work and i don't know why. here is my code
SomeitemCrudController.php
<?php
namespace App\Http\Controllers\Admin;
use Backpack\CRUD\app\Http\Controllers\CrudController;
// VALIDATION: change the requests to match your own file names if you need form validation
use App\Http\Requests\SomeitemRequest as StoreRequest;
use App\Http\Requests\SomeitemRequest as UpdateRequest;
class SomeitemCrudController extends CrudController
{
public function setUp()
{
/*
|--------------------------------------------------------------------------
| BASIC CRUD INFORMATION
|--------------------------------------------------------------------------
*/
$this->crud->setModel("App\Models\Someitem");
$this->crud->setRoute("admin/someitem");
$this->crud->setEntityNameStrings('someitem', 'someitems');
/*
|--------------------------------------------------------------------------
| BASIC CRUD INFORMATION
|--------------------------------------------------------------------------
*/
$this->crud->setFromDb();
// $this->crud->setColumns(['nama']);
$this->crud->addField([
'nama' => 'Nama',
'keterangan' => 'Keterangan',
'harga' => 'Harga'
]);
}
public function store(StoreRequest $request)
{
$redirect_location = parent::storeCrud();
return $redirect_location;
}
public function update(UpdateRequest $request)
{
$redirect_location = parent::updateCrud();
return $redirect_location;
}
}
the model, Someitem.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Backpack\CRUD\CrudTrait;
class SomeItem extends Model
{
use CrudTrait;
//
protected $table = 'someitem';
protected $fillable = ['nama', 'keterangan', 'harga'];
public $timestamps = true;
}
the request, SomeitemRequest.php
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
class SomeitemRequest extends \Backpack\CRUD\app\Http\Requests\CrudRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return \Auth::check();
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
// 'name' => 'required|min:5|max:255'
];
}
/**
* Get the validation attributes that apply to the request.
*
* #return array
*/
public function attributes()
{
return [
//
];
}
/**
* Get the validation messages that apply to the request.
*
* #return array
*/
public function messages()
{
return [
//
];
}
}
and then, the routes
<?php
Route::group([
'prefix' => config('backpack.base.route_prefix', 'admin'),
'middleware' => ['admin'],
'namespace' => 'Admin'
], function() {
CRUD::resource('pelayanan', 'Admin\PelayananCrudController');
Route::get('/test', function () {
return view('welcome');
});
});
i can access http://localhost/myapp/public/admin/test successfully, but i can't access http://localhost/myapp/public/admin/someitem , it always return Error 500
i'm new to laravel (and PHP), any helps is appreciated. thanks!
I want to make my answer very detailed, so that's why I've started it from the beginning. The version for backpack CRUD I use is ^3.2.
look at this file your-project/vendor/backpack/crud/src/CrudServiceProvider.php
Yes I know that class name is not CRUD but CrudServiceProvider. That is because they have register method in CrudServiceProvider and in it they do $loader->alias('CRUD', \Backpack\CRUD\CrudServiceProvider::class); which makes CRUD class alias to CrudServiceProvider. To know more about how it works inside read through Laravel Service Providers, Service Container and Facades.
So we've figured out that CRUD::resource() actually means CrudServiceProvider::resource(), so get to this method, there you will find only one line return new CrudRouter($name, $controller, $options); as far as I know (if I'm wrong correct me) it's called Factory pattern/method. So go to CrudRouter constructor (Github). Read through it. So the basic thing it does is instead of this
CRUD::resource('pelayanan', 'Admin\PelayananCrudController');
it will put something like this
Route::post('pelayanan/search', [
'as' => 'crud.pelayanan.search',
'uses' => 'Admin\PelayananCrudController#search',
]);
Route::get('pelayanan/reorder', [
'as' => 'crud.pelayanan.reorder',
'uses' => 'Admin\PelayananCrudController#reorder',
]);
Route::post('pelayanan/reorder', [
'as' => 'crud.pelayanan.save.reorder',
'uses' => 'Admin\PelayananCrudController#saveReorder',
]);
Route::get('pelayanan/{id}/details', [
'as' => 'crud.pelayanan.showDetailsRow',
'uses' => 'Admin\PelayananCrudController#showDetailsRow',
]);
Route::get('pelayanan/{id}/translate/{lang}', [
'as' => 'crud.pelayanan.translateItem',
'uses' => 'Admin\PelayananCrudController#translateItem',
]);
Route::get('pelayanan/{id}/revisions', [
'as' => 'crud.pelayanan.listRevisions',
'uses' => 'Admin\PelayananCrudController#listRevisions',
]);
Route::post('pelayanan/{id}/revisions/{revisionId}/restore', [
'as' => 'crud.pelayanan.restoreRevision',
'uses' => 'Admin\PelayananCrudController#restoreRevision',
]);
You said that you can't acces you public/admin/someitem and that's true, because you don't have route for it (actually check routes in cli with artisan route:list). But Backpack developers made one thing (don't actually know why) they put extra routes in destructor method by calling
`Route::resource($this->name, $this->controller, $options_with_default_route_names);`.
And by it should actually work, if it's not, check how index (CrudController) behaves.
One more thing maybe you forgot to php artisan vendor:publish --provider="Backpack\Base\BaseServiceProvider" and you don't have view files and that's why you get 500 error.
If you have more questions, just ask.
Related
This described in the class Laravel\Fortify\Http\Requests\LoginRequest
I want to add one more validation line
namespace Laravel\Fortify\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Laravel\Fortify\Fortify;
class LoginRequest extends FormRequest
{
/..
public function rules()
{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
];
}
}
But I cannot do the customizations in the vendor.
My question is how to do this?
Just to add to the comment above, it's a bad idea to make changes in the vendor folder as stated earlier. For one, any code pushes to a repository will not reflect these changes (unless you modify the ignore file).
For Laravel/Fortify adding new fields and changing the default validation rules, even the password requirements is very straightforward. It's not clear to me what your requirement is, but it might be easier to simply use a Validator. That is what Fortify uses as well. For example, Fortify publishes two files:
App\Actions\Fortify\CreateNewUser.php
App\Actions\Fortify\PasswordValidationRules.php
To add a new validation rule for a field, simply add it the CreateNewUser.php under the Validator::make method that Fortify itself is using. You can follow the same logic in your custom implementation. For example to add a firstname field, modify as follows:
Validator::make($input, [
'firstname' => ['required', 'string', 'max:255'],
])->validate();
return User::create([
'firstname' => $input['firstname'],
]);
You can add as many field as you want. To change the password requirements, make changes to the passwordRules() function in the PasswordValidationRules.php file as follows:
protected function passwordRules()
{
return ['required',
'string',
(new Password)->requireUppercase()
->length(10)
->requireNumeric()
->requireSpecialCharacter(),
'confirmed'];
}
All this info can be found at the official docs https://jetstream.laravel.com/1.x/features/authentication.html#password-validation-rules
In short, I solved the problem like this
copy vendor\laravel\fortify\src\Http\Controllers\AuthenticatedSessionController.php to
app\Http\Controllers\Auth\LoginController.php (change namespace and class name)
copy vendor\laravel\fortify\src\Http\Requests\LoginRequest.php to app\Http\Requests\LoginFormRequest.php (change namespace and class name)
add new route in routes/web.php
use App\Http\Controllers\Auth\LoginController;
//
Route::post('/login', [LoginController::class, 'store'])
->middleware(array_filter([
'guest',
$limiter ? 'throttle:'.$limiter : null,
]));
in LoginController changed LoginRequest to LoginFormRequest and
public function store(LoginFormRequest $request)
{
return $this->loginPipeline($request)->then(function ($request) {
return app(LoginResponse::class);
});
}
in LoginFormRequest add my new rule(s)
public function rules()
{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
'myNewRule' => 'required|string',
];
}
Here's how you can use your own validation rules when authenticating with Fortify:
Create a file LoginRequest.php in App\Http\Requests that extends Fortify's LoginRequest.php class
I would just copy that class and update the namespace:
namespace App\Http\Requests;
use Laravel\Fortify\Http\Requests\LoginRequest as FortifyLoginRequest;
use Laravel\Fortify\Fortify;
class LoginRequest extends FortifyLoginRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
Fortify::username() => 'required|string',
'password' => 'required|string',
'myAttribute' => 'rules' // Customizations...
];
}
}
Add a binding in your AppServiceProviders.php boot method to resolve Fortify's class with your own:
public function boot()
{
$this->app->bind('Laravel\Fortify\Http\Requests\LoginRequest', \App\Http\Requests\LoginRequest::class);
}
I've found that page: https://dev.to/aibnuhibban/login-customization-in-laravel-8-2gc8
Go to vendor > laravel > fortify > src > Rule > Password.php
There you can change those config.
Request class
class LoginRequest extends Request
{
public function authorize() {
return true;
}
public function rules() {
return [
'EmailAddress' => 'required',
'Password' => 'required',
];
}
public function messages() {
return [
"EmailAddress.required" => trans("login.RequiredEmailAddress"),
"Password.required" => trans("login.RequiredPassword")
];
}
}
Route
Route::post('/AuthenticateUser',
array(
'uses' => 'API\Login\apiLoginController#AuthenticateUser',
'as' => 'AuthenticateUser'
)
);
Controller Action Method
I have a controller, I did so far for request class only to validate the input parameters. below is the action method
public function AuthenticateUser(LoginRequest $request) {
dd("Hello");
}
Url
localhost:85/Laravel/public/api/v1/AuthenticateUser
I am using Postman Chrome extension to test the Url. So, as we can see that in the Request class both Email Address and the password are required parameters.
When I pass both parameters value. there is not issue and everything works. When I keep the Email Address value empty...I got 404 error and here is the screenshot.
Am I missing something to get rid of 404 error when Email address is not given? I am expecting an error message to enter Email Address
Below is the working state when I pass both email and password
Solution 1:
I managed to get rid of the 404 and return a 422 by adding the following header in the request:
accept:application/json
This is not really a bug in Laravel as Taylor pointed out but a way to differentiate if it is an AJAX/API request or not.
Solution 2:
Alternatively, if you don't want the client to specify that header, you can create a middleware that will add the header accept:application/json on every API requests. Here's how:
Create a new middleware: app/Http/Middleware/ForceJsonResponse.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ForceJsonResponse
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
In /app/Http/Kernel.php, inside $middlewareGroups.api, specify the namespace to your newly created middleware:
protected $middlewareGroups = [
'web' => [...],
'api' => [
[...]
\App\Http\Middleware\ForceJsonResponse::class,
],
];
Finally got it working by changing the request class like below.
class LoginRequest extends Request
{
public function wantsJson() {
return true;
}
public function authorize() {
return true;
}
public function rules() {
return [
'EmailAddress' => 'required',
'Password' => 'required',
];
}
public function messages() {
return [
"EmailAddress.required" => trans("login.RequiredEmailAddress"),
"Password.required" => trans("login.RequiredPassword")
];
}
}
just added below code.
public function wantsJson() {
return true;
}
It is because you are validating directly on route handling and not matching throughs NotFoundException. You need to pass the Request to your Controller as is and do:
$this->validate($request, [
'EmailAddress' => 'required|email',
'Password' => 'required',
]);
I use laravel custom form request with command php artisan make:request AddressBookRequest
And use that request in my controller like :
public function add_address_book($lang,$user_id,AddressBookRequest $request){
dd($request);
}
And when i run api route laravel shows :
NotFoundHttpException in RouteCollection.php line 161:
But when i change that AddressBookRequest to Request like :
public function add_address_book($lang,$user_id,Request $request){
dd($request);
}
Api works fine
AddressBookRequest :
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
class AddressBookRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'title' => 'required',
'address' => 'required',
'latitude' => 'required',
'longitude' => 'required'
];
}
public function messages()
{
return [
'title.required' => trans('address_book.title_required'),
'address.required' => trans('address_book.address_required'),
'latitude.required' => trans('address_book.latitude_required'),
'longitude.required' => trans('address_book.longitude_required'),
];
}
}
AddressBookController usecases:
<?php namespace App\Http\Aggregate\Address_book\Controller\v1_0;
use App\Http\Requests\AddressBookRequest;
use Illuminate\Routing\Controller as BaseController;
use EventHomes\Api\ApiController;
use JWTAuth;
class AddressBookController extends BaseController
{
And route :
Route::group(['namespace' => 'Aggregate\Address_book\Controller\v1_0', 'middleware' => 'jwt.auth', 'prefix' => 'api/v1.0/{lang}'], function () {
Route::post('customer/{id}/address_book', 'AddressBookController#add_address_book');
});
How can i fix it to use custom request?
Any help will be appreciated
You should add this line to the top of the controller:
use App\Http\Requests\AddressBookRequest;
Also, make sure authorize() method inside custom request class returns true:
public function authorize()
{
return true;
}
I fix it by adding :
use Illuminate\Foundation\Http\FormRequest;
use EventHomes\Api\ApiController;
abstract class Request extends FormRequest
{
use ApiController;
public function response(array $errors)
{
foreach($errors as $key=>$error)
{
return $this->respondUnprocessable(1004,'validation',$errors[$key][0]);
}
}
}
In requst.php
Some service makes HTTP request to my site and passes some input. This input has a little bit wrong structure for me, so I'm trying to modify it.
I made a middleware and attached this middleware to my route. The handle method looks like this:
public function handle($request, Closure $next)
{
$input = $request->all();
// Input modification
$request->replace($input);
\Log::info($request->all()); // Shows modified request
return $next($request);
}
However in my controller I got old input. Also I'm a little bit confused since I also use FormRequest, and as I realize these two requests are different entities. Then how can I modify the input in the middleware?
I don't know what's the exact problem in your case but I'll show you what I did to make it work and it might solve your problem:
app/Http/Middleware/TestMiddleware.php
<?php namespace App\Http\Middleware;
use Closure;
class TestMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$input = $request->all();
if (isset($input['mod'])) {
list($input['int'], $input['text']) = explode('-', $input['mod']);
unset($input['mod']);
// Input modification
$request->replace($input);
\Log::info($request->all()); // Shows modified request
}
return $next($request);
}
}
app/Http/Kernel.php
protected $middleware = [
'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode',
'Illuminate\Cookie\Middleware\EncryptCookies',
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse',
'Illuminate\Session\Middleware\StartSession',
'Illuminate\View\Middleware\ShareErrorsFromSession',
'App\Http\Middleware\VerifyCsrfToken',
Middleware\TestMiddleware::class, // this line added
];
app/Http/routes.php
Route::get('/test', ['uses' => 'TestController#index']);
app/Http/Requests/SampleRequest.php
<?php namespace App\Http\Requests;
class SampleRequest extends Request
{
public function rules()
{
return [
'int' =>
[
'required',
'integer'
],
'text' => [
'max: 5',
]
];
}
}
app/Http/Controllers/TestController.php
<?php namespace App\Http\Controllers;
use App\Http\Requests;
class TestController extends \Illuminate\Routing\Controller
{
public function index(Requests\SampleRequest $request)
{
dd($request->all());
}
}
In console I've run composer dump-autoload.
Now when I run the following url:
http://testproject.app/test?mod=23-tav
I'm getting in controller from dd:
array:2 [▼
"text" => "tav"
"int" => "23"
]
as expected and when I run for example http://testproject.app/test?mod=abc-tav I'm being redirected to mainpage in my case because data doesn't pass validation from SampleRequest (int is not integer)
I've got the following routes:
Route::controllers([
'auth' => 'Auth\AuthController',
'password' => 'Auth\PasswordController',
'admin' => 'AdminController',
'site' => 'SiteController'
]);
And then I've got the following method in the SiteController:
/**
* Get site details and pass to view
*
* #param Site $site
* #return mixed
* #internal param $site_id
*/
public function getDetails( Site $site )
{
return $site;
}
When I go to the URL site.com/site/details/13 it doesn't return the site object.
I've added $router->model( 'one', 'App\Site' ); into the RouteServiceProvider and it works, but what if later down the road I want to add another controller like this but use it for jobs, and use the getDetails method again and pass through the App\Job object? It will automatically send the App\Site model instead.
So is there a way I can prevent this from happening?
My limited knowledge of Laravel tells me you can't have a model/object as parameter in your route controller functions, and that you don't need something like $router->model( 'one', 'App\Site' ); to do this.
I'm assuming you'd want to do something like this:
As for your routes:
Route::controllers([
'auth' => 'Auth\AuthController',
'password' => 'Auth\PasswordController',
'admin' => 'AdminController',
'site' => 'SiteController',
'jobs' => 'JobController',
]);
In your SiteController:
use Illuminate\Http\Request;
use App/Site; //replace with namespace of model
public function getDetails($id)
{
//code for fetching the site object, depends on how your structure is,
//like $site = App\Site::find($id); etc
return $site;
}
Similarly, your JobController will be something like:
use Illuminate\Http\Request;
use App/Job; //replace with namespace of model
public function getDetails($id)
{
//code for fetching the job object, depends on how your structure is,
//like $job = App\Job::find($id); etc
return $job;
}
Look here: Laravel Docs - Implicit Controllers