Symfony EventSubscriber using KernelEvents throw me Offset 0 error - php

So I'm learning the basis of creating an API.
I'm trying to interact on the creation of a new user by using the API, however I need with symfony to hash passwords.
I made a PasswordEncoderSubscriber method which hash the password before inserting it into the database.
private $encoder;
public function __construct(PasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ['encodePassword' => EventPriorities::PRE_WRITE]
];
}
public function encodePassword(ViewEvent $event)
{
$result = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if ($result instanceof User && $method === "POST") {
$hash = $this->encoder->encodePassword($result, $result->getPassword());
$result->setPassword($hash);
}
}
I'm using the KernelEvents::View to call the function encodePassword before it written in the database with EventPriorities::PRE_WRITE.
Here's the error I got : Notice: Undefined offset: 0.
The code breaks just after the KernelEvents::VIEW did I forget something ?
Thanks!

According to symfony manual you should provide handler and it's priority as array with 2 items:
return [
KernelEvents::EXCEPTION => [
['processException', 10],
],
];
So, your code should be:
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => [
['encodePassword', EventPriorities::PRE_WRITE],
]
];
}

Related

Laravel - How to pass a custom array or custom request to a FormRequest?

I need to create a custom request on the fly and pass the request to the Form Request. The closest answer that I found was this SO answer. However when I run $request->validated(), it gives me error message Call to a member function validated() on null in /var/www/html/vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php:221.
Below is the whole code:
Function A
private $service;
public function __construct(
Service $service,
) {
$this->service = $service;
}
...
$data = [
"variable_a" => "string",
'variable_b' => true,
];
$request = new Request();
$request->replace($data);
$customFormRequest = new CustomFormRequest();
$validate = $customFormRequest::createFrom(
$request
);
$return_data = $this->service->project($validate);
Service
public function project(CustomFormRequest $request)
{
\Log::debug($request);
$data = $request->validated();
\Log::debug($data);
}
Note
$request and $request->all() has array values when logging inside Service.
The reason it doesn't work is because you are only getting an instance of the CustomFormRequest. Custom form requests are only validated when they are resolved out of the container.
class SomeClass
{
public function __construct(private Service $service) {}
public function call()
{
$data = [
'variable_a' => 'string',
'variable_b' => true,
];
$request = app(CustomFormRequest::class, $data);
$return_data = $this->service->project($request);
}
}

Yii2 JWT 401 Unauthorized

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.

Yii2 how to implementation Optimistic Locks

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';
}
}

Mocking problems with PhpSpec and Prophecy

I am trying to test a method that I have using PhpSpec and Prophecy. I am having a little trouble getting it to work, though. I don't want to use actual values for the mock, so I have used Argument::any(), but it seems to be wanting to execute the actual call inside the method. I thought that this was what mocks were to prevent against?
My Class:
class CreatePostValidator
{
protected $validator;
protected $data;
protected $rules = [
'title' => 'required',
'body' => 'required',
'author' => 'required',
'slug' => 'required'
];
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
public function with($data)
{
$this->data = $data;
}
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules);
if ($validator->fails())
{
return false;
}
return true;
}
}
My Test:
class CreatePostValidatorSpec extends ObjectBehavior
{
function let(Factory $factory)
{
$this->beConstructedWith($factory);
}
function it_is_initializable()
{
$this->shouldHaveType('Blog\Service\Laravel\CreatePostValidator');
}
function it_should_return_false_if_validator_fails(
Factory $factory, Validator $validator)
{
$factory->make(Argument::any(), Argument::any())->willReturn($validator);
$validator->fails()->willReturn(true);
$this->with(Argument::any());
$this->passes()->shouldReturn(false);
}
}
And the error I am getting back:
error: Argument 1 passed to
Double\Illuminate\Validation\Factory\P2::make() must be of the type
array, object given, called in
/Users/will/development/personal/blog/app/Blog/Service/Laravel/CreatePostValidator.php
on line 32 and defined in
/Users/will/development/personal/blog/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCreator.php(49)
:
eval()'d code line 10
This one is not an expectation, so just pass [] there:
$this->with([]);

Testing laravel repository which has model as a dependency

