3
votes

I have a User eloquent model that takes in an instance of the UserMailer class in its constructor but I get this error

Argument 1 passed to User::__construct() must be an instance of TrainerCompare\Mailers\UserMailer, none given, called in /var/www/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php on line 631 and defined

I understand the error but can't figure out what I have done wrong but I don't udnerstanding namespacing and composer class map vs psr0 autoloading very well. I have remembered to use composer dump-autoload so it is not that

relevant folder structure

composer.json
app/
  models/
    User.php
  TrainerCompare/
    Mailers/
      Mailer.php
      UserMailer.php
    Services/
      Validation/

composer.json autoload section. psr-0 section is there from when I added the validation service you can see in TrainerCompare/ and these classes work great. I added app/TrainerCompare/Mailers to the classmap per the tutorial I am following to get the mailer classes loaded

"autoload": {
        "classmap": [
            "app/commands",
            "app/controllers",
            "app/models",
            "app/database/migrations",
            "app/database/seeds",
            "app/tests/TestCase.php",
            "app/tests/helpers",
            "app/TrainerCompare/Mailers"
        ],
        "psr-0":{
            "TrainerCompare": "app/"
        }
    }

User.php

<?php

use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
use TrainerCompare\Mailers\UserMailer as Mailer;

class User extends BaseModel implements UserInterface, RemindableInterface
{
    protected $mailer;

    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }
}

Mailer.php

<?php namespace TrainerCompare\Mailers;

use Mail;

/**
* Email mailing class
*/
abstract class Mailer
{

    public function __construct()
    {
        # code...
    }

    public function sendTo($user, $subject, $view, $data = [])
    {
        Maill::send($view, $data, function ($message) use ($user, $subject) {
            $message->to($user->email)
                    ->subject($subject);
        });
    }
}

UserMailer.php

<?php namespace TrainerCompare\Mailers;

use User;

/**
* User Mailer Class
*/
class UserMailer extends Mailer
{

    public function __construct()
    {
        # code...
    }

    public function welcome($user)
    {
        $view = 'emails.users.welcome';
        $data = [];
        $subject = 'Welcome to Laracsts';

        return $this->sendTo($user, $subject, $view, $data);
    }
}
1
Could you post your Model.php as well?silkfire

1 Answers

5
votes

Eloquent (re)creates itself internally by calling:

new static

An example is in creating a new query:

return with(new static)->newQuery();

I'm not sure if automatic dependency resolution would work in this case, it should always work inside laravel, but as it also has it's own constructor method, you must at least forward a call to it and support the $attribute parameter:

public function __construct(array $attributes = array(), Mailer $mailer)
{
    $this->mailer = $mailer;

    parent::__construct($attributes);
}

EDIT

Opened an issue to understand it: https://github.com/laravel/framework/issues/3862

EDIT 2

As I've said in comment, you better create a service, as pointed by yourself, is a better application design. You should not be using your model to send e-mails. A service that receives a user model (or just name and e-mail) and send the message to that user would be a better way to go.

Answer given by Taylor Otwell about it in the issue:

Models aren't really meant to have dependencies injected into them that way. Kind of just the style of ActiveRecord style ORMs I guess. I would suggest passing the User to a Mailer class or something similar. Or if you're comfortable with it you could use App::make to grab an instance of Mail from the model instance, especially if you only need that dependency from a single method.