PHPMailer just keeps loading and returns nothing error wise!

I’m using the MVC format and just while i’m testing, i’m built a simple form where you submit your email and you get sent an email but for some reason when i submit it just keeps loading and then after a few minutes from timeout it returns no errors! Can someone tell me what i’m doing wrong?

Controller

public function reset()
	{
		if($_SERVER['REQUEST_METHOD'] == 'POST') {
			
			$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
			
			$data = [
				
				'email' => $email,
				'email_err' => '',
			];
			
			if(!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
				
				$data['email_err'] .= 'Invalid email address';
			}
			
			if(!$this->user->getUserByEmail($email) && filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
				
				$data['email_err'] .= 'Email address does not exist';
			}
			
			if(!empty($data['email_err'])) {
				
				$this->view('user/reset', $data);
				
			} else {
				
try
{
    $this->email->sendMail($data['email']);
    $this->view('user/login');
}
catch(\Exception $ex) // Global exception again here
{
    echo $ex->getMessage();
    die(); 

    // Later on, maybe pass the message into a "email not sent" view:
    $this->view('common/error');   // Or whatever your view name is
}
			
			}
			
		} else {
			
			$data = [
				
				'email' => '',
				'email_err' => ''
			];
			
			$this->view('user/reset', $data);
				
		}	
	}

Model/Class

<?php if (!defined('BASE_PATH')) exit('No direct script access allowed');

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require HELPERS_PATH.'vendor/autoload.php';

class Emails extends Model
{
    private $db;

    public function __construct()
    {
        $this->db = new Model();
    }
    
public function sendMail($email)
	{
		$mail = new PHPMailer(true);                              // Passing `true` enables exceptions
		try {
			//Server settings
			$mail->SMTPDebug = 3;                                 // Enable verbose debug output
			$mail->isSMTP();                                      // Set mailer to use SMTP
			$mail->Host = 'smtp.gmail.com';  // Specify main and backup SMTP servers
			$mail->SMTPAuth = true;                               // Enable SMTP authentication
			$mail->Username = 'email@gmail.com';                 // SMTP username
	    	$mail->Password = '*******';                           // SMTP password
			$mail->SMTPSecure = 'tls';                            // Enable TLS encryption, `ssl` also accepted
			$mail->Port = 587;                                    // TCP port to connect to

			//Recipients
			$mail->setFrom('mail@example.com');
			$mail->addAddress($email);     // Add a recipient              // Name is optional
		//	$mail->addReplyTo('mail@example.com');


			//Content
			$mail->isHTML(true);                                  // Set email format to HTML
			$mail->Subject = 'Forgotten password';
			$mail->Body    = 'This is the HTML message body <b>in bold!</b>';
			$mail->AltBody = 'This is the body in plain text for non-HTML mail clients';

			$mail->send();
} catch (Exception $e) {
    throw new \Exception($e->errorMessage()); // Wrap it into a global exception
} catch (\Exception $e) { // Global exception - see the leading slash
    throw $e;
}
	}
}

View:

    <form action="<?php echo FULL_ROOT . '/user/reset'; ?>" method="POST">
      <?php echo flash('restore_state'); ?>
      <div class="form-group row">
        <label for="EmailInput" class="col-sm-2 col-form-label">Email</label>
        <div class="col-sm-10">
          <input type="email" name="email" class="form-control" id="EmailInput" placeholder="Type your email" value="<?php echo $data['email']; ?>" />
          <a class='text text-danger'><?php echo $data['email_err']; ?></a>
        </div>
      </div>

      <div class="form-group row">
        <div style="margin-left: calc(16.6666667% + 15px);">
          <button type="submit" class="btn">Send me a link</button>
        </div>
      </div>
    </form>

One thing i know is that the PHPMailer is all up to date, my gmail email and password are fine and if i was to tamper with the file path i get an error saying not found so my code is finding the phpmailer just fine so i don’t know what is wrong?

I feel like this is incorrect. This may not solve the problem, but the design is not ideal. The point of using a model is to strictly draw or grab data from the database. What you are doing with yours is incorrect. That whole PHPMailer code should actually be in the controllers.

Thanks! You are right about that and i’m aware. I was just trying all possibilities but its very odd!

He is my helper alternative but i still get the same issue (just loading and then timing out):

<?php
// Import PHPMailer classes into the global namespace
// These must be at the top of your script, not inside a function
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\SMTP;

//Load Composer's autoloader
require '../vendor/autoload.php';