Problem is that i can't test one function, because it is touching other functions of the same repository.
Do I need to test one function in isolation from other functions in same repository, or it is normal that one function can access other functions in same repository ?
If function needs to be tested in isolation from other, how it can be done, because I don't understand how I can mock repository in which I'm working. I understand how to mock dependencies, but how to mock other functions in same repository ?
Am I mocking model correctly in setUp method in the test?
Code:
Real world binding of and repository:
// Bind User repository interface
$app->bind('MyApp\Repositories\User\UserInterface', function () {
return new EloquentUser(new User);
});
EloquentUser.php:
public function __construct(Model $user)
{
$this->user = $user;
}
public function findById($id)
{
return $this->user->find($id);
}
public function replace($data)
{
$user = $this->findById($data['user']['id']);
// If user not exists, create new one with defined values.
if ( ! $user) {
return $this->create($data);
} else {
return $this->update($data);
}
}
public function create($data)
{
$user = $this->user->create($data['user']);
if ($user) {
return $this->createProfile($user, $data['profile']);
} else {
return false;
}
}
private function createProfile($user, $profile)
{
return $user->profile()->create($profile);
}
public function update($user, $data)
{
foreach ($data['user'] as $key => $value) {
$user->{$key} = $value;
}
if (isset($data['profile']) && count($data['profile']) > 0) {
foreach ($data['profile'] as $key => $value) {
$user->profile->$key = $value;
}
}
return ($user->push()) ? $user : false;
}
EloquentUserTest.php
public function setUp()
{
parent::setUp();
$this->user = Mockery::mock('Illuminate\Database\Eloquent\Model', 'MyApp\Models\User\User');
App::instance('MyApp\Models\User\User', $this->user);
$this->repository = new EloquentUser($this->user);
}
public function testReplaceCallsCreateMethod()
{
$data = [
'user' => [
'id' => 1,
'email' => 'test#test.com',
],
'profile' => [
'name' => 'John Doe',
'image' => 'abcdef.png',
],
];
// Mock the "find" call that is made in findById()
$this->user->shouldReceive('find')->once()->andReturn(false);
// Mock the "create" call that is made in create() method
$this->user->shouldReceive('create')->once()->andReturn(true);
// Run replace method that i want to test
$result = $this->repository->replace($data);
$this->assertInstanceOf('Illuminate\Database\Eloquent\Model', $result, 'Should be an instance of Illuminate\Database\Eloquent\Model');
}
When running this test I got:
Fatal error: Call to a member function profile() on a non-object in C:\Htdocs\at.univemba.com\uv2\app\logic\Univemba\Repositories\User\EloquentUser.php on line 107
So it means that Test is trying to touch function in EloquentUser.php:
private function createProfile($user, $profile)
{
return $user->profile()->create($profile);
}
Do I need to mock createProfile ? because profile() cant be found. And if I need to do this, how can i do it because this function is in same repository that i'm testing?
Question is solved.
Just needed to create one more Model instance and pass it in mocked method.
My Working setUp method:
public function setUp()
{
parent::setUp();
$this->user = Mockery::mock('MyApp\Models\User\User');
App::instance('MyApp\Models\User\User', $this->user);
$this->repository = new EloquentUser($this->user);
}
Working test method:
public function testReplaceCallsCreateMethod()
{
$data = [
'user' => [
'id' => 1,
'email' => 'test#test.com',
'password' => 'plain',
],
'profile' => [
'name' => 'John Doe',
'image' => 'abcdef.png',
],
];
// Mock Model's find method
$this->user->shouldReceive('find')->once()->andReturn(false);
// Create new Model instance
$mockedUser = Mockery::mock('MyApp\Models\User\User');
// Mock Models profile->create and pass Model as a result of a function
$mockedUser->shouldReceive('profile->create')->with($data['profile'])->andReturn($mockedUser);
// Pass second instance Model as a result
$this->user->shouldReceive('create')->once()->andReturn($mockedUser);
// Now all $user->profile is properly mocked and will return correct data
$result = $this->repository->replace($data);
$this->assertInstanceOf('Illuminate\Database\Eloquent\Model', $result, 'Should be an instance of Illuminate\Database\Eloquent\Model');
}

Categories