How to pass current user ID to buildRules method of CakePHP model - php

I use AuthComponent with CakePHP 3.8 and now I need to do some logic in Model buildRules method but for this I need to get the current user ID.
Is there any way to pass/retrieve it without using hacks such as accessing directly from the session.
I know that it is possible to pass id via validator from controller as described in CakePHP's documentation
https://book.cakephp.org/3/en/core-libraries/validation.html#using-custom-validation-rules
And it works for validation, however, I am unable to access the validator from the inside of build rules.
When I do as described in here, I get an empty object.
https://book.cakephp.org/3/en/orm/validation.html#using-validation-as-application-rules
It seems that I am able to attach new validation rules but unable to retrieve the "Passed" provider to get the User ID.
It seems a trivial thing but a I spent quite a few hours trying to get the id in a proper way.

OK, After working a bit more, I found how to retrieve user_id inside build rules. Might be helpful to someone.
Do this in the controller
$this->ExampleModel->validator('default')->provider('passed', [
'current_user' => $this->Auth->user('id')
]);
And then put this in you buildRules method
public function buildRules(RulesChecker $rules)
{
$user_id = $this->validator('default')->provider('passed')['current_user'];
$rules->add(
function ($entity, $options) use($user_id) {
//return boolean for pass or fail
},
'ruleName',
[
'errorField' => 'some_id',
'message' => 'Some ID field is inconsistent with App logic.'
]
);
return $rules;
}

The proper way to handle this is usually using either events, or saving options.
For example to make the ID available for the application rules of all tables, you could do something like this:
$this->getEventManager()->on('Model.beforeRules', function (
\Cake\Event\Event $event,
\Cake\Datasource\EntityInterface $entity,
\ArrayObject $options
) {
$options['current_user'] = $this->Auth->user('id');
});
It would be available in the $options argument of the rule accordingly, ie as $options['current_user'].
For a specific save operation you can pass it to the options of the save() call:
$this->ExampleModel->save($entity, [
'current_user' => $this->Auth->user('id')
]);
There's also plugins that can help you with it, for example muffin/footprint.

Related

Laravel: Validate field only when the value changed

Some form fields in my laravel 5.5 application have validation rules that run against a remote API and take quite some time. I would thus only want to run these expensive checks when the field value changes (is different from the value currently stored in the model).
Is there something that implements this already, e.g. as a rule similar to sometimes?
I would image it like this: only_changed|expensive_validation|expensive_validation2. The latter rules would only be executed when the field value has changed.
Assuming you are using a custom Request class, the rules() method expects an associative array to be returned before it applies the validation.
You can build your array of rules dynamically using the request contents before applying the validation like so:
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$validation_array = [
'name' => 'required|string|max:255',
];
if ($this->my_field === $some_condition) {
$validation_array = array_merge($validation_array. [
'my_field' => "required|expensive_validation|expensive_validation2"
]);
}
return $validation_array;
}
Note: I haven't run this yet but the principle should be fine.

Validating Translated Entities in CakePHP 3

I'm having some difficulties validating a I18N field in CakePHP3.
The translate behavior is setup like this:
$this->addBehavior('Translate', [
'fields' => ['name', 'body', 'slug'],
'validator' => 'default'
]);
Like advertised here: https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#validating-translated-entities
The core validation is working properly. I have a validation rule in validationDefault function that checks if name is not empty and it works fine. Though, I also would like to add some application rules on top of this validation. The "name" field should have a unique value. I don't want to allow multiple entities with the same translated name.
This piece of code apparently doesn't work. CakePHP docs also are quite silent about this matter.
public function buildRules(RulesChecker $rules) {
// prevent duplicate creation
$rules->add($rules->isUnique(['name']));
return $rules;
}
Is this actually possible?
Thanks
What you are doing there is creating a rule for the name field on the main model, this won't affect translations. There is no built-in functionality for that, the behavior only assists with validation rules by making use of the validationTranslated() method in case it exists on your model class, it won't help with application rules.
You'd have to create a custom application rule that checks the translation table, by matching against the field, locale, model and content fields, something along the lines of this:
$rules->add(
function (EntityInterface $entity) {
$behavior = $this->behaviors()->get('Translate');
$association = $this->association($behavior->getConfig('translationTable'));
$result = true;
foreach ($entity->get('_translations') as $locale => $translation) {
$conditions = [
$association->aliasField('field') => 'name',
$association->aliasField('locale') => $locale,
$association->aliasField('content') => $translation->get('name')
];
if ($association->exists($conditions)) {
$translation->setErrors([
'name' => [
'uniqueTranslation' => __d('cake', 'This value is already in use')
]
]);
$result = false;
}
}
return $result;
}
);
Note that this uses the association object rather then the target table, this will ensure that further conditions like the model name are being applied automatically.
Also this requires to set the errors on the entity manually, as they are nested, which isn't supported by the rules checker, currently it can only set errors on the first level entity (see the errorField option).
It should also be noted that it would be possible to modify the rules checker for the translation table association (via the Model.buildRules event), however this would result in the errors being set on new entities that will be put in a separate property (_i18n by default) on the main entity, where the form helper won't find the error, so one would then have to read the error manually, which is a little annoying.
See also
Cookbook > Database Access & ORM > Validation > Applying Application Rules

