I am new to TDD, so excuse me if this is a noob question.
I have Users and Projects, related in 2 different ways:
A Project Belongs to a User
Many Users can be authorized to many Projects.
I am following this tutorial http://net.tutsplus.com/tutorials/php/testing-like-a-boss-in-laravel-models/?search_index=16 , so I am using FactoryMuff.
This is my Project class:
<?php
use LaravelBook\Ardent\Ardent;
class Project extends Ardent
{
/**
* Ardent validation rules
*/
public static $rules = array(
'name' => 'required', // Project Title
'user_id' => 'required|numeric', // User owner id
);
/**
* Array used by FactoryMuff to create Test objects
*/
public static $factory = array(
'name' => 'string',
'user_id' => 'factory|User', // Will be the id of an existent User.
);
/**
* Belongs to user
*/
public function owner()
{
return $this->belongsTo( 'User', 'user_id');
}
/**
* Many Users can be authorized
*/
public function authorized()
{
return $this->belongsToMany( 'User', 'project_user', 'project_id', 'user_id')->withTimestamps();
}
}
And this is my User class:
<?php
use Zizaco\Entrust\HasRole;
use Zizaco\Confide\ConfideUser;
class User extends ConfideUser {
/**
* Ardent validation rules
*/
public static $rules = array(
'username' => 'required|min:4|unique:users',
'email' => 'required|email|unique:users',
'password' => 'required|between:4,16|confirmed',
'password_confirmation' => 'required|between:4,16',
);
/**
* Array used by FactoryMuff to create Test objects
*/
public static $factory = array(
'username' => 'string',
'email' => 'email',
'password' => '12345',
'password_confirmation' => '12345',
);
(more code)
/**
* Many Users can be authorized on many Projects
*/
public function authorized()
{
return $this->belongsToMany( 'Project', 'project_user', 'user_id', 'project_id')->withTimestamps();
}
/**
* Users can have many Projects
*/
public function projects()
{
return $this->hasMany( 'Project');
}
}
And this is my ProjectTest:
<?php
use Zizaco\FactoryMuff\Facade\FactoryMuff;
use Way\Tests\Factory;
class ProjectTest extends TestCase
{
use Way\Tests\ModelHelpers;
/**
* Test relationship with User
*/
public function testRelationshipWithUser()
{
// Instantiate new Project
$project = FactoryMuff::create('Project');
$this->assertEquals($project->user_id, $project->owner->id);
}
/**
* Test relationship with Authorized Users
*/
public function testRelationshipWithAuthorizedUsers()
{
// Instantiate new Project
$project = FactoryMuff::create('Project');
$project->save();
$project->authorized()->attach($project->user_id);
$this->assertEquals($project->user_id, $project->authorized->first()->id);
}
}
If I run the tests individually (commenting the other) both pass. However, if I run both, I get this error:
Caused by
PDOException: SQLSTATE[HY000]: General error: 1 table users has no column named password_confirmation
Why is it complaining about that column in the second test and not in the first? :S
You need to set public $autoPurgeRedundantAttributes = true; on your User model.
Ardent (which Confide extends from) will automatically purge the _confirmation fields, but by default it is set to false.
Related
In my laravel project I use morphToMany relation to connect costs to the orders and products of the orders.
I have this migration:
Schema::create('costables', function (Blueprint $table) {
$table->unsignedBigInteger('cost_id');
$table->morphs('costable');
$table->foreign('cost_id')
->references('id')->on('costs')
->onUpdate('cascade')
->onDelete('cascade');
});
This is the Order model, with costs() method in it:
namespace Domain\Webshop\Models;
use Domain\Customer\Models\Address;
use Domain\Customer\Models\Member;
use Domain\Product\Models\Cost;
use Domain\Webshop\Database\Factories\OrderFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
class Order extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'member_id',
'shipping_address_id',
'billing_address_id',
'status',
];
public static $rules = [
'member_id' => 'required|integer',
'shipping_address_id' => 'sometimes|integer',
'billing_address_id' => 'nullable|integer',
'status' => 'required|integer',
];
protected $casts = [
'member_id' => 'integer',
'shipping_address_id' => 'integer',
'billing_address_id' => 'integer',
];
protected $hidden = ['created_at', 'updated_at', 'deleted_at'];
/**
* Create a new factory instance for the model.
*
* #return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory(): OrderFactory
{
return OrderFactory::new();
}
public function member(): BelongsTo
{
return $this->belongsTo(Member::class, 'member_id');
}
public function shippingAddress(): BelongsTo
{
return $this->belongsTo(Address::class, 'shipping_address_id');
}
public function costs(): MorphToMany
{
return $this->morphToMany(Cost::class, 'costable');
}
}
And this is my phpunit test, for testing costs on the Order and OrderProduct models. The $this->model is the actual model, I use this as a trait:
/** #test */
public function shouldHasCosts()
{
$item = $this->model::factory()
->hasCosts(1)
->create();
$this->assertInstanceOf(
Cost::class,
$item->first()->costs()->first(),
"It should be defined a relation called costs on $this->model"
);
}
My problem is, the test success in the Order model case, but fails at the OrderProduct. What have I forgotten, or whats wrong in my code?
I found the problem myself: in the test's setup there was a factory create for the OrderProduct, so in that case there was two rows in the database, and the shouldHasCosts() test is examine the first(), so it was wrong. I deleted the unnecessary create, and it has solved the problem.
In a Laravel project I want to use the Laravel authentication table 'users' to have a foreign field key to point to another table as layer of abstraction. Is there a way of forcing the user registration to add a row to that abstraction layer table? Its model is simple there is just one attribute.
My RegisterController:
` protected function create(array $data) {
Rekvirent::create([
'rekvirent' => $data['initialer'],
]);
return User::create([
'initialer' => $data['initialer'],
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
`
I get an error message from mysql that theres a foreign key error suggesting that the rekvirent has not been inserted when it gets to inserting the row in the users table.
My rekvirent model is as follows
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Rekvirent extends Model {
public $timestamps = false; // dumps timestamps from table
public $incrementing = false; // if no autoincrementing
protected $table = 'rekvirent'; // change default snake-case name
protected $keyType = 'string'; // if key is not integer
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'initialer',
];
/*
public function user() {
return $this->hasOne('App\User');
}
*/
}
If you are looking to do certain actions once a new user is registered, you can listen to events.
If you see Illuminate/Foundation/Auth/RegistersUsers.php :
/**
* Handle a registration request for the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
$this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath());
}
You will notice laravel is internally emitting an event Illuminate\Auth\Events\Registered. You can listen to that event and then do actions you need like inserting into a separate table etc.
See event documentation for listening an event.
In Laravel 5.4, they hard-coded the user authentication system, So when you use artisan command 'make:auth' everything will be created for you under the hood, but the thing is that i want when my user get registered successfully i want to create a new instance of 'Profile Class' and make the table columns empty until the user fills his profile, So where can I place the code for creating user profile?
In the RegisterController you can override the registered function.
This function is called directly after a user has successfully registered.
/**
* The user has been registered.
*
* #param \Illuminate\Http\Request $request
* #param mixed $user
* #return mixed
*/
protected function registered(Request $request, $user)
{
// Create a profile here
}
Alternatively, you could also do this with model events directly on the user model
class User extends Authenticatable
{
protected static function boot()
{
parent::boot();
static::creating(function($user) {
// Create profile here
});
}
}
In your app\Http\Controllers\Auth\RegisterController.php on the create() method right after you create a new user you can do this:
use App\Profile; // <-- Import this at the top
protected function create(array $data)
{
$user = User::create([ // <-- change this as you see fit
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
Profile::create(['user_id' => $user->id]);
return $user;
}
When I am taking an attempt to login it just redirecting to "admin" page with any value. I did all the possible try found in google. But still not having luck. I am badly in need of help. My code is given below :
Controller: LoginController.php
<?php
class LoginController extends BaseController {
public function doLogin()
{
$rules = ['username'=>'required','password'=>'required'];
$credentials = array(
'username' => Input::get('username'),
'password' => Input::get('password')
);
$validation = Validator::make($credentials, $rules);
if($validation->fails()){
return Redirect::back()
->withInput()
->withErrors($validation);
}
else{
Auth::attempt($credentials);
return Redirect::intended('admin');
}
}
public function doLogOut()
{
Auth::logout();
return Redirect::to('/login');
}
}
Model: User.php
<?php
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password', 'remember_token');
}
UserTableSeeder:
<?php
class UserTableSeeder extends Seeder {
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
$vader = DB::table('users')->insert([
'username' => 'admin',
'password' => Hash::make('admin'),
'created_at' => new DateTime(),
'updated_at' => new DateTime()
]);
}
}
Routes:
Route::post('login','LoginController#doLogIn');
Let's take a closer look at these lines:
else {
Auth::attempt($credentials);
return Redirect::intended('admin');
}
What you're doing in this snippet is
You try to log the user in.
Then you redirect, regardless of whether it worked or not.
If you want to make sure the user is actually logged in, you should wrap the attempt within an if clause.
I'm having some trouble getting my Laravel relationships to work out. In my application, there is a one-to-many relationship between users and ideas. (A user may have multiple ideas.) I'm using Ardent.
Here's my User model:
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
use LaravelBook\Ardent\Ardent;
class User extends Ardent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password', 'remember_token');
protected $fillable = array('first_name', 'last_name', 'email', 'password');
public $validation_errors;
public $autoPurgeRedundantAttributes = true;
public $autoHashPasswordAttributes = true;
public $autoHydrateEntityFromInput = true;
public static $passwordAttributes = array('password');
public static $rules = array(
'first_name' => 'required|between:1,16',
'last_name' => 'required|between:1,16',
'email' => 'required|email|unique:users',
'password' => 'required|between:6,100'
);
public function ideas()
{
return $this->hasMany('Idea');
}
}
And here's my Idea model:
use LaravelBook\Ardent\Ardent;
class Idea extends Ardent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'ideas';
protected $fillable = array('title');
public $validation_errors;
public $autoPurgeRedundantAttributes = true;
public $autoHydrateEntityFromInput = true;
public static $rules = array(
'title' => 'required'
);
public function user()
{
return $this->belongsTo('User');
}
}
Finally, here's my controller code:
class IdeasController extends BaseController {
public function postInsert()
{
$idea = new Idea;
$idea->user()->associate(Auth::user());
if($idea->save())
{
return Response::json(array(
'success' => true,
'idea_id' => $idea->id,
'title' => $idea->title),
200
);
}
else
{
return Response::json(array(
'success' => false,
'errors' => json_encode($idea->errors)),
400
);
}
}
}
$idea->save() throws the error:
{
"error": {
"type": "LogicException",
"message": "Relationship method must return an object of type Illuminate\\Database\\Eloquent\\Relations\\Relation",
"file": "\/var\/www\/3os\/vendor\/laravel\/framework\/src\/Illuminate\/Database\/Eloquent\/Model.php",
"line": 2498
}
}
At first, I was trying to set the user_id in the Idea like so:
$idea->user_id = Auth::id();
I then changed it to:
$idea->user()->associate(Auth::user());
But the results were the same.
Any suggestions would be much appreciated.
You cannot use associate in that direction, since it can only be used on a belongsTo relationship. In your case, an idea belongs to a user and not the other way around.
I suspect there is an error when saving, as you create an idea without the required title, and you then try to get the errors by calling $idea->errors, while it should be $idea->errors().
associate will work on belognsTo relationship , in your cause what you have to use is Attaching A Related Model. See more about Attaching A Related Mode in documentation.