Yii2 how to implementation Optimistic Locks.
I'm trying to follow this official doc.
I thought I carefully follow the step.
but still error :
Here my procedure.
Create a column in the DB "version defualt velue = '0'
2.Model.php
use yii\behaviors\OptimisticLockBehavior;
class OptimisticTest extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'optimistictest';
}
public function rules()
{
return [
[['version'], 'required'],
[['created_by', 'updated_by','version'], 'integer'],
];
}
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'value' => new Expression('NOW()'),
],
[
'class' => BlameableBehavior::className(),
],
[
'class' => OptimisticLockBehavior::className(), //'getLockAttribute' =>$this->version
],
];
}
}
myController.php
public function actionUpdate($id)
{
$model = $this->findModel($id);
$tempDocs = $model->docs;
$modelRunning = $this->findModelRunning($model->running_id);
$model->scenario = 'update';
try {
if ($model->load(Yii::$app->request->post()) &&
$modelRunning->load(Yii::$app->request->post()) &&
Model::validateMultiple([$model,$modelRunning]))
{
if($modelRunning->save())
{
$this->CreateDir($model->ref);
$model->docs = $this->uploadMultipleFile($model,$tempDocs);
$model->save();
}
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('update', [
'model' => $model,
'modelRunning' => $modelRunning,
]);
}
} catch (StaleObjectException $e) {
// logic to resolve the conflict
Yii::$app->session->setFlash('danger',Yii::t('app', 'Record can not be updated, there is a user associated with it'));
return $this->redirect(['index']);
}}
Error is From Model.php in public function behaviors()
in step 1. Override this method to return the name of this column.
how to override this method.
what i'm missing.
Overriding optimisticLock() method means, that you have to implement the method in your model so it can be used instead of default implementation.
Your model should look like this
class OptimisticTest extends \yii\db\ActiveRecord
{
//... your other methods in model
public function optimisticLock()
{
//this method should return the name of version attribute
return 'version';
}
}
Related
This is my ReportResource file
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->cname,
'start' => $this->whenPivotLoaded('company_package', function () {
return $this->pivot->start_date;
}),
];
}
Company Model
class Company extends Model
{
//
public function packages(){
return $this->belongsToMany('App\Package')->withPivot('start_date');
}
}
Package Model
class Package extends Model
{
public function companies(){
return $this->belongsToMany('App\Company')->withPivot('start_date');
}
}
Controller
return new ReportResource(Company::with('packages')->get());
What I am doing wrong .Please help
Please help me in fixing this problem. I want to try sizeg/yii2-jwt (https://github.com/sizeg/yii2-jwt). I followed the Step-by-step usage example but I always get authorization issues. I also want to change the Model (I want to replace it with something other than the User model).
On Github it says after installing the plugin I have to edit web.php
'jwt' => [
'class' => \sizeg\jwt\Jwt::class,
'key' => 'secret',
'jwtValidationData' => \app\components\JwtValidationData::class,
],
After that I should create JwtValidationData class. where you have to configure ValidationData informing all claims you want to validate the token:
class JwtValidationData extends \sizeg\jwt\JwtValidationData
{
/**
* #inheritdoc
*/
public function init()
{
$this->validationData->setIssuer('');
$this->validationData->setAudience('');
$this->validationData->setId('4f1g23a12aa');
parent::init();
}
}
in the User model:
public static function findIdentityByAccessToken($token, $type = null)
{
foreach (self::$users as $user) {
if ($user['id'] === (string) $token->getClaim('uid')) {
return new static($user);
}
}
return null;
}
And the controller:
class ProfileController extends Controller {
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => JwtHttpBearerAuth::class,
'optional' => [
'login',
],
];
return $behaviors;
}
private function generateJwt($id) {
$jwt = Yii::$app->jwt;
$signer = $jwt->getSigner('HS256');
$key = $jwt->getKey();
$time = time();
return $jwt->getBuilder()
->issuedBy('')
->permittedFor('')
->identifiedBy('4f1g23a12aa', true)
->issuedAt($time)
->expiresAt($time + 3600)
->withClaim('uid', $id)
->getToken($signer, $key);
}
public function actionLogin($person_id)
{
$token = $this->generateJwt($person_id);
return $this->asJson([
'id' => $token->getClaim('uid'),
'token' => (string) $token
]);
}
public function actionData()
{
return $this->asJson([
'success' => true
]);
}
}
I thought it was the same as the tutorial but I always get unauthorized. How to solve this problem?
You just created a token for the user, but where you use that?
you have to send token as "Bearer" authentication in your header to achieve this goal if you want to authenticate the user by "JwtHttpBearerAuth" behavior.
otherwise, you have to login the user manually in your code.
I have tried different approach for create action.
Usually i have one action which renders, validates and saves data.
Now i want two separate actions. One for rendering view and second for validation and data storage.
View
$form = ActiveForm::begin([
'action' => ['ew/eshop-create'],
'method' => 'post',
]);
echo $form->field($model, 'input')->textarea([
'rows' => '20'
]);
echo Html::submitButton(
'<i class="glyphicon glyphicon-send"></i> OdoslaƄ',
[
'class' => 'btn btn-success',
'name' => 'create-button'
]
);
ActiveForm::end();
Model
class EshopCreate extends Model
{
public $input;
public function attributeLabels()
{
return [
'input' => 'JSON vstup'
];
}
public function rules()
{
return [
['input', 'required'],
['input', 'validateInput'],
];
}
public function validateInput()
{
// validate json
$this->addError('input', 'Something is wrong');
}
}
Controller
class EwController extends Controller
{
public function actionEshopCreateForm()
{
$model = new EshopCreate();
return $this->render('eshop-create-form', [
'model' => $model
]);
}
public function actionEshopCreate()
{
$model = new EshopCreate();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
exit('create');
}
return $this->redirect(['ew/eshop-create-form']);
}
}
Edit:
So i had problem with validation. I switched model->load and model->validate in actionEshopCreate.
So it works fine, but message from validateInput is not displayed. Also when i turn off clientvalidation, there are no error messages at all. So my question is how to pass errors from one action to another.
Thanks.
You are redirecting to a different action when the form is submitted and fail to pass the validation
return $this->redirect(['ew/eshop-create-form']);
the EshopCreate model will lose all validation messages when the redirection happens
Probably you want to do something like this
class EwController extends Controller
{
public function actionEshopCreateForm()
{
$model = new EshopCreate();
return $this->render('eshop-create-form', [
'model' => $model
]);
}
public function actionEshopCreate()
{
$model = new EshopCreate();
if ($model->load(Yii::$app->request->post()) && $model->validate())
{
//store the model data in session or somewhere for example where you can retrieve it later in the actionEshopCreateForm() action
return $this->redirect(['ew/eshop-create-form']);
}
return $this->render('eshop-create-form', [
'model' => $model
]);
}
}
I have 2 FormRequest classes (ReadersFormRequest, SocialMediaFormRequest) and I want to store and update a Reader. A Reader may have 0 or many social media accounts, so it's necessary to validate the request.
ReadersFormRequest
class ReadersFormRequest extends FormRequest
{
public function rules()
{
return [
'first_name'=>'required',
'last_name'=>'required',
'birthday'=>'required',
'region'=>'required',
'photo_url'=>'required',
'support'=>'required',
'riwayas_id'=>'required',
'description'=>'required',
];
}
}
SocialMediaFormRequest
public function rules()
{
return [
'url'=>'required|url',
'title'=>'required'
];
}
So I want to merge the SocialMediaFormRequest rules in ReadersFormRequest rules
I found a solution:
make SocialMediaFormRequest rules a static method and merge it in SocialMediaFormRequest rules
SocialMediaFormRequest
public static function rules()
{
return [
'url'=>'required|url',
'title'=>'required'
];
}
ReadersFormRequest
public function rules()
{
return array_merge(SocialMediaFormRequest::rules(),[
'first_name'=>'required',
'last_name'=>'required',
'birthday'=>'required',
'region'=>'required',
'photo_url'=>'required',
'support'=>'required',
'riwayas_id'=>'required',
'description'=>'required',
]);
}
I think the merge is correctly done, but in update controller when I call this ReadersFormRequest, I don't know what happens.
public function update(ReadersFormRequest $request, Readers $reader)
{
// valid and update reader
Readers::whereId($reader->id)->update([
'first_name' => $request->validated()['first_name'],
'last_name' => $request->validated()['last_name'],
'photo_url' => $request->validated()['photo_url'],
'birthday' => $request->validated()['birthday'],
'region' => $request->validated()['region'],
'support' => $request->validated()['support'],
'riwayas_id' => $request->validated()['riwayas_id'],
'description' => $request->validated()['description']
]);
// For update their social media account links
foreach ($request->validated()['url'] as $key => $url)
{
}
return redirect(route('readers.show',$reader));
}
When I PUT the reader form this controller is not called.
Alter your class to:
class ReadersFormRequest extends FormRequest
{
public function rules()
{
return [
'first_name'=>'required',
'last_name'=>'required',
'birthday'=>'required',
'region'=>'required',
'photo_url'=>'required',
'support'=>'required',
'riwayas_id'=>'required',
'description'=>'required',
'url'=>'required|url',
'title'=>'required'
];
}
}
or if you really want to use a class do:
class ReadersFormRequest extends FormRequest
{
public function rules(SocialMediaFormRequest $social)
{
$mediaRules = $social->rules();
$rules = [
'first_name'=>'required',
'last_name'=>'required',
'birthday'=>'required',
'region'=>'required',
'photo_url'=>'required',
'support'=>'required',
'riwayas_id'=>'required',
'description'=>'required',
];
return array_merge($rules,$mediaRules);
}
}
This's how I handled it in similar situation:
public static function combineValidations($rules1, $rules2)
{
if(!is_array($rules2)) return $rules1;
foreach($rules2 as $key => $item)
{
if(!isset($rules1[$key]))
{
$rules1[$key] = $item;
}else
{
$rules1[$key] .= '|'.$item;
}
}
return $rules1;
}
I am using a repository pattern in my Laravel 4 project but come across something which I think I am doing incorrectly.
I am doing user validation, before saving a new user.
I have one method in my controller for this:
public function addNewUser() {
$validation = $this->userCreator->validateUser($input);
if ( $validation['success'] === false )
{
return Redirect::back()
->withErrors($validation['errors'])
->withInput($input);
}
return $this->userCreator->saveUser($input);
}
Then the validateUser method is:
public function validate($input) {
$rules = array(
'first_name' => 'required',
'last_name' => 'required',
'email_address' => 'unique:users'
);
$messages = [
];
$validation = Validator::make($input, $rules, $messages);
if ($validation->fails())
{
$failed = $validation->messages();
$response = ['success' => false, 'errors' => $failed];
return $response;
}
$response = ['success' => true];
return $response;
}
This may be okay, but I dont like doing the if statement in my controller? I would rather be able to handle that in my validation class.
But to be able to redirect from the validation class, I need to return the method in the controller.
What if I then want to have 5 methods called, I cant return them all?
I would like to be able to simply call the methods in order, then in their respective class handle what I need to and if there is any errors redirect or deal with them. But if everything is okay, simply ignore it and move to the next function.
So example:
public function addNewUser()
{
$this->userCreator->validateUser($input);
$this->userCreator->formatInput($input);
$this->userCreator->sendEmails($input);
return $this->userCreator->saveUser($input);
}
If doing the if statement in the controller isn't as bad as I think then I can continue, but this seems incorrect?
For repository pattern, you can use this :-
setup your basemodel like this
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model{
protected static $rules=null;
protected $errors=null;
public function validateForCreation($data)
{
$validation=\Validator::make($data,static::$rules);
if($validation->fails())
{
$this->errors=$validation->messages();
return false;
}
return true;
}
/**
* #return errors
*/
public function getErrors() { return $this->errors; }
}
now in your repository, add these methods
protected $model;
protected $errors=null;
public function model(){ return $this->model; }
public function getErrors(){ return $this->errors; }
public function create($inputs)
{
if(!$this->model->validateForCreation($inputs))
{
$this->errors=$this->model->getErrors();
return false;
}
$new=$this->model->create($inputs);
return $new;
}
and the controller will look like this..
public function postCreate(Request $request)
{
$inputs=$request->all();
if($new=$this->repo->create($inputs))
{
return redirect()->back()
->with('flash_message','Created Successfully');
}
return redirect()->back()->withInput()->withErrors($this->repo->getErrors())
->with('flash_message','Whoops! there is some problem with your input.');
}