Laravel resource toArray() not working properly - php

I've created a new API resource called UserResource:
class UserResource extends JsonResource
{
public function toArray($request)
{
/** #var User $this */
return [
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email_verified_at' => $this->email_verified_at,
];
}
}
And then I'm trying to get current user object and pass it to the resource:
class UserController extends Controller
{
public function index(Request $request)
{
/** #var User $user */
$user = $request->user();
return new UserResource($user);
}
}
But it always throws an exception Trying to get property 'first_name' of non-object. $user variable contains current user.
I'm using Xdebug and I checked that the resource contains formatted json in $this->resource, but not current user's model:
So why? Laravel's documentation says that I'll be able to get current resource (User model in my case) in $this->resource parameter, but it does not work. Any ideas?
UPDATE: here is break point for xbedug in UserResource:

It seems strange to me that you get the User object from the index's request and I reckon that somethings probably wrong there.
Normally, the index method is used to return a listing of all instances of the resp. model, e.g. something like
public function index() {
return UserResource::collection(User::all());
}
Try to return a user object (as resource) which you are sure exists
$user = Users::findOrFail(1);
return new UserResource($user);
and see if the error still persists. If it does not, something is wrong with the data you pass in the request.

I had a look at the JsonResource Class. Look at the contructor:
public function __construct($resource)
{
$this->resource = $resource;
}
Seems like the $user is saved to $this->resource. So you have to change your return statement to:
return [
'first_name' => $this->resource->first_name,
'last_name' => $this->resource->last_name,
'email_verified_at' => $this->resource->email_verified_at,
];
Does that work?

As #Clément Baconnier said in comments to the question, the right way to fix it is reinstalling vendor folder. The problem is that project has been created via Laravel new command from local with php 7.2, but there's php 7.4 in container.

Related

Laravel Validator Not Returning Key

I am creating a new API call for our project.
We have a table with different locales. Ex:
ID Code
1 fr_CA
2 en_CA
However, when we are calling the API to create Invoices, we do not want to send the id but the code.
Here's a sample of the object we are sending:
{
"locale_code": "fr_CA",
"billing_first_name": "David",
"billing_last_name": "Etc"
}
In our controller, we are modifying the locale_code to locale_id using a function with an extension of FormRequest:
// This function is our method in the controller
public function createInvoice(InvoiceCreateRequest $request)
{
$validated = $request->convertLocaleCodeToLocaleId()->validated();
}
// this function is part of ApiRequest which extend FormRequest
// InvoiceCreateRequest extend ApiRequest
// So it goes FormRequest -> ApiRequest -> InvoiceCreateRequest
public function convertLocaleCodeToLocaleId()
{
if(!$this->has('locale_code'))
return $this;
$localeCode = $this->input('locale_code');
if(empty($localeCode))
return $this['locale_id'] = NULL;
$locale = Locale::where(Locale::REFERENCE_COLUMN, $localeCode)->firstOrFail();
$this['locale_id'] = $locale['locale_id'];
return $this;
}
If we do a dump of $this->input('locale_id') inside the function, it return the proper ID (1). However, when it goes through validated();, it doesn't return locale_id even if it's part of the rules:
public function rules()
{
return [
'locale_id' => 'sometimes'
];
}
I also tried the function merge, add, set, etc and nothing work.
Any ideas?
The FormRequest will run before it ever gets to the controller. So trying to do this in the controller is not going to work.
The way you can do this is to use the prepareForValidation() method in the FormRequest class.
// InvoiceCreateRequest
protected function prepareForValidation()
{
// logic here
$this->merge([
'locale_id' => $localeId,
]);
}

Is it possible to override injected Request from a Laravel Package?

