Fortify has requireUppercase rule, but no requireLowercase, is it possible to somehow still have a rule that forces lower case in the password?
*Edit: Is it ok to use Laravel's password rule object instead of Fortify's one in PasswordValidationRules.php?
use Illuminate\Validation\Rules\Password; instead of use Laravel\Fortify\Rules\Password;
and then use it:
// app/Actions/Fortify/PasswordValidationRules.php
protected function passwordRules()
{
return [Password::min(8)->mixedCase()];
}
Or that might cause issues?
These are the methods that the Password object includes, you can skip some and add news:
Example:
$request->validate([
'password' => [
'required',
Password::min(12)
->letters()
->numbers()
->symbols(),
],
]);
Require at least 8 characters...
Password::min(8)
Require at least one letter...
Password::min(8)->letters()
Require at least one uppercase and one lowercase letter...
Password::min(8)->mixedCase()
Require at least one number...
Password::min(8)->numbers()
Require at least one symbol...
Password::min(8)->symbols()
You can use either Password class. The way the classes are used here is for nothing but validation, so there shouldn't be any lingering effects.
They will give you slightly different error messaging. The big difference (as you mentioned) is that the Fortify version doesn't have a requireLowercase() method.
Still, here are full examples of using each in PasswordValidationRules.php
Using the Fortify Password class.
<?php
namespace App\Actions\Fortify;
use Laravel\Fortify\Rules\Password;
trait PasswordValidationRules
{
/**
* Get the validation rules used to validate passwords.
*
* #return array
*/
protected function passwordRules()
{
return [
'required',
'string',
(new Password())
->requireUppercase()
->requireNumeric()
->requireSpecialCharacter(),
'confirmed',
];
}
}
and here is an example using the Illuminate\Validation\Rules\Password class.
<?php
namespace App\Actions\Fortify;
use Illuminate\Validation\Rules\Password;
trait PasswordValidationRules
{
/**
* Get the validation rules used to validate passwords.
*
* #return array
*/
protected function passwordRules()
{
return [
'required',
'string',
Password::min(8)
->mixedCase()
->numbers()
->symbols(),
'confirmed',
];
}
}
You could also create a class that extends Fortify's Password Class and add a requireLowercase method in nearly the same manner that requireUppercase is implemented.
Related
I am trying to update a row in the pages table.
The slug must be unique in the pages table on the slug and app_id field combined.
i.e. there can be multiple slugs entitled 'this-is-my-slug' but they must have unique app_id.
Therefore I have found that formula for the unique rule is:
unique:table,column,except,idColumn,extraColumn,extraColumnValue
I have an update method and getValidationRules method.
public function update($resource,$id,$request){
$app_id=22;
$request->validate(
$this->getValidationRules($id,$app_id)
);
// ...store
}
When I test for just a unique slug the following works:
public function getValidationRules($id,$app_id){
return [
'title'=> 'required',
'slug'=> 'required|unique:pages,slug,'.$id
];
}
However, when I try and add the app_id into the validation rules it returns server error.
public function getValidationRules($id,$app_id){
return [
'title'=> 'required',
'slug'=> 'required|unique:pages,slug,'.$id.',app_id,'.$app_id
];
}
I have also tried to use the Rule facade, but that also returns server error. Infact I can't even get that working for just the ignore id!
public function getValidationRules($id,$app_id){
return [
'title'=> 'required',
'slug'=> [Rule::unique('pages','slug')->where('app_id',$app_id)->ignore($id)]
];
}
Any help is much appreciated :)
Thanks for the respsonses. It turned out a couple of things were wrong.
Firstly if you want to use the Rule facade for the validation rules, make sure you've included it:
use Illuminate\Validation\Rule;
The other method for defining the validation rule seems to be limited to the following pattern:
unique:table,column,except,idColumn
The blog post that I read that showed you could add additional columns was for laravel 7, so i guess that is no longer the case for laravel 9.
Thanks for your responses and help in the chat!
I recommend you to add your own custom rule.
First run artisan make:rule SlugWithUniqueAppIdRule
This will create new file/class inside App\Rules called SlugWIthUniqueAppRule.php.
Next inside, lets add your custom rule and message when error occured.
public function passes($attribute, $value)
{
// I assume you use model Page for table pages
$app_id = request()->id;
$pageExists = Page::query()
->where('slug', $slug)
->where('app_id', $app_id)
->exists();
return !$pageExists;
}
public function message()
{
return 'The slug must have unique app id.';
}
Than you can use it inside your validation.
return [
'title'=> 'required|string',
'slug' => new SlugWithUniqueAppIdRule(),
];
You can try it again and adjust this custom rule according to your needs.
Bonus:
I recommend to move your form request into separate class.
Run artisan make:request UpdateSlugAppRequest
And check this newly made file in App\Http\Requests.
This request class by default will consists of 2 public methods : authorize() and rules().
Change authorize to return true, or otherwise this route can not be accessed.
Move your rules array from controller into rules().
public function rules()
{
return [
'title'=> 'required|string',
'slug' => new SlugWithUniqueAppIdRule(),
];
}
To use it inside your controller:
public function update(UpdateSlugAppRequest $request, $resource, $id){
// this will return validated inputs in array format
$validated = $request->validated();
// ...store process , move to a ServiceClass
}
This will make your controller a lot slimmer.
I have a web route for manufactures module (Backed route that handle resource)
Route::resource('/admin/manufactures', App\Http\Controllers\Back\ManufacturerController::class);
I have create a create a ManufacturerRequest with a simple rule name=>required
and i want to use Laravel Test to test the resource (CRUD)
In my controller the store method is as follow
public function store(ManufacturerRequest $request)
{
//db
$request->validate();
Manufacturer::create($request->all());
}
I have a simple test
$response = $this->post('/admin/manufactures' ,
[
'_token' => csrf_token(),
'name' => 'test'
]);
$response->assertStatus(200);
which is return 403, but besides that the store Method takes ManufacturerRequest object that handles validation, and in the case of the test i pass an array because it only accepts array.
So how can I create a Test that "simulate" form and pass request to controller in order to test validation and CRUD
What you want to do is very easy and is also explained on the Documentation, it is very important that you fully read the documentation so you have a rough idea of what you can do with the framework, specially because you are new with it.
As you did not specify which version you are using, I will be using Laravel 8, but it is roughly the same across the board.
Based on your code:
Resource route
Route::resource('/admin/manufactures', ManufacturerController::class);
Controller
public function store(ManufacturerRequest $request)
{
//db
$request->validate();
Manufacturer::create($request->all());
}
You need to change your controller to:
public function store(ManufacturerRequest $request)
{
//db
Manufacturer::create($request->all());
}
Yes, just remove the $request->validate(); as the framework will automatically resolve the FormRequest and authorize and validate. If you read part of the validate explanation you will see this:
So, how are the validation rules evaluated? All you need to do is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic.
So, when the first line of the controller is run, it means the FormRequest passed the authorization check and validated the input.
What you can also update on your controller is:
public function store(ManufacturerRequest $request)
{
//db
Manufacturer::create($request->validated());
}
See I have changed $request->all() with $request->validated(), validated will only return the fields you have a key on the FormRequest's rules, if you use all you will be passing everything you have on the request (also passing non-validated data and that is not good).
Before you try anything, I recommend you read my answer on this post, so you can have a clearer picture about testing.
So, you are getting a 403 maybe because you have a middleware asking for you to be logged in, and you did not use $this->actingAs().
Just because you did not share the FormRequest rules, I will just give a super small example. If you have a this rule inside:
'name' => ['required', 'string'],
What you can do to test that is:
public function test_manufacturer_is_created(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->post('/admin/manufactures', ['name' => $name = 'Manufacturer 1']);
$response->assertSuccessful();
$this->assertDatabaseHas(
'manufacturers',
[
'name' => $name
]
);
}
/**
* #depends test_manufacturer_is_created
*/
public function test_unauthorized_error_is_thrown_when_the_user_is_not_logged_in(): void
{
$response = $this->post('/admin/manufactures', ['name' => 'Manufacturer 1']);
$response->assertUnauthorized();
}
/**
* #depends test_manufacturer_is_created
* #dataProvider invalidDataProvider
*/
public function test_error_should_be_returned_when_invalid_data_is_sent($value, bool $deleteField): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)
->post(
'/admin/manufactures',
! $deleteField ? ['name' => $value] : []
);
$response->assertInvalid(['name']);
}
public function invalidDataProvider(): array
{
return [
'Missing name' => [null, true],
'Empty name' => ['', false],
'Null name' => [null, false],
'Array name' => [[], false],
'Number name' => [123, false],
'Boolean name' => [true, false],
];
}
Have in mind I used a lot of things in here:
I have tested if the normal insertion works, if it is checking the a valid name is input (FormRequest rules) and that if the user is not logged in it should throw an unauthorized exception.
I have used #depends, that is used to run tests ONLY if the dependant test passes, that way we can prevent running the "negative" tests just because the normal flow did not succeed, so it makes no sense to run the other ones and also get a "the test did not pass".
I have also used #dataProvider, that is used to share data with a test, so instead of copy-pasting the test a lot of times with data variation, you just vary what data you want the test to use.
Default Laravel Validation class allows strange emails. Here is the Validation rules that I defined:
return Validation::make($data, [
'email' => 'required|string|email|max:100|unique:customers,email'
]);
When I tried to use some strange email like:
aaaa?#%&'#şğüçi̇ö.com it passes the validation. However the non latin characters on the email is converted before DB insert. So the email address on the database doesn't match with the original one.
In order to prevent this I want to disallow the usage of non-latin characters after the # symbol. I tried the custom rule which is:
public function passes($attribute, $value)
{
return filter_var($value, FILTER_VALIDATE_EMAIL)
&& preg_match('/#.+\./', $value);
}
but it is not working. It would be good to get some help on this.
Edit 1
Thanks for your responses! But apparently the reason that the custom validator not taking action is that Laravel sanitizes all input data before any manipulation. That's why after it converts the non-latin characters, preg_replace() returns 1 all the time since there is no non-latin characters on the input. First of all I need to find a solution to this and prevent Laravel to sanitize the input.
From your question I understand you already created a custom Validation Rule and use it like
...
'email' => [
'required',
'string',
...
new ValidateLatinEmail()
]
As you can see here, your RegEx is the problem with that validation
This one should work:
public function passes($attribute, $value)
{
return filter_var($value, FILTER_VALIDATE_EMAIL)
&& preg_match('/#[\x00-\x7F]*\./', $value);
}
I find soft delete in cakephp 3 that implemented via traits. And I try to implement it via behaviors. But unlike the trait version, SoftDeleteBehavior do not work.
I have this line in my model initialize method:
$this->addBehavior('SoftDelete');
And this is my SoftDeleteBehavior
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
use Cake\ORM\RulesChecker;
use Cake\Datasource\EntityInterface;
use App\Model\Behavior\MyQuery;
class SoftDeleteBehavior extends Behavior {
public $user_id = 1;
public function getDeleteDate() {
return isset($this->deleteDate) ? $this->deleteDate : 'deleted';
}
public function getDeleter() {
return isset($this->deleter) ? $this->deleter : 'deleter_id';
}
public function query() {
return new MyQuery($this->connection(), $this);
}
/**
* Perform the delete operation.
*
* Will soft delete the entity provided. Will remove rows from any
* dependent associations, and clear out join tables for BelongsToMany associations.
*
* #param \Cake\DataSource\EntityInterface $entity The entity to soft delete.
* #param \ArrayObject $options The options for the delete.
* #throws \InvalidArgumentException if there are no primary key values of the
* passed entity
* #return bool success
*/
protected function _processDelete($entity, $options) {
if ($entity->isNew()) {
return false;
}
$primaryKey = (array)$this->primaryKey();
if (!$entity->has($primaryKey)) {
$msg = 'Deleting requires all primary key values.';
throw new \InvalidArgumentException($msg);
}
if (isset($options['checkRules']) && !$this->checkRules($entity, RulesChecker::DELETE, $options)) {
return false;
}
$event = $this->dispatchEvent('Model.beforeDelete', [
'entity' => $entity,
'options' => $options
]);
if ($event->isStopped()) {
return $event->result;
}
$this->_associations->cascadeDelete(
$entity,
['_primary' => false] + $options->getArrayCopy()
);
$query = $this->query();
$conditions = (array)$entity->extract($primaryKey);
$statement = $query->update()
->set([$this->getDeleteDate() => date('Y-m-d H:i:s') , $this->getDeleter() => $this->user_id])
->where($conditions)
->execute();
$success = $statement->rowCount() > 0;
if (!$success) {
return $success;
}
$this->dispatchEvent('Model.afterDelete', [
'entity' => $entity,
'options' => $options
]);
return $success;
}
If I use trait, SoftDeleteTrait works in true manner. But SoftDeleteBehavior do not work properly!
One is a PHP language construct, the other is a programmatic concept. You may want to read upon what traits are, so that you understand that this question, as it stands, doesn't make too much sense. Also stuff like "doesn't work" doesn't serve as a proper problem description, please be more specific in the future.
That being said, CakePHP behaviors do serve the purpose of horizontal code reuse, similar to traits, as opposed to vertical reuse by inheritance.
However, even if they have conceptual similarities, you cannot simply exchange them as you seem to do in your code, a trait will be composited into the class on which it is used, so that it becomes part of it as if it were written directly in the class definition, and therefore has the ability to overwrite inherited code like the Table::_processDelete() method, a behavior on the other hand is a totally independent class, which is being instantiated and injected as a dependency into a table class at runtime, and calls to its methods are being delegated via the table class (see Table::__call()), unless a method with the same name already exists on the table class, which in your case means that _processDelete() will never be invoked.
I'd suggest that you study a little more on PHP/OOP basics, as this is rather basic stuff that can be untangled easily by just having a look at the source. Being able to understand how the CakePHP code base and the used concepts do work will make your life much easier.
I want to add a custom validation rule that can be reused across the application when necessary. Where is the best place to put it?
I know I can put them in AppModel but it's messy and pollutes/bloats the AppModel with methods that are only used occasionally for validation, and I find I need to prefix them all with validateUsername, validateCustom, etc. to keep them clearly organised from the other methods in the AppModel. I feel like there is a better way of doing this in the OOP structure.
Is there a way to specify custom rules as static methods of a Lib class, for example?
e.g.
app/Lib/Validate.php
class Validate {
public function username($value) {
$value = array_shift($value);
return preg_match('/^[A-Z0-9_-]+$/i', $value);
}
}
And then use the rules only in the models when you need to:
app/Model/MyModel.php
App::uses('Validate', 'Lib');
class MyModel extends AppModel {
public $validate = array(
'username' => array(
'rule' => 'Validate::username',
'message' => 'Username contains invalid characters'
)
);
}
You could do this with a Behavior. http://book.cakephp.org/2.0/en/models/behaviors.html There is a setup callback you could use to attach validation rules or create your own custom functions. You can then attach the Behaviors as needed to models.