Handle case where hook_mail() does not exist

Created on 11 October 2023, about 1 year ago
Updated 25 June 2024, 6 months ago

Problem/Motivation

I'm working on moving our platform over to this module from Swiftmailer. We have some tests to ensure how emails are sent (e.g. all outgoing messages need to be sent from an email within our primary domain) and those tests just directly trigger the core mail handler's mail method, like this:

$result = $this->container->get('plugin.manager.mail')->mail('uiowa_core', 'key', 'admin@example.com', 'en', $this->params);

When the tests run, I'm getting this error:

Error: Call to a member function fromArray() on null

The error can be traced back to the EmailBuilderManager::createInstanceFromMessage method. We have not created an EmailBuilder for the uiowa_core module because we don't actually send any emails from that module. Therefore the method never sets a value for the EmailBuilder to be used. The core mail handler allowed this sort of ad hoc mail sending. It seems like the EmailBuilder should be set to LegacyEmailBuilder if nothing else is found. This seems like the base case of email functionality to me.

I'm open to being persuaded that I should just rewrite our tests. However, I'm a bit nervous about doing so because I'm worried that we'll miss some edge case where an email is being sent in this fashion. I didn't find anything in the documentation that covers this particular case (everything is covered by writing EmailBuilders).

Steps to reproduce

Add custom code to send mail via the core mail handler's mail method and observe that no EmailBuilder can be assigned and therefore the email sending will fail.

Proposed resolution

TBD

Remaining tasks

TBD

User interface changes

TBD

API changes

TBD

Data model changes

TBD

✨ Feature request
Status

Active

Version

1.3

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States pyrello

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @pyrello
  • πŸ‡¬πŸ‡§United Kingdom adamps

    You don't have to write an EmailBuilder. This module will call hook_mail() if it can be found - see EmailBuilderManager::findDefinitions(), this comment:

    // Add definitions for any implementations of hook_mail() that don't
    // already have one, using LegacyEmailBuilder.
    

    Maybe something about your test environment causes a problem?

  • πŸ‡ΊπŸ‡ΈUnited States pyrello

    The test doesn't implement hook_mail(). Instead it just calls the mail() method directly. This case doesn't seem to be covered by this module.

    I could add a hook_mail() implementation to our test module. However, that also changes the test. Because it is technically possible to call the mai() method directly, the test would no longer cover that case.

  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 7.4 & MySQL 5.7
    last update about 1 year ago
    5 pass, 2 fail
  • @pyrello opened merge request.
  • πŸ‡ΊπŸ‡ΈUnited States pyrello

    @AdamPS, I have added a failing test to demonstrate what I'm talking about.

  • πŸ‡¬πŸ‡§United Kingdom adamps

    I believe the problem is the missing hook_mail() rather than that you are calling mail() directly. I don't currently feel this is an important case for this module to support. You can easily write an empty hook_mail() implementation and then calling directly should work.

  • πŸ‡ΊπŸ‡ΈUnited States pyrello

    I would say that the problem is that it is possible to call mail() without an existing implementation and it still works to send mail. Moreover it's been this way for a long time so my guess is that we are not the only ones that used it like this. It seems like this case should be covered even though it's not standard.

    Take a look at where the test I added above fails: https://git.drupalcode.org/project/symfony_mailer/-/blob/1.x/src/MailMan...

    The $builder->fromArray() method is called without a check to ensure that $builder actually got set to something. At a minimum, there should be a check here that allows it to gracefully fail or trigger an exception. I would contend instead that the EmailBuilderManager::createInstanceFromMessage() method should return the LegacyEmailBuilder if no other match is found.

    I'm happy to add that to my patch, but I was hoping to get some indication of whether this was the correct way to make sure that the LegacyEmailBuilder is used if nothing else can be found.

  • πŸ‡¬πŸ‡§United Kingdom adamps

    If you want to investigate a fix that's OK, but in my view it's minor so it's not something that's worth changing a lot of code and risking creating new bugs. I think there will be a problem that LegacyEmailBuilder isn't actually a plug-in instance with an ID so you can't create it. Maybe you can find a solution.

  • πŸ‡ͺπŸ‡ΈSpain sluc23

    I'm facing similar issue but in my case I use AEgir to create Drupal9/10 websites including symfony_mailer as part of the Drupal platform. AEgir installation succeed most of the process but it fails at the very end when it sends a welcome email to notify the site installation.. it tries to send the email as

    $mail_success = \Drupal::service('plugin.manager.mail')->mail('install', 'welcome-admin', $mailto, $langcode, $mail_params, $from, TRUE);
    

    ref: https://gitlab.com/aegir/aegir3/provision/-/blob/7.x-3.x/platform/drupal...

    and the result is similar as described here:

    Error: Call to a member function fromArray() on null in <drupal_root>/web/modules/contrib/symfony_mailer/src/MailManagerReplacement.php on line 89 #0 <provision_root>/platform/drupal/install_9.inc(42): Drupal\symfony_mailer\MailManagerReplacement->mail()
    #1 /data/disk/lspiegel/.drush/sys/provision/platform/drupal/install_9.inc(187): install_send_welcome_mail()
    . . . 
    

    Definitely this is a different use case as the described as part of the ticket in test suite, but seems to be similar causes.
    I'll probably find a workaround to overcome this problem.

  • πŸ‡©πŸ‡ͺGermany Anybody Porta Westfalica

    Just ran into the same issue: πŸ› Implement hook_mail (Error: Call to a member function fromArray() on null in Drupal\symfony_mailer\MailManagerReplacement->mail()) Active
    and also found πŸ’¬ fromArray() on null in MailManagerReplacement->mail() Closed: works as designed

    So I agree it would make sense to fix this or at least make the error message more clear.

  • πŸ‡©πŸ‡ͺGermany Anybody Porta Westfalica

    @AdamPS after thinking about this for a while, I think if EmailBuilderManager::createInstanceFromMessage() returns NULL that should presumably lead to an error / exception or log entry (or fallback) instead of calling the ->fromArray() method on NULL here:

        // Create an email from the array.
        $builder = $this->emailBuilderManager->createInstanceFromMessage($message);
        $email = $builder->fromArray($this->emailFactory, $message);
    

    What do you think?

    BTW in the watchdog_mailer case I don't really understand why that happens, as we have a hook_mail implementation with the correct key, but that's another topic... ;)

Production build 0.71.5 2024