I'm building a custom package in Laravel that has a resource Controller. For this example the resource is an Organization In this Controller the basic index, show, store etc actions are defined.
This is my store method in the controller:
/**
* Store a newly created Organization in storage.
*
* #param OrganizationStoreRequest $request
* #return JsonResponse
*/
public function store($request): JsonResponse
{
$newOrganization = new Organization($request->all());
if ($newOrganization->save()) {
return response()->json($newOrganization, 201);
}
return response()->json([
'message' => trans('organilations::organisation.store.error')
], 500);
}
My OrganzationStoreRequest is pretty basic for now:
class OrganizationStoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => 'required'
];
}
}
The store method can be called on a API When the package is being used in a Laravel application. My problem here is that I want to give the users of my package the ability to override my OrganizationStoreRequest with there own requests, as they might have to use different authorize or validation methods.
I've tried building a middleware and Binding my own instance to the OrganizationStoreRequests but I'm not getting the results I want.
Is there anyway for the user of the package to be able to override the OrganizationStoreRequets in the controller of my package ?
With the help of fellow developers on Larachat we've come to the following (easy) solution.
When creating an own request we can bind an instance of it to the IoC Container. For example, creating the OrganizationOverrideRule and extending the original:
class OrganizationOverrideRules extends OrganizationStoreRequest
{
public function rules(): array
{
return [
'name' => 'required',
'website' => 'required',
'tradename' => 'required'
];
}
}
Then in the AppServiceProvider you can bind the new instance like so:
App::bind(
OrganizationStoreRequest::class,
OrganizationOverrideRules::class
);
Then the OrganizationOverrideRules will be used for validation and authorization.

"Call to a member function updateJob() on null"

