This is a follow up of How to wait for a page reload in Laravel integration testing
What I am doing is to edit a user's profile and then redisplay the view.
My profile action: (UserController)
public function profile(){
return view('user/profile');
}
The view contains code like
{{ Auth::user()->firstname }}
now during my test, the old (unchanged) user data is displayed.
The test:
protected function editUserProfile()
{
$this->visit('/user/profile');
$firstName = $this->faker->firstname;
$lastname = $this->faker->lastname;
$this->within('#userEditForm', function() use ($firstName, $lastname) {
$this->type($firstName, 'firstname');
$this->type($lastname, 'surname');
$this->press('Save')
->seePageIs('/user/profile')
->see($firstName) # here the test fails
->see($lastname);
});
}
When I change the UserController like this:
public function profile(){
Auth::setUser(Auth::user()->fresh());
return view('user/profile');
}
everything works fine.
Now I want to understand, why that is like this.
Why does the integration test behave differently to the browser in that case? Is there a better way to align that behavior so the tests do only fail if there is a "real problem"? Or is my code just bad?
You're probably using update (int $uid) for the request?
The most likely explanation is that Laravel only uses a single application instance during the test. It's taking the input you give it, building a request object, and then sending it to the controller method. From here it can render the view and check that it contains your text.
In the authentication implementation once you call Auth::user() it doest one of two things:
If no user is loaded it attempts to retrieve it from storage.
If a user is already loaded it returns it.
Your update method (I'm guessing) is retrieving a new instance of the user from storage and updating it, not the cached one.
For example:
\Auth::loginUsingId(1234);
\Auth::user()->email; // 'user#example.com'
$user = \App\User::find(1234);
$user->email; // 'user#example.com';
$user->update(['email' => 'user2#example.com']);
$user->email; // 'user2#example.com'
\Auth::user()->email; // 'user#example.com'
Related
I am trying to make a global variable in AppServiceProvider.php that I will need throught my whole application meaning in all blade files. This variable is $profile which gets the profile data from user and displays them in blades. I made it so when I am on my profile it shows authenticated user which is me and it is fine (in url is like this profile/Authuser), that Authuser is username from database. Problem is when I go to some other profile then I get error undefined username (in url profile/Someuser). I need help on to get that username in AppServiceProvider.php. Problem is in that $username in service provider. I don't know how to pass it in there globally. Any help is appreciated. Here is my code.
AppServiceProvider.php
public function boot()
{
$profileId = $this->getIdFromUsername($username); // Here is problem, I don't know how to get that username
view()->composer('*', function ($view) {
$view->with('profile', Auth::id() ? UserProfile::profileDetails($profileId, Auth::user()->id) : []);
});
Builder::defaultStringLength(191); // Update defaultStringLength
}
public function getIdFromUsername($username)
{
if ($user = User::where('username', $username)->first()) {
return $user->id;
}
return abort(404);
}
web.php
Route::get('profile/{profile}', 'UserProfileController#showProfile')->name('profile.show');
I believe you are over complicating yourself.
If I understand your app. A user has a Profile correct?
Go to your User Model and create a relation between User and Profile
public function userProfile()
{
return $this->hasOne('App\UserProfile');
}
With that, the profile will follow the user, and you don't need to be passing it around.
If you want the Profile for the current User.
Auth::user()->userProfile();
If you want the profile of another user then
$owner = User::where('username', $username)->first();
$owner->userProfile();
Basically you can have access to the profile of your logged in user, or any other user easily by just finding the user you want.
Now, if you really wish to have a Model in every view, you are placing it in the wrong place. You see, Service Providers are intended to tie things up, not to get data. What you are probably thinking about is a View Composer that you do tie in with a Service Provider, but the actual data comes from the Composer itself. You can learn more about View Composer in the Docs. https://laravel.com/docs/7.x/views#view-composers
View Composers are just one way of doing it, a quick google search brought up this question which offers 3 additional alternatives to the view composer.
How to pass data to all views in Laravel 5?
Hope that helps.
I'm writing unit tests for an API using PHPUnit and Laravel. Most functions I'm testing require that the user is authenticated before the function can be ran. The user data is stored in one table, and their permissions are stored inside of another table. I can fake the user object inside of Laravel, but I need to be able to also pull the corresponding permissions from the other table without having to hit the database like the dingo router currently is doing.
Currently running Laravel 5.8 and PHPUnit 8.1.5. I currently have the users object that I generated from a Laravel factory saved to a text file. I am able to pass that to a function called "actingAsApi" (found on Github, code below) and that allows me to authenticate as that user. However, the function is still going out and getting all permissions for that user from the database. I'm trying to mock or fake the permissions object it is pulling somewhere so that it doesn't need to hit the database at all. I also tried using the built in Passport functions for Passport::actingAs, and those did not work either as they were still hitting the DB (and not really working anyways).
actingAsApi (inside of TestCase.php)
protected function actingAsApi($user)
{
// mock service middleware
$auth = Mockery::mock('Dingo\Api\Http\Middleware\Auth[handle]',
[
Mockery::mock('Dingo\Api\Routing\Router'),
Mockery::mock('Dingo\Api\Auth\Auth'),
]);
$auth->shouldReceive('handle')
->andReturnUsing(function ($request, \Closure $next) {
return $next($request);
});
$this->app->instance('Dingo\Api\Http\Middleware\Auth', $auth);
$auth = Mockery::mock('Dingo\Api\Auth\Auth[user]',
[
app('Dingo\Api\Routing\Router'),
app('Illuminate\Container\Container'),
[],
]);
$auth->shouldReceive('user')
->andReturnUsing(function () use ($user) {
return $user;
});
$this->app->instance('Dingo\Api\Auth\Auth', $auth);
return $this;
}
Test inside of my Test file
public function testActAs() {
$user = 'tests/users/user1.txt';
$this->actingAsApi($user);
$request = new Request;
$t = new TestController($request);
$test = $t->index($request);
}
I expect the actingAsApi function to allow me to also pass in the mock permissions data that corresponds to my mock user object data from the file, but instead it is hitting the database to pull from the permissions table.
EDIT:
So i've been playing around with doing mock objects, and i figured out how to mock the original controller here:
$controlMock = Mockery::mock('App\Http\Controllers\Controller', [$request])->makePartial();
$controlMock->shouldReceive('userHasPermission')
->with('API_ACCESS')
->andReturn(true);
$this->app->instance('App\Http\Controllers\Controller', $controlMock);
but now I can't figure out how to get my call from the other controllers to hit the mocked controller and not a real one. Here is my code for hitting an example controller:
$info = $this->app->make('App\API\Controllers\InfoController');
print_r($info->getInfo('12345'));
How can i make the second block of code hit the mocked controller and not standup a real one like it does in its constructor method?
Finally came on an answer, and it is now fixed. Here's how I did it for those wondering:
$request = new Request;
$controlMock = m::mock('App\API\Controllers\InfoController', [$request])->makePartial();
$controlMock->shouldReceive('userHasPermission')
->with('API_ACCESS')
->andReturn(true);
print_r($controlMock->getInfo('12345'));
Basically, I was trying to Mock the original API controller, and then catch all of the calls thrown at it. Instead, I should've been mocking the controller I'm testing, in this case the InfoController. I can then catch the call 'userHasPermission', which should reach out to the Controller, but I am automatically returning true. This eliminates the need for hitting the database to receive permissions and other info. More information on how I solved it using Mockery can be found here: http://docs.mockery.io/en/latest/cookbook/big_parent_class.html. As you can see, this is referred to as a 'Big Parent Class'. Good luck!
Odd issue here, I have a view content.home that relies on the authenticated user's User model to resolve and be passed to it.
Before the User model is ready, I have a simple function in which I pass a name to the authenticated user's User model.
That code is below, including the view return:
public function name()
{
$input = Input::all();
$name = $input['name'];
if(strlen($name)>2) //some validation
{
$user = User::where('id',Auth::id())->first();
$user->name = $name;
$user->save();
return view('content.home')->with('user', Auth::user());
}
}
My problem is, when I return the view I don't have the user's name. As soon as I refresh the page, it appears. Other user data provided by Auth::user() is there, but not the name. How can that be when I just saved it? It isn't NULL, again if I refresh the page right away it shows up.
I'm getting the name in the blade view like so:
{{$user->name}}
Is save() async? I don't think so. Is there some latency?
How can I make sure that the model being passed is properly resolved?
Thank you!
Answering this in case someone has a similar issue:
The reason this failed without a refresh is that there is some kind of cache on Auth::user().
When I pass the $user I saved to rather than the Auth::user(), even though it is the same user, it works.
So change return view('content.home')->with('user', Auth::user());
to
return view('content.home')->with('user', $user);
We are currently working on an application with a Google Login with Laravel with Socialite. We have a Auth user who gets a permission number ex. 264. We have made a function which returns an array with all binary numbers this permission number is made off.
Because calling this function every single time a page loads may be kinda heavy, we thought of adding this once when the Auth::user() is created. We thought of adding a custom constructor in the Model, but we can't make it work.
function __construct($attributes = array()) {
parent::__construct($attributes);
$this->permissionsArray = PermissionHelper::permissionConverter($this->permissions);
}
But we can't get it to work, $this doesn't have values when calling this function.
TLDR;
Directly after making the Auth user I want to call the permissionConverter function and save the data to the user so we can use it more often. Any suggestions on how to do this?
EDIT: I checked all answers out today, succeeded with one of them, but I assumed Laravel put the authenticated user in the SESSION or something. I found out it doesn't and it gets all the data from the database every request. We couldn't do what we requested for unfortunately. So I just had to refactor the script and make it as efficient as possible (although it became a bit less readable for less experienced programmers).
Thanks for the help :D
Maybe you can use this solution ? https://stackoverflow.com/a/25949698/7065748
Create a on the User Eloquent model a boot method with
class User extends BaseModel {
public static function boot() {
static::creating(function($model) {
$model->permissionsArray = PermissionHelper::permissionConverter($model->permissions);
});
// do the same for update (updating) if necessary
}
}
Can't you just use this method ?
If new user:
$user = new User(); // or User:create(['...']) directly
$user->name = 'toto';
// and all other data
or
$user = Auth::user();
then
$user->permissionsArray = PermissionHelper::permissionConverter($user->permissions);
$user->save();
I do have a UserController and User Model in my Laravel 5 source.
Also there is one AuthController is also Present (shipped prebuilt with laravel source).
I would like to query data from db in my blades making use of Eloquent Models.
However, Neither in my User Model (Eloquent ) nor in any of the controller, the user() method is defined. even then, I could use it in my blade by accessing it from Auth class. why?
For example,
in my blade, {{ Auth::user()->fname }} works. it retrieve the data fnamefrom my users table and echo it.
What is the logic behind it, and can i emulate the same for other db tables such as tasks?
Whenever you do it automatically or manually some like this
if (Auth::attempt(['email' => $email, 'password' => $password]))
{
}
The selected User's Data will be stored in the storage/framework/sessions
It will have data something like
a:4:{s:6:"_token";s:40:"PEKGoLhoXMl1rUDNNq2besE1iSTtSKylFFIhuoZu";s:9:"_previous";a:1:{s:3:"url";s:43:"http://localhost/Learnings/laravel5/laravel";}s:9:"_sf2_meta";a:3:{s:1:"u";i:1432617607;s:1:"c";i:1432617607;s:1:"l";s:1:"0";}s:5:"flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}
The above sessions file doesn't have any data and it will have the data such as user's id, url, token in json format.
Then whenever you call the {{ Auth::user()->fname }} Laravel recognises that you're trying to fetch the logged in user's fname then laravel will fetch the file and get the user's primary key and refer it from the user's table from your database. and you can do it for all coloumns of the users table that you have.
You can learn more about it here
This user function is defined under
vendor/laravel/framework/src/Illuminate/Auth/Guard.php
with following content :
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if ($this->loggedOut) return;
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method because that would tremendously slow an app.
if ( ! is_null($this->user))
{
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveById($id);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
if (is_null($user) && ! is_null($recaller))
{
$user = $this->getUserByRecaller($recaller);
if ($user)
{
$this->updateSession($user->getAuthIdentifier());
$this->fireLoginEvent($user, true);
}
}
return $this->user = $user;
}
this Guard.php has more functions defined in it which we use every now and then without even knowing where they are coming from
It works because Laravel comes with decent authentication.
Auth is the authentication library and has plenty of features like this, check out the documentation!