I have a dynamically generated form. I use an array to retrieve all the data. Something like this:
<input type="text" class="dynamically-generated" name="ItemArray[]">
<!-- This code as many times as desired -->
Now I want these inputs to be validated in the request:
public function rules()
{
return [
'Item' => 'integer'
];
}
I need however to do this in each of the elements in the array. This would be pretty easy in plain PHP. How is this possible in Laravel? I want to do this properly
You're more than likely going to validate these inputs before storing them. So you could something like the following.
/**
* Store a new something.
*
* #param Request $request
* #return Response
*/
public function store(Request $request)
{
$this->validate($request, [
'item' => 'required|max:255'
]);
// The something is valid, store in database...
}
The one you are using above is for complex validation scenarios.
You can read more on Validation in Laravel here
Related
Just started with my first PHP laravel project, and I have this
UserRepository.php
public function validateNewEmail(Request $request, $emailName) {
$request->validate([
$emailName => ['required', 'email', 'unique:users'],
]);
}
public function validateNewPassword(Request $request, $passwordName) {
$request->validate(
// rule
[
$passwordName => ['required', 'min:8',
'regex: // some long long regex'
],
// message
[
$passwordName.".regex" => "Your new password must be more than 8 characters long, should contain at-least 1 uppercase, 1 lowercase, 1 numeric and 1 special character.",
]
);
}
// Usr Id is by itself as it might be optional in registering.
public function validateNewUsrId(Request $request, $userIdName) {
$request->validate([
$userIdName => 'required|unique:users',
]);
}
And I can then use this repository easily like this in my controller.
$this->userRepository->validateNewEmail($request, "email");
$this->userRepository->validateNewPassword($request, "password");
$this->userRepository->validateNewUsrId($request, "usr_id");
The reason is because there might be multiple controllers that use the same rules, thus putting them in one place is better
However, I realised that this method does not work because it returns the first error only. For example when both the email and password is wrong, only the email error gets returned to the frontend.
What is the best way to achieve what I want? I want to put all my validation/rules in one place to be reused.
My first solution is this: Each function returns the error MessageBag which is then joined together. I will use return $validator->errors(); to return the MessageBag which can be found here https://laravel.com/docs/8.x/validation#retrieving-the-first-error-message-for-a-field
However I slightly dislike it because then in order to check for whether an error occured, I would need to check if MessageBag is empty and then throw an error which seems a little weird.
The other way I thought of is to return the rules instead, so that I can join them in my controller and then validate all in one go. This seems better but I have to combine the error messages as well, which can be a little tricky if there are multiple (since the key is not fixed, see the code $passwordName.".regex" for example.), as I will need to update the key to each message.
The best way for me, is if I could return a validator for each function, and then use some sort of Validate::ValidateAll function? Is that possible?
How is this implemented usually?
Modern Laravel applications typically use form request validation. With this approach, Laravel handles all the validation and returns error messages automatically. Simply write your form request class, and then use it in place of Request in your controller methods:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;
class MyFormRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'email' => ['required', 'email', 'unique:users'],
'password' => [
'required',
Password::min(8)->mixedCase()->numbers()->symbols()
],
'usrid' => ['required', 'unique:users'],
];
}
}
public method store(MyFormRequest $request)
{
// $request has been validated, no further checking needed
}
Note I'm using Laravel's built-in password validation rules instead of relying on a "long long regex."
Creating extra field 'images' resource forms usually throws 'column not found' type database level error.
But I need that type of extra field on the resource forms for some business logic under the hood when the create/update form is submitted.
I tried using removeNonCreationFields method on resource to remove that field column from saving to database but does not work and still throws error.
Please note that ->hideWhenCreating() or ->readonly() is not relevant as I need to interact on that field on create/delete forms.
Is there any other way to make such situation success with extra fields? Please help. Thanks.
My solution was:
app/Nova/Post.php
/**
* #param NovaRequest $request
* #param \Illuminate\Database\Eloquent\Model $model
* #param \Illuminate\Support\Collection $fields
* #return array|void
*/
protected static function fillFields(NovaRequest $request, $model, $fields)
{
$fillFields = parent::fillFields($request, $model, $fields);
// first element should be model object
$modelObject = $fillFields[0];
// remove all extra non-database attributes from the model
unset($modelObject->to_profile_gallery);
// I am not sure it will work if we unset on $model parameter and return it
// But you should try first doing so on $model parameter and return $model
return $fillFields;
}
Then you should use two functions, one for how to save in database and another for how to retrieve that specific data from database. Use these on the extra Field.
->fillUsing(function($request, $model, $attribute, $requestAttribute){
// during creation photos are handled by Nova Resource Observer
if($model->type !== post_type_photo()) return;
// run only for update request
FilepondHelper::handleMediaFillUsingCallback(PostMediaTag::photos, true, $request, $model, $attribute, $requestAttribute); // only update
})
->resolveUsing(function($value, $resource, $attribute) use($request){
return FilepondHelper::handleMediaResolveUsingCallback(PostMediaTag::photos, $value, $resource, $attribute, $request);
}),
Hoping this will solve your issue. Thanks
Hasnat approach worked perfectly for my case.
I wanted to have one special field when creating a resource only for internal logic, which should be ignored/discarded and not related in any way to a database field.
Thanks!
I have this controller for a RESTful API I am building in Laravel Lumen which takes a relatively big amount of parameters and parses them into where queries, and data is fetched depending on if they were provided. For example,
GET /nodes?region=California
GET /nodes?ip=127.0.0.1
I am currently taking them in the constructor, building an array of the parameters (since I couldn't figure out how to get the raw get array in Lumen and it would be inconvenient because I already have other parameters there), and filtering out the null values (I am setting values to null if they are not in the query).
Now, when it comes to filtering the values each in the array, I am doing it by a foreach array. This is the cleanest way I could figure out to do it, without too much code (I don't want to make my controllers too fat.).
Is there any other way to do this cleanly, maybe with separation of functions/classes?
Here is my constructor code:
/**
* Get some values before using functions.
*
* #param Request $request Instance of request.
*/
public function __construct(Request $request)
{
$this->offset = (int) $request->input('offset', 0);
// TODO: I'm not sure how to implement this, code in question
$this->filters = [
'region' => $request->input('region', null),
'name' => $request->input('name', null),
'ip' => $request->input('ip', null)
];
$this->filters = array_filter($this->filters, function ($v) {
return !is_null($v);
});
// Set a sane SQL limit.
$this->limit = 5;
$this->request = $request;
}
And the controller code:
/**
* List all nodes.
*
* #return [string] [JSON containing list of nodes, if sorted.]
*/
public function all()
{
try {
// use filters provided
$data = Nodes::limit($this->limit)->offset($this->offset);
foreach ($this->filters as $filter => $value) {
$data->where($filter, $value);
}
$data = $data->get();
$response = $this->respond($data);
} catch (\Exception $e) {
$response = $this->respondServerError('Could not retrieve data from database.');
}
return $response;
}
So any time I have to do filtering of a resource-list in an API, here's how I do it.
First off though, before I begin, a quick tip concerning getting the Request object when you're in your controller method: If you add Request $request as a parameter for your all() function, you will have access to the $request variable there, same as your constructor. So the complete signature would be public function all(Request $request). Controller methods have the same magic dependency injection that other class constructors get in Laravel/Lumen. Alternatively, in your function you can always ask the app() function to give you an object of a specific class. Because the Request object is bound in the Container to just 'request', you can ask for the full class name, or just 'request': $request = app('request');
So once I have my request object, inside my controller method I like to go through each filter either as a group, or one-by-one, depending on how complex each filter is. Sometimes filters are complex, like a list of comma-separated IDs that need to be exploded into an array. If it's just simple string filters though, I tend to throw the list into an array and run through that.
Here's an example function to illustrate some ideas:
public function getIndex(Request $request)
{
//Create a User object to append WHERE clauses onto
$user = app('App\Models\User');
//Run through our simple text fields
foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
if ($request->has($field)) {
$user->where($field, $request->input($field));
}
}
//This field uses a LIKE match, handle it separately
if ($request->has('email')) {
$user->where('email', LIKE, '%' . $request->input('email') . '%');
}
//This field is a list of IDs
if ($request->has('id')) {
$ids = explode(',', $request->input('id'));
$user->whereIn('id', $ids);
}
//Use pagination
$users = $user->paginate(25);
/**
* Continue with the rest of response formatting below here
*/
}
You'll notice I used the paginate function to limit my results. When building an API endpoint that lists resources, you're going to want to put in your headers (my preference) or the response body information on how to get the first, previous, next, and last page of results. The Pagination feature in Laravel makes that easy, as it can construct most of the links using the links() method.
Unfortunately, you need to tell it what filter parameters were passed in the request so it can make sure it adds those to the links it generates. Otherwise you'll get links back without your filters, which doesn't do the client very much good for paging.
So here's a more complete example of recording filter parameters so they can be appended onto pagination links:
public function getIndex(Request $request)
{
//Create a User object to append WHERE clauses onto
$user = app('App\Models\User');
//List of filters we found to append to links later
$appends = [];
//Run through our simple text fields
foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
if ($request->has($field)) {
$appends[$field] = $request->input($field);
$user->where($field, $request->input($field));
}
}
//This field uses a LIKE match, handle it separately
if ($request->has('email')) {
$appends['email'] = $request->input('email');
$user->where('email', LIKE, '%' . $request->input('email') . '%');
}
//This field is a list of IDs
if ($request->has('id')) {
$appends['id'] = $request->input('id');
$ids = explode(',', $request->input('id'));
$user->whereIn('id', $ids);
}
//Use pagination
$users = $user->paginate(25);
//Make sure we append our filter parameters onto the pagination object
$users->appends($appends);
//Now calling $users->links() will return the correct links with the right filter info
/**
* Continue with the rest of response formatting below here
*/
}
Pagination documentation can be found here: https://laravel.com/docs/5.2/pagination
For an example of how pagination linking can be awesomely done, check out Github's API documentation: https://developer.github.com/v3/#pagination
In the end it's not too far off from what you were doing, conceptually. The advantage here is that you move the code into the method that needs it, instead of having it run in your constructor every single time the controller is initialized, even if a different method will be called.
Hope that helps!
Im really new to Laravel. I have manage to set up a database via the migration functionality, and now i want to renturn a table from the database as json. What im working on is kind of a rest-api-thingy. Nothing too fancy.
In my router i have a route going to /api/cases wich inits the controller for the cases. From that controller i basically just want to return a table from my database as JSON.
Router:
Route::resource('/api/cases', 'CasesController');
Controller:
class CasesController extends \BaseController {
public function index()
{
//return db table as json here
}
}
Model:
class Case extends \Eloquent {
protected $fillable = [];
}
And my database looks like this:
I have only one table, named "cases". That one has attributes like "id", "name", "title".
How would i now return that rest-like as json?
You can simply call the toJSON() method:
Case::all()->toJson();
I assume you have your Case model tested and working properly. Once that's done, you can query for all the objects in this table, convert the result to an array, and encode it as JSON.
public function index()
{
return Response::json(Case::all()->toArray());
}
I don't believe it is the job of the ORM to worry about presentation logic, and that is what JSON is. You'll aways need to cast data to various types as well as hide things and sometimes create a buffer zone to rename things safely.
You can do all of that with Fractal which I built for exactly this reason.
<?php namespace App\Transformer;
use Acme\Model\Book;
use League\Fractal\TransformerAbstract;
class BookTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* #var array
*/
protected $availableIncludes = [
'author'
];
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
],
];
}
/**
* Include Author
*
* #return League\Fractal\ItemResource
*/
public function includeAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
}
Embedding (including) stuff might be a bit more than you need right now, but it can be very handy too.
I often give talks about APIs and the dangers of trying to expose database schema directly. Unless you app is on an internal network, and only your app looks at this data, and your app will never going to change at all then interacting directly with the table is a very bad idea.
Here is my talk, which uses Laravel as an example a few times.
I have a drop down list where users can select their timezone on a form. On submit, I'm looking for the best way to validate the timezone input field using Yii rules.
I'm able to get an array of timezones in PHP using the following:
DateTimeZone::listIdentifiers(DateTimeZone::ALL)
The values are like this (an indexed key with a timezone value):
Array([0]=>'Pacific/Midway',[1]=>'Pacific/Niue',...);
My select box looks like this:
<select name="timezone" id="timezone">
<option value="Pacific/Midway">Pacific/Midway (GMT-11:00)</option>
<option value="Pacific/Niue">Pacific/Niue (GMT-11:00)</option>
...
</select>
I tried the following:
public function rules() {
return array(
array('timezone','in','range'=>DateTimeZone::listIdentifiers(DateTimeZone::ALL)),
array(timezone','required'),
);
} // rules()
But it never validates, I think because the the range is a multidimensional array and in order for range to work it needs to be just an array of values like array('value1','value2'); Is that true?
Is there a better approach?
Best way is to create custom validator class, some example (untested, tune/fix to your needs):
class CountryValidator extends CValidator
{
/**
* Validates the attribute of the object.
* If there is any error, the error message is added to the object.
* #param CModel $object the object being validated
* #param string $attribute the attribute being validated
*/
protected function validateAttribute($object,$attribute)
{
$ids = DateTimeZone::listIdentifiers(DateTimeZone::ALL);
if(!in_array($object->$attribute, $ids))
{
$this->addError($object,$attribute,'Wrong time zone selected!');
}
}
}
Then use it in your model:
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
return array(
array('timezone', 'alias.to.CountryValidator'),
);
}
This method has very important advantage: Like other validator it is reusable, and does not mess your model code.