Skip to content

RFC for refactoring Cake\Mailer in CakePHP 4.x #12114

@burzum

Description

@burzum

By spending some time with DDD, trying to build a hexagonal app with Cake and looking at how others frameworks do it I came across these things I'm not happy with in CakePHPs mailer code when I was working on my infrastructure layer that is used to send emails.

Architectural Issues

  • \Cake\Mailer\Email is a god object
    • It's aware of the transport
    • It's aware of rendering
  • It has ~3000 lines of code 😮

Proposed Change

  • Add \Cake\Mailer\Transport\TransportInterface
  • Add \Cake\Mailer\MailerInterface
  • Add \Cake\Mailer\ implements MailerInterface
  • Add \Cake\Mailer\EmailInterface
  • \Cake\Mailer\Email implements EmailInterface
  • \Cake\Mailer\Email should be a pure data transfer object
  • Move the rendering of an email to a dedicated class
    • Add an interface for that class
  • Add SendingFailedException

Benefits

  • This will make testing of each single component a lot more easier
  • Less coupling
  • Might be opinionated but I think it's a lot easier to understand than having this god object

Example

$mail = new Email();
$mail->setSubject('testing')
    ->setReceiver('burzum@cakephp.org')
    ->setFrom('noreply@cakephp.org')
    ->setFormat(EmailFormat::BOTH);

// MailRenderer will call Email::setHtmlContent() and/or setPlainContent()
$mailRenderer = new MailRenderer();
// Takes only instances of EmailInterface
$mailRenderer->set(['foo' => 'bar'])->render($mail);

// Alternatives?
$email->setHtmlContent($myContent);
$email->setHtmlContent($mailRenderer->render());

$mailer = new Mailer(new Transport());
$mailer->send($mail);

We can use another class and using __call() as a proxy to all of the three classes above to combine them in a fluid interface as we have it right now but with proper abstraction.

$mailer = new SomeMailer();
$mail
     // Not required, just if default needs to be changed
    ->setRender($renderer)
     // Not required, just if default needs to be changed
    ->setTransport($transport)
    ->setSubject('testing')
    ->setReceiver('burzum@cakephp.org')
    ->setFrom('noreply@cakephp.org')
    ->setViewVars(['foo' => 'bar'])
    ->send();

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions