nameless nameless - 9 days ago 4
PHP Question

Laravel Auth modify for two kinds of users

I'm currently trying to modify the laravel Auth two be able to register two different kinds of users, a seller and a buyer. Both have the same form, except one field, that only the seller has, called

companyName
.

So what I did is putting a dropdown for registration instead of the normal register button, this is what I got there:

<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Registrieren
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li>
<a href="{{url('/register/customer')}}">Als Käufer</a>
<a href="{{url('/register/seller')}}">Als Händler</a>
</li>
</ul>
</div>


Then I made a route for this two kinds of registrations, like this:

Route::get('/register/{userType}', 'Auth\RegisterController@showRegistrationForm');


In the controller then I simply overwrote this
showRegistrationForm
function to pass the
userType
into my view, just like that:

public function showRegistrationForm($userType)
{
return view('auth.register', ['userType'=> $userType]);
}


And in my view, I got this:

@extends('master')

@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Registrieren
als <?php if ($userType == 'customer') echo "Käufer";if ($userType == 'seller') echo "Verkäufer";?></div>
<div class="panel-body">
<form class="form-horizontal" role="form" method="POST" action="{{ url('/register') }}">
{{ csrf_field() }}

<div class="form-group{{ $errors->has('sex') ? ' has-error' : '' }}">
<label for="sex" class="col-md-4 control-label">Anrede</label>
<div class="col-md-6">

<select class="form-control" id="sex">
<option value="male">Herr</option>
<option value="female">Frau</option>
</select>
</div>

@if ($errors->has('sex'))
<span class="help-block">
<strong>{{ $errors->first('sex') }}</strong>
</span>
@endif
</div>

<div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
<label for="firstName" class="col-md-4 control-label">Vorname</label>

<div class="col-md-6">
<input id="firstName" type="text" class="form-control" name="firstName"
value="{{ old('firstName') }}" required autofocus>

@if ($errors->has('firstName'))
<span class="help-block">
<strong>{{ $errors->first('firstName') }}</strong>
</span>
@endif
</div>
</div>

<div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}">
<label for="name" class="col-md-4 control-label">Nachname</label>

<div class="col-md-6">
<input id="name" type="text" class="form-control" name="name"
value="{{ old('name') }}" required autofocus>

@if ($errors->has('name'))
<span class="help-block">
<strong>{{ $errors->first('name') }}</strong>
</span>
@endif
</div>
</div>

<?php if ($userType == 'seller'){ ?>
<div class="form-group{{ $errors->has('companyName') ? ' has-error' : '' }}">
<label for="companyName" class="col-md-4 control-label">Firmenname</label>

<div class="col-md-6">
<input id="companyName" type="text" class="form-control" name="companyName"
value="{{ old('companyName') }}" required autofocus>

@if ($errors->has('companyName'))
<span class="help-block">
<strong>{{ $errors->first('companyName') }}</strong>
</span>
@endif
</div>
</div>
<?php } ?>

<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-4 control-label">E-Mail Addresse</label>

<div class="col-md-6">
<input id="email" type="email" class="form-control" name="email"
value="{{ old('email') }}" required>

@if ($errors->has('email'))
<span class="help-block">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
</div>
</div>

<div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
<label for="password" class="col-md-4 control-label">Passwort</label>

<div class="col-md-6">
<input id="password" type="password" class="form-control" name="password" required>

@if ($errors->has('password'))
<span class="help-block">
<strong>{{ $errors->first('password') }}</strong>
</span>
@endif
</div>
</div>

<div class="form-group{{ $errors->has('password_confirmation') ? ' has-error' : '' }}">
<label for="password-confirm" class="col-md-4 control-label">Passwort
wiederholen</label>

<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control"
name="password_confirmation" required>

@if ($errors->has('password_confirmation'))
<span class="help-block">
<strong>{{ $errors->first('password_confirmation') }}</strong>
</span>
@endif
</div>
</div>

<div style="display:none;" class="form-group{{ $errors->has('role') ? ' has-error' : '' }}">
<label for="role" class="col-md-4 control-label">Deine Rolle:</label>

<div class="col-md-6">
<input name="role" type="radio"
<?php if ($userType == 'customer') echo "checked";?> value="Käufer">&nbsp;Käufer<br/>
<input name="role" type="radio"
<?php if ($userType == 'seller') echo "checked";?> value="Verkäufer">&nbsp;Verkäufer

@if ($errors->has('role'))
<span class="help-block">
<strong>{{ $errors->first('role') }}</strong>
</span>
@endif
</div>
</div>

<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Registrieren
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection


So mostly basic, just few more fields then with the auth without modification and the
companyName
only showing up when accessed over the route
/register/seller
.