test store method in Laravel

I am trying to test the store method in the simplest way possible. I don't necessarily need Mockery, I'm fine with actually adding to the database. I couldn't get Mockery to work anyway.
Now I have this:
public function testStore()
{
$data = ['name' => 'TestClub', 'code' => 'TCL'];
Input::replace($data);
$this->route('POST', 'clubs.store', $Input::all());
$this->assertResponseOk();
}
This is my controller method:
public function store() {
$validator = Validator::make($data = Input::all(), Club::$rules);
if ($validator->fails()) {
return Redirect::back()->withErrors($validator)->withInput();
}
Club::create($data);
return redirect(Session::get('backUrl'));
}
I get a return code 500, I have no idea why. Can I somehow see the request it's sending?
I am using Laravel 5.0
First, never use Input::all(). You may not know what's in the form when it's submit, instead use only.
However, you also shouldn't be putting validation logic in the Controller if you can help it. Instead, you should make a new Request and put your validation rules in the rules array. Let's look at that
php artisan make:request StoreUserRequest
Then inside of this Request, you'll add return true to your authorize function (or whatever logic you need to identify if the user should be able to make the request), and then your rules to the rules() function (to the array within the rules function, to be more specific).
Now add your new StoreUserRequest as a dependency to the first argument of the store() function.
public function store(App\Http\Requests\StoreUserRequest $request)
Now use the $request->only() method to get your fields back.
Club::create($request->only(['field1', 'field2', 'blob1', 'text1']);
Furthermore, make sure that any fields you wish to add data to are listed within the protected $fillable array in your Club model.
protected $fillable = ['field1', 'field2', 'blob1', 'text1'];
This should do it.

Using Laravel 5 Method Injection with other Parameters

So I'm working on an admin interface. I have a route set up like so:
Route::controllers([
'admin' => 'AdminController',
]);
Then I have a controller with some methods:
public function getEditUser($user_id = null)
{
// Get user from database and return view
}
public function postEditUser($user_id = 0, EditUserRequest $request)
{
// Process any changes made
}
As you can see, I'm using method injection to validate the user input, so URL's would look like this:
http://example.com/admin/edit-user/8697
A GET request would go to the GET method and a POST request to the POST method. The problem is, if I'm creating a new user, there won't be an ID:
http://examplecom/admin/edit-user/
Then I get an error (paraphrased):
Argument 2 passed to controller must be an instance of EditUserRequest, none given
So right now I'm passing an ID of 0 in to make it work for creating new users, but this app is just getting started, so am I going to have to do this throughout the entire application? Is there a better way to pass in a validation method, and optionally, parameters? Any wisdom will be appreciated.
You can reverse the order of your parameters so the optional one is a the end:
public function postEditUser(EditUserRequest $request, $user_id = null)
{
}
Laravel will then resolve the EditUserRequest first and pass nothing more if there's no user_id so the default value will kick in.

How to read CakePHP Model using specific fields?

I'm new to CakePHP and I'm stuck in reading a Model using other fields. I did a cake bake command to generate a simple users CRUD. I can view the user using the url CakePHP provided.
/users/view/1
I can view the user using id = 1. What if I want to view a user by name instead of id?
/users/view/username
By default the view function reads the User model by id.
$this->User->read(null, $id)
Thank you.
you can use find function or findBy<Field>() in your case findByUsername()
check this
I've never used cakePHP myself but I'm going to suggest that you will likely have to implement a new user model method, something like getUserByUsername($username)
This would then in turn interface with your DAL that would get the details of that user based on the username and return a user object that can be used however you wish...
It seems that CakePHP is focusing to deprecate some functions, such as findAll(). Perhaps soon the magic methods such as findBy<field>() will have the same fate.
I can recommend what martswite is suggesting, you should create your custom function:
function findUser($username=''){
return $this->find('first', array(
'conditions' => array(
'User.username' => $username
)
));
}
Perhaps you have a status field, maybe the profile isn't public, you can add a condition:
function findUser($username=''){
return $this->find('first', array(
'conditions' => array(
'User.username' => $username,
'User.status' => 1
)
));
}
I think that's more modular than findBy<Field>.

Categories