What's the right way to work with MVC and Laravel - laravel

I'm building a CRUD using laravel and I'm not sure about the MVC rules.
I thought that all the functions related to database (Crud) should be done inside the model and not the controller. But I found this inside User's Controller:
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
I know it's not persisting to the database, just returning a new instance of the class User. Should I call this function inside the model so then persist it ?
Doesn't make much sense calling this to just make a ->save().

Your example is okay, but if you think your controller's doing too much work that it should not be doing, you can refactor your code to transfer the work.
For example, in your code, the password is being bcrypted, you can create a new function in User model (or another helper class if you want, like UserHelper or UserQuery)
class User ...
{
public static function registerUser($data)
{
$data['password'] = bcrypt($data['password']);
$user = self::create($data);
return $user;
}
}
You can now use this to directly pass user data, it will shoulder the bcrypting of the password.
$new_user = User::registerUser(['username' => 'helloworld', 'password' => 'worldhello']);
I think we should always put in mind if a class/method/any other point of control is doing something that is beyond its purpose, then that's the time we should think of refactoring it to another point of control.

Related

Can't get user while testing auth in laravel

I'm writing automated tests for a legacy laravel project, 5.8.38.
I have this test method.
public function testUserReceivesAnEmailWithAPasswordResetLink()
{
Notification::fake();
$user = factory(User::class)->create([
'email' => 'john#example.com',
]);
$this->post($this->passwordEmailPostRoute(), [
'email' => 'john#example.com',
]);
$this->assertNull($token = DB::table('password_resets')->first());
Notification::assertSentTo($user, ResetPassword::class, function ($notification, $channels) use ($token) {
return Hash::check($notification->token, $token->token) === true;
});
}
This always fails because the user cannot be retrieved. The passwordEmailPostRoute() method goes to the src/Illuminate/Auth/Passwords/PasswordBroker.php sendResetLink() method, eventually ending up in src/Illuminate/Auth/EloquentUserProvider.php at retrieveByCredentials() method.
This always returns null.
I tried dumping data and queries, but everything failed. Any ideas appreciated.
This seems to be a very specific issue which I caused for myself.
My user factory generated wrong values for a morph connection field which prevented the return of a valid User object. I had to change the factory and the issue is now resolved.

Simplify API boilerplate in Laravel's controllers?

When I write APIs with Laravel, I often use the same method as GitHub API v3. I add URLs to ease the navigation during development and also for the users that will develop using that API.
In this example, I manually add the URLs on every field then add a count for the pagers on the frontend. I sometime have more complicated stuff if I want to add the necessary to filter the results (if used with Vuetify or Kendo Grid).
class UserController extends Controller
{
function index() {
$users = User::all()->each(function ($item, $key) {
$item['activities'] = url("/api/users/{$item['id']}/activities");
$item['classrooms'] = url("/api/users/{$item['id']}/classrooms");
});
return [
'count' => count($users),
'courses' => $users,
];
}
}
Is there a way to make this code less boilerplate? Is there a package that does everything out of the box?
I'm a big fan of fractal especially spaties fractal package. This enables you to transform objects into responses.
There are two concepts in fractal serializers and transformers. Serializers is about the whole request, meta information data etc. Transformer is how you transform each model or object. Normally you would not make custom serializers, but in your case in can solve most of your trouble.
use League\Fractal\Serializer\ArraySerializer;
class YourSerializer extends ArraySerializer {
public function collection($resourceKey, array $data)
{
return [
$resourceKey => $data,
'count' => count($data),
];
}
}
Create your transformer for your user. The other big thing you gain for using this, is you have one plays to be responsible for transformation. Instead of each controller having to have the logic, which will be spread out and you have to remember to include it.
use League\Fractal\TransformerAbstract;
class AccountBalanceTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'id' => $user->id,
'name' => $user->id,
// other user fields
'activities' => url("/api/users/{$user->id}/activities"),
'classrooms' => url("/api/users/{$user->id}/classrooms"),
];
}
}
You have to assign the serializer in your fractal.php config.
'default_serializer' => YourSerializer::class,
Now you should be able to transform you responses like so.
return fractal($users, new UserTransformer())
->withResourceName('courses')
->respond(Response::HTTP_OK);
For making it easier to use and avoid repeating your self, i usually do a method on a parent controller like so. While setting the transformer on the object.
public function respond($data, $resourceKey, $status = Response::HTTP_OK) {
return fractal($data, $this->transformer)
->withResourceName($resourceKey)
->respond($status);
}
This will produce a response similar to what was specified in the question.