function sendEmail($email){     
    
$mail = new PHPMailer(true);
//Tell PHPMailer to use SMTP
$mail->isSMTP();
//Enable SMTP debugging
// 0 = off (for production use)
// 1 = client messages
// 2 = client and server messages
$mail->SMTPDebug = 2;
//Set the hostname of the mail server
$mail->Host = 'smtp.gmail.com';
// use
// $mail->Host = gethostbyname('smtp.gmail.com');
// if your network does not support SMTP over IPv6
//Set the SMTP port number - 587 for authenticated TLS, a.k.a. RFC4409 SMTP submission
$mail->Port = 587;
//Set the encryption system to use - ssl (deprecated) or tls
$mail->SMTPSecure = 'tls';
//Whether to use SMTP authentication
$mail->SMTPAuth = true;
//Username to use for SMTP authentication - use full email address for gmail
$mail->Username = "email@gmail.com";
//Password to use for SMTP authentication
$mail->Password = "********";
//Set who the message is to be sent from
$mail->setFrom('from@example.com', 'First Last');
//Set an alternative reply-to address
//$mail->addReplyTo('replyto@example.com', 'First Last');
//Set who the message is to be sent to
$mail->addAddress($email, 'John Doe');
//Set the subject line
//$mail->isHTML(true);    
$mail->Subject = 'PHPMailer GMail SMTP test';
//Read an HTML message body from an external file, convert referenced images to embedded,
//convert HTML into a basic plain-text alternative body
$mail->Body = 'This is the HTML message body <b>in bold!</b>';
//$mail->msgHTML(file_get_contents('contents.html'), __DIR__);
//Replace the plain text body with one created manually
$mail->AltBody = 'This is a plain-text message body';
//Attach an image file
//$mail->addAttachment('images/phpmailer_mini.png');
//send the message, check for errors
if (!$mail->send()) {
    echo "Mailer Error: " . $mail->ErrorInfo;
} else {
    echo "Message sent!";
    //Section 2: IMAP
    //Uncomment these to save your message in the 'Sent Mail' folder.
    #if (save_mail($mail)) {
    #    echo "Message saved!";
    #}
}

}

function sendEmail2($email, $subject){     
    

$message = '<html><body>';
$message .= '<h1>Hello!</h1>';
$message .= 'standard php mail';
$message .= '</body></html>';
            		   

$headers  = 'MIME-Version: 1.0' . "\r\n";

$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
$headers .= "From: Jack <noreply@".$_SERVER['HTTP_HOST']."> \n";

mail($email, $subject, $message, $headers);

}

The 2nd function works fine but in this day and age we should b using SMTP always so all help much appreciated in fixing this odd bug!

By controller do you mean something like this! And then the require '../vendor/autoload.php'; gets placed in the global file?

<?php if (!defined('BASE_PATH')) exit('No direct script access allowed');

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\SMTP;

class User extends Controller
{
    public $user;

    public function __construct()
    {
        $this->load_helper(['view']);
        $this->load_helper(['url']);
        $this->load_helper(['session']);
        //$this->load_helper(['email']);
        $this->setting = $this->model('Settings');
        $this->user = $this->model('Users');
    }
    
public function reset()
	{
		if($_SERVER['REQUEST_METHOD'] == 'POST') {
			
			$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
			
			$data = [
				
				'email' => $email,
				'email_err' => '',
			];
			
			if(!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
				
				$data['email_err'] .= 'Invalid email address';
			}
			
			if(!$this->user->getUserByEmail($email) && filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
				
				$data['email_err'] .= 'Email address does not exist';
			}
			
			if(!empty($data['email_err'])) {
				
				$this->view('user/reset', $data);
				
			} else {
				
try
{
    $this->sendEmail($data['email']);
redirect('');
}
catch(\Exception $ex) // Global exception again here
{
    echo $ex->getMessage();
    die(); 

    // Later on, maybe pass the message into a "email not sent" view:
    $this->view('common/error');   // Or whatever your view name is
}
			
			}
			
		} else {
			
			$data = [
				
				'email' => '',
				'email_err' => ''
			];
			
			$this->view('user/reset', $data);
				
		}	
	}

    public function sendEmail($email)
    {

$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->SMTPDebug = 2;
$mail->Host = 'smtp.gmail.com';
$mail->Port = 587;
$mail->SMTPSecure = 'tls';
$mail->SMTPAuth = true;
$mail->Username = "email@gmail.com";
$mail->Password = "*********";
$mail->setFrom('from@example.com', 'First Last');
$mail->addAddress($email, 'John Doe');
$mail->isHTML(true);    
$mail->Subject = 'PHPMailer GMail SMTP test';
$mail->Body = 'This is the HTML message body <b>in bold!</b>';
//$mail->msgHTML(file_get_contents('contents.html'), __DIR__);
$mail->AltBody = 'This is a plain-text message body';
if (!$mail->send()) {
    echo "Mailer Error: " . $mail->ErrorInfo;
} else {
    echo "Message sent!";
}

    }

}

Don’t do this. Redirecting after you have already output to the screen will give you a headers already sent error. This has been mentioned countless times on countless threads. You also shouldn’t be relying on try-catch blocks. You need to know how to debug your own code. try-catch blocks will not catch everything for you. Also, try indenting properly. This looks like you just copied and pasted it and don’t know what you’re doing. You need to step back and plan things out. This isn’t how you make a proper application “work”.