Now, I know this question has been asked a lot, but I searched and searched but I just can't figure it out and I've been stuck for hours now. I'm really sorry if it turnes out to be a really dumb mistake (It probably will).
So, I have my Controller which instatiates the editAction() function when a button on my website is pressed. It checks if the request is a _POST request, passes on the data and checks if the input given is valid, all this works fine.
I then try to access a function in my Manager class. And that's where the error is happening and my website spits out:
"Call to a member function updateJob() on null".
Now, PhpStorm is not saying there's an error or a warning, it recognizes the jobManager class and I've checked the namespaces and class names, but all are correct. The variables are also defined correctly, as far as I can see. I'd be really thankful if someone could tell me what I am doing wrong. The code is below.
current state after adding $jobManager to __construct:
class IndexController extends AbstractActionController
{
/**
* Entity manager.
* #var EntityManager
*/
private $entityManager;
/**
* Post manager.
* #var JobManager
*/
private $jobManager;
public function __construct($entityManager, $jobManager)
{
$this->entityManager = $entityManager;
/***
* Edit from comment advice:
* I have added this line to my __construct
* But this does not solve the issue.
***/
$this->jobManager = $jobManager;
}
public function indexAction()
{
// Get recent jobs
$jobs = $this->entityManager->getRepository(Jobs::class)
->findBy(['status'=>Jobs::STATUS_READY]
);
// Render the view template
return new ViewModel([
'jobs' => $jobs
]);
}
public function editAction()
{
// Create the form.
$form = new JobForm();
// Get post ID.
$jobId = $this->params()->fromRoute('id', -1);
// Find existing job in the database.
$jobs = $this->entityManager->getRepository(Jobs::class)
->findOneById($jobId);
if ($jobs == null) {
$this->getResponse()->setStatusCode(404);
return;
}
// Check whether this job is a POST request.
if ($this->getRequest()->isPost()) {
// Get POST data.
$data = $this->params()->fromPost();
// Fill form with data.
$form->setData($data);
if ($form->isValid()) {
// Get validated form data.
$data = $form->getData();
// Use job manager service to add new post to database.
$this->jobManager->updateJob( $jobs, $data);
// Redirect the user to "backups" page.
return $this->redirect()->toRoute('backups');
}
} else {
$data = [
'id' => $jobs->getId(),
'jobName' => $jobs->getJobName(),
'status' => $jobs->getStatus(),
'vmId' => $jobs->getVmId(),
'targetfolderPrefix' => $jobs->getTargetFolderPrefix(),
'numberOfBackups' => $jobs->getNumberOfBackups(),
'lastBackupUsed' => $jobs->getLastBackupUsed(),
'repeat' => $jobs->getRepeat(),
'scheduleRunAtMinute' => $jobs->getScheduleRunAtMinute(),
'scheduleRunAtHour' => $jobs->getScheduleRunAtHour(),
'scheduleRunAtDOW' => $jobs->getScheduleRunAtDOW(),
'hostId' => $jobs->getHostId()
];
$form->setData($data);
}
// Render the view template.
return new ViewModel([
'form' => $form,
'jobs' => $jobs
]);
}
}
What is wrong
$this->jobManager->updateJob( $jobs, $data);
You are telling PHP:
"In $this class, look in the jobManager object and run the method updateJob with these variables.... "
But in $this class you have written:
/**
* Post manager.
* #var JobManager
*/
private $jobManager;
But you have nowhere set jobManager to be anything. You have no setter function in the class as well as no other function setting what a jobManager variable actually is... so jobManager can never be anything.
So what you're in effect doing is saying to PHP
"In $this class, look in the jobManager empty null-space and run the method updateJob with these variables..."
This is clearly not going to end well.
How to fix it
You need to set what jobManager is before you can use it, as referenced by Xatenev. Typically when you construct the class or using a Setter method, if preferred.
ONE:
public function __construct(EntityManager $entityManager, JobManager $jobManagerVar)
{
$this->entityManager = $entityManager;
$this->jobManager = $jobManagerVar;
}
Alternatively - if ->jobManager method needs to be defined after the object IndexController is created; then you need to use a Setter class (because the jobManager var is *private*.
Thus TWO:
class IndexController extends AbstractActionController
{
...
public function setJobManager($jobManagerVar){
$this->jobManager = $jobManagerVar
}
...
}
And then when you instantiate the IndexController you can do:
// ONE from above:
$theClass = new IndexController($entity,$jobManager);
or
// TWO from above
$theClass = new IndexController($entity);
...
$theClass->setJobManager($jobManger);
There are various other nuances as to methods of setting values in classes, I'm not going to go over them all, it will depend on what's going on in your wider project.

Yii2 REST Simplify BasicAuth

I'm impressed with how simple it was to create a REST api in Yii2. However, i'm having a little trouble understanding the Basic Authentication. My needs are utterly simple and i'd like my solution to follow suit.
I need Basic token authentication here. I'm not even against hardcoding it for now, but here's what i've done thus far.
I have database table to hold my singular token ApiAccess(id, access_token)
ApiAccess.php - Model - NOTE: IDE shows syntax error on this first line
class ApiAccess extends base\ApiAccessBase implements IdentityInterface
{
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['access_token' => $token]);
}
}
Module.php - in init() function
\Yii::$app->user->enableSession = false;
I made an ApiController that each subsequent noun extends
ApiController.php
use yii\rest\ActiveController;
use yii\filters\auth\HttpBasicAuth;
use app\models\db\ApiAccess;
class ApiController extends ActiveController
{
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
return $behaviors;
}
}
As it stands, accessing an api endpoint in the browser prompts for a username and password. Request via REST Client displays access error.
How do I properly tie HttpBasicAuth to my ApiAccess model?
OR
How do I hardcode an api access token? (First option is obviously best)
Let's watch and try to understand "yii" way basic auth for REST.
1st. When you adding behavior to your REST controller, you enabling basic auth:
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
];
As you did. What does it mean? It means that your application will parse your authorization header. It looks like:
Authorization : Basic base64(user:password)
Here is a trick for yii2. If you look at code more carefully, you will see that yii uses access_token from user field, so your header should look like:
Authorization : Basic base64(access_token:)
You can parse this header by your own, if you want to change this behavior:
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
'auth' => [$this, 'auth']
];
....
public function auth($username, $password)
{
return \app\models\User::findOne(['login' => $username, 'password' => $password]);
}
2nd thing to do. You must implement findIdentityByAccessToken() function from identityInterface.
Why your IDE complaining?
class User extends ActiveRecord implements IdentityInterface
Here's how your user class declaration should look.
From your implementation and structure:
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['access_token' => $token]);
}
you not returning object of class which implements identity interface.
How to make it properly?
Add column access_token to your users table, and return back your user model (you can look how it must look here - https://github.com/yiisoft/yii2-app-advanced/blob/master/common/models/User.php)
If you do this - default code will work with your findIdentityByAccessToken() implementation.
If you don't want to add field to users table - make new one with user_id,access_token fields. Then your implementation should look like:
public static function findIdentityByAccessToken($token, $type = null)
{
$apiUser = ApiAccess::find()
->where(['access_token' => $token])
->one();
return static::findOne(['id' => $apiUser->user_id, 'status' => self::STATUS_ACTIVE]);
}
Hope i could cover all of your questions.

Laravel & Mockery: Unit testing the update method without hitting the database

Alright so I'm pretty new to both unit testing, mockery and laravel. I'm trying to unit test my resource controller, but I'm stuck at the update function. Not sure if I'm doing something wrong or just thinking wrong.
Here's my controller:
class BooksController extends \BaseController {
// Change template.
protected $books;
public function __construct(Book $books)
{
$this->books = $books;
}
/**
* Store a newly created book in storage.
*
* #return Response
*/
public function store()
{
$data = Input::except(array('_token'));
$validator = Validator::make($data, Book::$rules);
if($validator->fails())
{
return Redirect::route('books.create')
->withErrors($validator->errors())
->withInput();
}
$this->books->create($data);
return Redirect::route('books.index');
}
/**
* Update the specified book in storage.
*
* #param int $id
* #return Response
*/
public function update($id)
{
$book = $this->books->findOrFail($id);
$data = Input::except(array('_token', '_method'));
$validator = Validator::make($data, Book::$rules);
if($validator->fails())
{
// Change template.
return Redirect::route('books.edit', $id)->withErrors($validator->errors())->withInput();
}
$book->update($data);
return Redirect::route('books.show', $id);
}
}
And here are my tests:
public function testStore()
{
// Add title to Input to pass validation.
Input::replace(array('title' => 'asd', 'content' => ''));
// Use the mock object to avoid database hitting.
$this->mock
->shouldReceive('create')
->once()
->andReturn('truthy');
// Pass along input to the store function.
$this->action('POST', 'books.store', null, Input::all());
$this->assertRedirectedTo('books');
}
public function testUpdate()
{
Input::replace(array('title' => 'Test', 'content' => 'new content'));
$this->mock->shouldReceive('findOrFail')->once()->andReturn(new Book());
$this->mock->shouldReceive('update')->once()->andReturn('truthy');
$this->action('PUT', 'books.update', 1, Input::all());
$this->assertRedirectedTo('books/1');
}
The issue is, when I do it like this, I get Mockery\Exception\InvalidCountException: Method update() from Mockery_0_Book should be called exactly 1 times but called 0 times. because of the $book->update($data) in my controller. If I were to change it to $this->books->update($data), it would be mocked properly and the database wouldn't be touched, but it would update all my records when using the function from frontend.
I guess I simply just want to know how to mock the $book-object properly.
Am I clear enough? Let me know otherwise. Thanks!
Try mocking out the findOrFail method not to return a new Book, but to return a mock object instead that has an update method on it.
$mockBook = Mockery::mock('Book[update]');
$mockBook->shouldReceive('update')->once();
$this->mock->shouldReceive('findOrFail')->once()->andReturn($mockBook);
If your database is a managed dependency and you use mock in your test it causes brittle tests.
Don't mock manage dependencies.
Manage dependencies: dependencies that you have full control over.

Categories