My RegisterController is of course also a bit modified, or especially the create function, it looks like this now:

protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'firstName' => $data['firstName'],
'sex' => $data['sex'],
'email' => $data['email'],
'username' => $data['username'],
'password' => bcrypt($data['password']),
'role' => $data['role'],
'templateURL' => ""
]);

if($data['role'] == 'Verkäufer'){
Complaint::create([
'user_id' => $user->id,
'complaintCount' => 0
]);
}

switch($data['role']){
case 'Käufer':
$user->attachRole(2);
break;
case 'Verkäufer':
$user->attachRole(3);
$user->companyName = $data['companyName'];
$user->save();
break;
default:
$user->attachRole(2);
break;
}


return $user;
}


And now comes the problem: As you can see, in my "invidual" views, which is basically just one anyway, I still post to the url
/register
, which I thought should work, but it doesn't.... Any ideas why this is not working? I also tried to add individual routes, so something like that:

Route::post('/register/seller', 'Auth\RegisterController@create');
Route::post('/register/buyer', 'Auth\RegisterController@create');


but thats not working as well. In both cases, I just get the same window back, as if there was an error (so my data still entered (expect the password), but nothing is registered or entered in the db, but as well there are no errors showing up, neither in my view, nor in the console.

What's interesting as well is the network tab, it seems like it definetely sends the request to
/register
as it shows up there with status code 302, but as well there's the route
/register/customer
again, and I'm wondering why...What's also interesting is that I think that somehow it kinda works, as if I enter a password with less then 6 characters or 2 different passwords, I get an error, so somehow the form seems to be posted, but nothing is entered into the db....

Any ideas why this happens like this and whats the problem?

Answer

First of all, I'd like to suggest you a Polymorphic approach to saving the users. Right now, you have only 2 user types, what if you get a third user type (say retailer or wholesaler or blah blah)... and for each of them, you will require different fields which may or may not fit in for all user types...

So, go with this

class User
{
  public function profile()
  {
    return $this->morphTo();
  }
}

class Seller
{
  public function user()
  {
    return $this->morphOne('App\User', 'profile');
  }
}

class Buyer
{
  public function user()
  {
    return $this->morphOne('App\User', 'profile');
  }
}

Now, In your routes, add these

Route::get('login', 'LoginController@show')->name('login.show');
Route::post('login', 'LoginController@login')->name('login.post');

Route::get('register', 'RegisterController@show')->name('register.show');
Route::post('register', 'RegisterController@register')->name('register.post');

Route::get('logout', 'LoginController@logout')->name('login.logout');

Now, in your form add a dropdown/radio button for User Type selection (you can also make seprate and run different routes and make these fields hidden);

Say,

<select name="type">
  <option value="1">Buyer</option>
  <option value="2">Seller</option>
<select>

Your RegisterController@register can be as follows:

use App\Buyer;
use App\Seller;
use Validator;

class RegisterController extends Controller
{
  public function show()
  {
    return view('auth.register');
  }

  public function register()
  {
    $inputs = request()->all();

    $validator = $this->validator($inputs);

    if($validator->fails()) {
      return redirect()->back()->withErrors($validator)->withInput();
    }

    $userInputs = array_only($inputs, ['name', 'email', 'password']);
    $userInputs['password'] = Hash::make($userInputs['password']);

    switch($inputs['type'])
    {
      case 1:
        $sellerInputs = array_only($inputs, ['company_name']);

        $seller = Seller::create();
        $user = $seller->user()->create($userInputs);
      case 2: 
        $buyer = Buyer::create();
        $user = $buyer->user()->create($userInputs);
      default:
        $user = null;
        break;
    }

    if(!$user) {
      return redirect()->back(); //Show flash messsage etc... and redirect back to show an error
    }

    auth()->attempt(array_only($inputs, ['email', 'password']));
    return redirect(route('some.route'));
  }

  protected validator($inputs)
  {
    $rules = [
      'name' => 'required|min:1|max:50',
      'email' => 'required|email|min:1|max:100',
      'password' => 'required|min:6|max:25',
      // Other rules
    ];

    $messages = [
      // Any special messages if required...
    ];

    return Validator::make($inputs, $rules, $messages);
  }
}

Use same kind of coding structure in LoginController... I am simply going to write the login behind logging in below

public function login()
{
  $inputs = request()->all();

  //Validator etc...

  if(auth()->attempt(array_only($inputs, ['email', 'password']))) {
    return redirect(route('some.route'));
  } else {
    return redirect()->back(); // Again... Show some error flash message
  }
}

Note :- I have not tested the code but I am 99% sure this should work... I wrote all this down myself... Took a damn half an hour almost!

Hope everything is answered and you understood. Let me know in the comments below if you have any other query :)