The first step to any successful project is

  • Plan.
  • Write down on paper and brain storm the idea.
  • Plan and plan some more.

That is how you write good code. “Stuffing” things where you “think” will work will not work! You have to understand your code before you start writing it. That’s why you should plan things before you start writing anything.

Sorry like I mentioned above this is just for testing purposes! Believe me I literally have OCD for messy code! Gotta be tabbed and everything must have it’s correct status. Thanks and please ignore the redirect! I copied and pasted in a hurry and then updated it two mins later so the redirect is no longer in there! Also I removed the try and catch as well ages ago as well because I realised it was not helping :grin:

It’s exactly what it looks like! I copied and pasted from my code to here and did not format correctly! Yes I have done tonnes of planning for this! But I guess you are right about one thing as I have not planned the email bit! I thought I could just wing it but I’m clearly wrong! But I think the main reason while I’m failing with this PHPMailer library is because I don’t fully understand how it works so I can’t debug it easily!

Anyway thanks for using your time to try to help me! Much appreciated and if i find the error I’ll post it on here! In the mean time I will just use the standard PHPMail function until I can get SMTP to work!

Thanks again!

What I don’t really like about PHPMailer is that what mail is sent is tightly to coupled to how mail is sent. Whereas for example with SwiftMailer you have a mailer object and a message object, and the mailer object can be used to send message objects. Meaning you can instantiate the mailer once and pass it as many messages as you like and it’ll send them all.

Of course you can keep overriding variables in PHPMailer as well, but that’s messy at best. What if you’ve sent message A, and then change it to message B, but forget to change the subject; you’ve then got message B with the subject of message A. Good luck debugging that one.

As far as your controller goes, I would inject it a mailer service. Something like this

class UserController
{
    private $userRepository;
    private $mailer;

    public function __construct(UserRepository $userRepository, Mailer $mailer)
    {
        $this->userRepository = $userRepository;
        $this->mailer = $mailer;
        $this->templating = $templating;
    }

    public function reset()
    {
        // check if the POST, filter email etc
        $this->mailer->send(
            new Message(
                ['from@example.com', 'First Last'],
                [$email, 'John Doe'],
                'PHPMailer GMail SMTP test',
                'This is the HTML message body in <b>bold</b>',
                'This is the plain test message body',
            )
        );
        $this->templating->render('done');
    }
}

and the Message would look something like this

final class Message
{
    public $from;
    public $to;
    public $title;
    public $htmlBody;
    public $plainTextBody;

    public function __construct($from, $to, $title, $htmlBody, $plainTextBody)
    {
        $this->from = $from;
        $this->to = $to;
        $this->title = $title;
        $this->htmlBody = $htmlBody;
        $this->plainTextBody = $plainTextBody;
    }
}

This is known as a Data Transfer Object or DTO for short - it doesn’t contain any logic, it’s just used to get a bunch of information from A to B in a coherent manner.

And then lastly the Mailer service

final class Mailer
{
    private $swiftMailer;
    
    public function __construct(Swift_Mailer $swiftMailer)
    {
        $this->swiftMailer = $swiftMailer;
    }
    
    public function send(Message $message)
    {
        $swiftMessage = new Swift_Message($message->subject)
            ->setFrom([$message->from[0] => $message->from[1]])
            ->setTo([$message->to[0] => $message->to[1]])
            ->setBody($message->htmlBody, 'text/html')
            ->addPart($message->plainTextBody, 'text/plain');

        $this->swiftMailer->send($swiftMessage);
    }
}

All of this has the following advantages:

  • The controller knows that it is able to send a message, but doesn’t know (or care) how it is sent, that is up to the Mailer class (see Single Responsibility Principle).
  • The Mailer class knows how to send messages using Swift Mailer, but as you can see it does not leak any information to the outside world that it is using Swift Mailer. The outside world shouldn’t care (See Abstraction and/or Encapsulation). This has the advantage that you could swap out Swift Mailer for any other mailer at any time without any of the other classes having to know about this (indeed, using a MailerInterface and having the Mailer class implement that interface would be even better, but goes a bit too far out of scope for this post).
  • The controller is mean and lean, easy to read because it’s just high level translation of HTTP to lower level application concerns (See Hexagonal Architecture)
3 Likes

Just thought i’d let you know, it was not my coding at all nor your wonderful code! I found out that my hosting blocks all SMTP servers that are not their own to prevent spam! I know ridiculous but basically the reason why it was loading for so long was because my host was rejecting it! Disgrace really that they did not even give an error message!

SO IF YOU EVER USE ONE.COM HOSTING YOU WILL BE FORCED TO USE THEIR SMTP SERVER! BAD!

var_dump($mail->send()) if it return false,means email is not send succussful

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.