Should processing logic of STRIPE be in the Laravel controller or in the validator?

I am a newcomer to laravel. I have a controller ProductController like this
public function buy(Request $request, User $user) {
\Stripe\Stripe::setApiKey("sk_test_xxxxxxxxxxxxxxxxxxxxxxxx");
$token = $_POST['stripeToken'];
$charge = \Stripe\Charge::create([
'amount' => 100,
'currency' => 'aud',
'description' => 'Example charge',
'source' => $token,
]);
if ($charge->status === "succeeded") {
//-- Processing... --//
}
I would like to ask the more appropriate design style, should I put the part of STTRIE in other places, such as the validator.
If yes, it is to make a rule and a request than verify it in the validator ?
Can someone tell me how to use the rule in the request?
Creating a Stripe charge is not request validation. It's an API call to Stripe. So, it should definitely not stay in the validator.
You can have this logic in a controller for small apps, but for medium/large scale apps with abstraction (e.g. if you want to have the option later to change the payment provider from Stripe to say Braintree), it should be in a service class.
Also, never use $_POST directly. Use $request->input instead. As a thumb rule, if you have 2 ways to do something in code, always use the way that implements higher level libraries (libraries > then framework > then core PHP).
To write the validation using Laravel 5.5+
public function buy(Request $request, User $user)
{
// first define your rules
$rules = [
'amount' => 'required|numeric'
];
$validatedData = $request->validate($rules);
// The purchase is valid...
}
For Laravel 5.0 - 5.4:
public function buy(Request $request, User $user)
{
// first define your rules
$rules = [
'amount' => 'required|numeric'
];
$validatedData = $this->validate($rules);
// The purchase is valid...
}
The typical responsibilities of a controller in my opinion:
Take in a request
Return a response
I think it is probably fine to have one conditional or validation check.

Unit testing user login in Laravel

Users can only log into the system when their account is active. I use a factory to create a user (initially their status is set to 'pending') and then try to post to the login form.
From here, I'm not too sure what assertions I need to do. I have already written the code that does this functionality (I have a custom Validator in the validateLogin form) and then it throws an error message something along the lines of 'your account is not active'.
What's the best way to approach this? What I have so far:
/** #test */
public function it_can_only_login_if_the_status_is_active()
{
$user = factory(User::class)->create();
$this->post('login', ['email' => $user->email, 'password' => $user->password, '_token' => csrf_token()]);
}
With the build in Laravel testing methods you could do this:
$user = factory(User::class)->create();
$this->visit('/login')
->type($user->email, 'email')
->type($user->password, 'password')
->press('Login')
->see('your account is not active');
I can recommend the Codeception library if you need to test some complicated features, because with it you can verify results in the DB, create acceptance and functional tests.

non-static method basemodel::validate() should not be called statically,assuming $this from compatible context

Was tryin to validate my registration form calling the validation method from the basemodel in my controller
The method
public function postSIgnup ()
{
$validation = User::validate(Input::all());
}
Routes
Route::post('register', array('before=>'csrf', 'uses'=>'UsersController#postSignup'));
Help mi solve this problem
You can't just say 'validate my whole form'.
The reason this error occurs is because you are trying to use the validation method from Laravel.
Basic Form Validation in Laravel
First you want to grab all your form data/content.
$input = Input::all();
Secondly you can setup some rules. Those rules can be set in an array which Laravel will use.
Make sure the names are correctly spelled. (Those are the ones u used in your form-template.)
$rules = array(
'real_name' => 'Required|Min:3|Max:80|Alpha',
'email' => 'Required|Between:3,64|Email|Unique:users',
'age' => 'Integer|Min:18',
'password' =>'Required|AlphaNum|Between:4,8|Confirmed',
'password_confirmation'=>'Required|AlphaNum|Between:4,8'
);
To make use of the validator you have to create anew instance first:
You attach the form input and the rules you have set above.
When they match you can save your form to your database or what else you would like to do with it. That will probably look like:
$validator = Validator::make($input,$rules);
Cool,
We can check now if the Validator passes or not...
if($validator->fails()){
$messages = $validator->messages();
return Redirect::to('yourpage')->withErrors($messages);
}else{
// Handle your data...
}

Resources