My controller looks like this:
public function store(Request $request) {
$validateData = $request->validate([
'name' => 'required|unique:languages',
'code' => 'required|size:3',
'flag' => 'required|size:2'
]);
$language = new Language();
$language->name = $request->name;
$language->code = $request->code;
$language->flag = $request->flag;
$saveLanguage = $language->save();
if(!$saveLanguage){
return response()->json(['error'=>'Something went wrong, please try later.'],500);
}
return response()->json(['success'=>'Language has been created successfully', 'data'=>$language],200);
As you can see, I am instantiating a new Language object and everything works fine, but first problem is, imagine I change Language class in future (for example: you have to pass 2 parameters in constructor), I have to change this controller and every other controllers where I am instantiating Language object.
The second problem is I can't or it's too hard to test this controller.
I am curious what is the best solution to solve this problems in laravel?
For example is it a good solution to use simple factory or factory method pattern for every model I am using in my controllers.
I think when you write something like this $var = new SomeClass() in other class, this otherClass is depends on SomeClass and when you want to change SomeClass you have to update otherClass to. What do you think abaout this, how can I avoid this.
Or you could use the Eloquent Create function, this way you don't have to worry about the constructor.
$language = Language::create($request->only(['name', 'code', 'flag']));
This function will insert the data in the database and return the model.
Problem #1: If you change the signature of a constructor (e.g. adding new required parameters to the Language class constructor), then yes, you'll need to update all places where that constructor is called. There's no way around that, unless you can encapsulate the logic for what those parameters should be into a helper method somewhere (though you'll still need to update all calls the first time, while future changes will be abstracted away). However, modern IDEs (e.g. PHPStorm) can help you automate the process of replacing the old signature with the new signature.
Problem #2: You can actually test a controller like this quite easily. To take an example from the Laravel docs and apply it to your code, you could do something like this (in Laravel 7.x):
$response = $this->postJson('/language', ['name' => 'Swedish', 'code' => 'swe', 'flag => 'SE']);
$response
->assertStatus(200)
->assertJson([
'success' => 'Language has been created successfully',
])
->assertJsonPath('data.name', 'Swedish')
->assertJsonPath('data.code', 'swe')
->assertJsonPath('data.flag', 'SE');
Related
I have this function that receives a "user" model by parameter , I want to collect the properties of that object, for this I do it this way, the code works for me but the editor "phpstorm" complained to me with this error and it was to know what would be the best way to do this.
Thank you
public function sendEmail(User $user)
{
$params = [
'name' => "{$user->completeName}",
'language' => "{$user->locale}",
'user_id' => "{$user->id}"
];
}
Field accessed via magic method less... (Ctrl+F1)
Inspection info: Referenced field is not found in subject class. Note: Check is not performed on objects of type "stdClass" or derived.
Thanxs,
maybe this is simpler
$params = $user->toArray();
or
$params = $user->toJson();
That's because in Laravel your model does not actually have the properties defined.
In PHP there is the concept of magic methods though ( http://php.net/manual/en/language.oop5.overloading.php#object.get ), where the __get() method allows you to basically intercept the access to an otherwise inaccessible (or non-existing) property.
This is what happens behind the scenes in Laravel. All your property accesses are "intercepted" and Laravel looks if your database contains a column which is named like the property you are trying to access (very simplified speaking).
In a Laravel context you can savely ignore this warning.
I'm doing a PHPUnit on my controller and I can't seem to mock the Request right.
Here's the Controller:
use Illuminate\Http\Request;
public function insert(Request $request)
{
// ... some codes here
if ($request->has('username')) {
$userEmail = $request->get('username');
} else if ($request->has('email')) {
$userEmail = $request->get('email');
}
// ... some codes here
}
Then on the unit test,
public function testIndex()
{
// ... some codes here
$requestParams = [
'username' => 'test',
'email' => 'test#test.com'
];
$request = $this->getMockBuilder('Illuminate\Http\Request')
->disableOriginalConstructor()
->setMethods(['getMethod', 'retrieveItem', 'getRealMethod', 'all', 'getInputSource', 'get', 'has'])
->getMock();
$request->expects($this->any())
->method('get')
->willReturn($requestParams);
$request->expects($this->any())
->method('has')
->willReturn($requestParams);
$request->expects($this->any())
->method('all')
->willReturn($requestParams);
// ... some codes here
}
The problem here is that when ever I var_dump($request->has('username'); it always return the $requestParams value in which is the whole array. I'm expecting that it should return true as the username key exists in the array.
Then when I delete the username key on the $requestParams, it should return false as it does not contain the username key on the array
Its not ideal to mock Requests, but sometimes you just want to do it anyway:
protected function createRequest(
$method,
$content,
$uri = '/test',
$server = ['CONTENT_TYPE' => 'application/json'],
$parameters = [],
$cookies = [],
$files = []
) {
$request = new \Illuminate\Http\Request;
return $request->createFromBase(
\Symfony\Component\HttpFoundation\Request::create(
$uri,
$method,
$parameters,
$cookies,
$files,
$server,
$content
)
);
}
As far as I can see and understand you're telling your unit test that when you call $request->has() on your request object that it should return the $requestParams array, not true or false, or anything else.
Unless you specifically check what is send with a method call your mock doesn't actually care what is send, it just cares that it was called.
You might want to explore creating an empty request and filling it with data if that is possible in your use case as that'll let you run your unit test with more ease and less issues. This won't work in all cases.
You could include what assertions you're making in your unit test so we can see more clearly what you're running into, but as it is. It returns exactly what you're telling it to return. Even if that's not what you actually want it to return.
Mocks are used to separate your Unit-Test from the rest of your system. As such you usually tend to only check if a specific method is called to see if your code actually exits to the class you mocked and if it has the expected data you'd send along. In some extreme cases you can want to mock the system you're actually testing, but this usually indicates that your code is too dependent on other classes or it's doing too much.
Another reason to use mocks is to satisfy Type Casting constraints in your method calls. In these cases you'll usually create an empty mocked object and fill it with some dummy data your code will accept or break on to test the code.
In your case it seems you want to check if your code actually works correctly and for this I'd suggest either not mocking the request, or making specific tests where you tell it to return true, or false (test for both cases)
So something along the lines of:
$request->expects($this->any())
->method('has')
->with('username')
->willReturn(true); // or false in your next test
Edit:
As you mentioned in the comment Below you ran into the issue that you're using the has method multiple times in your code and ran into issues.
The Questions I've linked to in my response comment go into greater detail but to sum it up, you can use an inline function or the at() method to deal with multiple cases.
With at() you can supply specific iterations of the code to hit only that bit of the test. It has been mentioned that this makes your tests rather brittle as any has added before the previous ones would break the test.
$request->expects($this->at(0))
->method('has')
->with('username')
->willReturn('returnValue');
$request->expects($this->at(1))
->method('has')
->with('email')
->willReturn('otherReturnValue');
The inline function (callback) solution would allow you to customize your test to allow multiple cases and to return data as required. Unfortunately I'm not too familiar with this concept as I haven't used it myself before. I suggest reading the PHPUnit docs for more information about this.
In the end I'd still suggest not mocking the request and instead making an empty request that you'll fill with the data you want to check. Laravel comes with some impressive methods that'll let you manually fill the request with a lot of data you'd usually test against.
For example you can add data (post/get data) by using
request->add(['fieldname' => 'value'])
As a last few pointers I'd like to mention that it seems you use var_dump.
Laravel comes with two of it's own functions that are similar and quite useful in debugging.
You can use dd(); or dump();
dd(); dumps and stops the execution of code, while dump(); just outputs whatever you decide. so you could do dd($request); or dump($request); and see what the variables/class objects/etc holds. It'll even put it in a rather spiffy layout with some Javascript and such to allow you to see what's in it and such. Might want to check it out if you didn't knew it existed.
If you use request()->user() you can set user resolver. It allows you to return user you want. I had the same problem and solution for me was like this:
public function testSomething()
{
$user = User::factory()->create();
request()->setUserResolver(function() use ($user) {
return $user;
});
// Dumped result will be newly created $user
dd(request()->user());
}
A simpler answer than #Ian, if your situation is simpler:
Per https://stackoverflow.com/a/61903688/135114,
if
your function under test takes a $request argument, and
you don't need to do funky stuff to the Request—real route paths are good enough for you
... then you don't need to "mock" a Request (as in, mockery),
you can just create a Request and pass it, e.g.
public function test_myFunc_condition_expectedResult() {
...
$mockRequest = Request::create('/path/that/I_want', 'GET');
$this->assertTrue($myClass->myFuncThat($mockRequest));
}
I was running unit test on a FormRequest child class with Laravel Framework 9.3.0 and get this error:
Error : Call to a member function get() on null
/vendor/symfony/http-foundation/Request.php:676
# code failing
$customRequest->get('parameter');
As you can see in Request class, there are lot of public properties (source code):
public $attributes;
public $request;
public $query;
public $server;
public $files;
public $cookies;
public $headers;
...
This is the way i find to partially mock Request class, example below:
# test code
$this->customRequest = new CustomRequest();
$parameterBag = \Mockery::mock(ParameterBag::class);
$parameterBag->shouldReceive('get')
->with('parameter', \Mockery::any())
->andReturn(null) // anything
;
$this->customRequest->attributes = $parameterBag;
In some of my tests, I have a user model I have created and I run some methods that need to save certain attributes. In rails, I would typically call something like user.reload which would repopulate the attributes from the database.
Is there a way in laravel to do that? I read through the api and couldn't find a method for it: http://laravel.com/api/4.1/Illuminate/Database/Eloquent/Model.html Any ideas on the "right" way to do this?
There was a commit submitted to the 4.0 branch made in August to add a reload() method, but so far it hasn't been merged with the newer Laravel branches.
But... Laravel 5 is providing a "fresh()" method that will return a new instance of the current model. Once you're using Laravel 5.0 or newer, you can reload a model like this:
$model = $model->fresh();
Note that fresh() doesn't directly update your existing $model, it just returns a new instance, so that's why we need to use "$model = ". It also accepts a parameter which is an array of relations that you want it to eager load.
If you aren't yet using Laravel 5 but you want the same functionality, you can add this method to your model(s):
public function fresh(array $with = array())
{
$key = $this->getKeyName();
return $this->exists ? static::with($with)->where($key, $this->getKey())->first() : null;
}
Update: If you're using Laravel 5.4.24 or newer, there is now a $model->refresh() method that you can use to refresh the object's attributes and relationships in place rather than fetching a new object like fresh() does. See Jeff Puckett answer for more specifics on that.
Thanks to PR#19174 available since 5.4.24 is the refresh method.
$model->refresh();
This way you don't have to deal with reassignment as is shown in other answers with the fresh method, which is generally not helpful if you want to refresh a model that's been passed into another method because the variable assignment will be out of scope for the calling contexts to use later.
refresh() is a mutable operation: It will reload the current model instance from the database.
fresh() is an immutable operation: It returns a new model instance from the database. It doesn't affect the current instance.
// Database state:
$user=User::create([
'name' => 'John',
]);
// Model (memory) state:
$user->name = 'Sarah';
$user2 = $user->fresh();
// $user->name => 'Sarah';
// $user2->name => 'John'
$user->refresh();
// $user->name => 'John'
I can't see it either. Looks like you'll have to:
$model = $model->find($model->id);
You can also create one yourself:
public function reload()
{
$instance = new static;
$instance = $instance->newQuery()->find($this->{$this->primaryKey});
$this->attributes = $instance->attributes;
$this->original = $instance->original;
}
Just tested it here and it looks it works, not sure how far this goes, though, Eloquen is a pretty big class.
I believe #Antonio' answer is the most correct, but depending on the use case, you could also use a combination of $model->setRawAttributes and $model->getAttributes.
$users = User::all();
foreach($users as $user)
{
$rawAttributes = $user->getAttributes();
// manipulate user as required
// ..
// Once done, return attribute state
$user->setRawAttributes($rawAttributes);
}
The primary downside to this is that you're only "reloading" the data attributes, not any relationships you've altered, etc. That might also be considered the plus side.
EDIT
As of L5 - fresh() is the way to go
Can I do this in a Controller:
$this->User->read(null, $id);
$this->User->find('list');
Is it correct?
Am I using MVC correctly?
Can these easy functions be used in a Controller? Or, do I need to create these functions in the Model? Like Model->getUser(), and have that function use Model->read().
I know that functions it's called by Model, but, when I want pass some parameters, and function makes big, for example:
$this->User->find('all', array(
'conditions' => array(
'User.active' => true,
'User.group_id' => 3,
'User.age >=' => 18
)
));
Can I call this function in Controller, or need create a custom function in Model, to call it? Like... $this->User->findSomeCustomFunction($param1, $param2, $param3)?
TLDR:
It's "ok" to call a find() from your Controller, however best practice is to put any/all find()s in your models.
If you make a habit of putting all your find()s in your models, it will make it much easier to maintain your code in the long run.
Explanation/example:
In this case, as an example, you could start with a seemingly simple function:
//User model
public function getUsers() {
return $this->find('list');
}
But later, maybe you need something more along the lines of:
//User model
public function getUsers($opts = array()) {
$defaults = array(
'findType' => 'all',
'activeOnly' => true,
);
$params = array_merge($defaults, $opts);
$qOpts = array('conditions' => array());
//active only
if(!empty($params['activeOnly'])) $conditions[$this->alias.'.active'] = 1;
return $this->find($params['findType'], $qOpts);
}
(Pardon if there are many ways to make that code better - it was just off the top of my head - It gives you the idea.)
Keeping all your find()s in the Model also keeps you from having to search through each Controller every time you want to write a find() to determine if you've used a similar find() anywhere else. If you're programming as a team, that can be a nightmare, and you're almost guaranteed to be duplicating code.
It is perfectly fine to call Model->find() from a Controller. However, you will also want follow the DRY (Don't Repeat Yourself) principles. That basically means "Don't copy-paste code everywhere."
So, if you find that you need to make this exact Model->find() call from many Controller actions, it is considered good practice to abstract it into a function call against the Model. So yes, your Controllers would then call $this->User->findSomeCustomFunction().
I have built a simple Notification system in my Cake app that I want to have a function that will create a new notification when I call a certain method. Because this is not something the user would actually access directly and is only database logic I have put it in the Notification model like so:
class Notification extends AppModel
{
public $name = 'Notification';
public function createNotification($userId, $content, $url)
{
$this->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
}
And then whenever I want to create a notification within my app I just do:
$this->Notification->createNotification($userId,'Test','Test');
However this doesn't work! The controller is talking to the model fine, but it doesn't create the row in the database... I'm not sure why... but it would seem I'm doing this wrong by just doing all the code in the model and then calling it across the app.
Edit: Based on answers and comments below, I have tried the following the code to create a protected method in my notifications controller:
protected function _createNotification($userId, $content, $url)
{
$this->Notification->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
Now the thing that is stumping me still (apologies if this is quite simple to others, but I have not used protected methods in CakePHP before) is how do I then call this from another controller? So for example If have a method in my PostsController and want to create a notification on successful save, how would I do this?
I thought about in my PostsController add method:
if($this->save($this->request-data){
$this->Notification->_createNotification($userId,'Test','Test');
}
But being protected I wouldn't be able to access the method from outside of the NotificationsController. Also I'm using the same syntax as if I was calling a function from a model so again it doesn't feel right.
Hopefully someone can help me out and get me back on track as this is a new area to me.
the controller should pass all data to the model
$this->createNotification($this->request->data);
the model then can use the data:
public function createNotification(array $data) {
$key = $data[$this->alias]['key'];
$data[...] = ...;
$this->create();
return $this->save($data);
}
you never ever try to access the controller (and/or its request object) from within a model.
you can also invoke the method from other models, of course:
public function otherModelsMethod() {
$this->Notification = ClassRegistry::init('Notification');
$data = array(
'Notification' => array(...)
);
$this->Notification->createNotification($data);
}
and you can make your methods verbose, but that usually makes it harder to read/understand/maintain with more and more arguments:
public function createNotification($userId, $content, $url) {
$data = array();
// assign the vars to $data
$data['user_id'] = $userId;
...
$this->create();
return $this->save($data);
}
so this is often not the cake way..
Methods in a model are not "publicly accessible" by definition. A user cannot call or invoke a method in a model. A user can only cause a controller action to be initiated, never anything in the model. If you don't call your model method from any controller, it's never going to be invoked. So forget about the "non-public" part of the question.
Your problem is that you're working in the model as if you were in a controller. There is no request object in a model. You just pass a data array into the model method and save that array. No need for $this->request. Just make a regular array(), put the data that was passed by the controller in there and save it.
The whole approach is totally wrong in the MVC context IMO and screams for the use of the CakePHP event system. Because what you want is in fact trigger some kind of event. Read http://book.cakephp.org/2.0/en/core-libraries/events.html
Trigger an Event and attach a global event listener that will listen for this kind of events and execute whatever it should do (save something to db) when an event happens. It's clean, flexible and extendible.
If you did a proper MVC stack for your app most, if not all, events aka notifications should be fired from within a model like when a post was saved successfully for example.
This is what I have ended up doing. While it certainly isn't glamorous. It works for what I want it to do and is a nice quick win as the notifications are only used in a few methods so I'm not creating a large amount of code that needs improving in the future.
First to create a notification I do the following:
$notificationContent = '<strong>'.$user['User']['username'].'</strong> has requested to be friends with you.';
$notificationUrl = Router::url(array('controller'=>'friends','action'=>'requests'));
$this->Notification->createNotification($friendId,$notificationContent,$notificationUrl);
Here I pass the content I want and the URL where the user can do something, in this case see the friend request they have been notified about. The url can be null if it's an information only notification.
The createNotification function is in the model only and looks like:
public function createNotification($userId, $content, $url = null)
{
$this->saveField('user_id',$userId);
$this->saveField('content',$content);
$this->saveField('url',$url);
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
This creates a new record in the table with the passed content, sets its status to 0 (which means unread) and the date it was created. The notification is then set as read when a user visits the notifications page.
Again this is most probably not an ideal solution to the problem outlined in this question... but it works and is easy to work with And may prove useful to others who are learning CakePHP who want to run functions from models when building prototype apps.
Remember nothing to stop you improving things in the future!
First of all, you can improve your last solution to do one save() (instead of 5) the following way:
public function createNotification($userId, $content, $url = null){
$data = array(
'user_id' => $userId,
'content' => $content,
'url' => $url,
'datetime' => date('Y-m-d H:i:s'),
'status' => 0
);
$this->create();
$this->save($data);
}
When I began programming CakePHP(1.3) more than a year ago I also had this problem.
(I wanted to use a function of a controller in any other controller.)
Because I didn't know/researched where to place code like this I've done it wrong for over a year in a very big project. Because the project is really really big I decided to leave it that way. This is what i do:
I add a function (without a view, underscored) to the app_controller.php:
class AppController extends Controller {
//........begin of controller..... skipped here
function _doSomething(){
//don't forget to load the used model
$this->loadModel('Notification');
//do ur magic (save or delete or find ;) )
$tadaaa = $this->Notification->find('first');
//return something
return $tadaaa;
}
}
This way you can access the function from your Notification controller and your Posts controller with:
$this->_doSomething();
I use this kind of functions to do things that have nothing to do with data submittance or reading, so i decided to keep them in the app_controller. In my project these functions are used to submit e-mails to users for example.. or post user actions to facebook from different controllers.
Hope I could make someone happy with this ;) but if you're planning to make a lot of these functions, it would be much better to place them in the model!