Browse Source

Add TransportFactory and update Email class to use it.

This offloads the responsibility of generating transport instances
to the factory class and related methods on Email class are now deprecated.
ADmad 7 years ago
parent
commit
471b1f00a4
3 changed files with 201 additions and 146 deletions
  1. 21 106
      src/Mailer/Email.php
  2. 114 0
      src/Mailer/TransportFactory.php
  3. 66 40
      tests/TestCase/Mailer/EmailTest.php

+ 21 - 106
src/Mailer/Email.php

@@ -15,7 +15,6 @@
 namespace Cake\Mailer;
 
 use BadMethodCallException;
-use Cake\Core\App;
 use Cake\Core\Configure;
 use Cake\Core\StaticConfigTrait;
 use Cake\Filesystem\File;
@@ -293,24 +292,6 @@ class Email implements JsonSerializable, Serializable
     protected $_priority;
 
     /**
-     * An array mapping url schemes to fully qualified Transport class names
-     *
-     * @var array
-     */
-    protected static $_dsnClassMap = [
-        'debug' => 'Cake\Mailer\Transport\DebugTransport',
-        'mail' => 'Cake\Mailer\Transport\MailTransport',
-        'smtp' => 'Cake\Mailer\Transport\SmtpTransport',
-    ];
-
-    /**
-     * Configuration profiles for transports.
-     *
-     * @var array
-     */
-    protected static $_transportConfig = [];
-
-    /**
      * A copy of the configuration profile for this
      * instance. This copy can be modified with Email::profile().
      *
@@ -1560,7 +1541,7 @@ class Email implements JsonSerializable, Serializable
     public function setTransport($name)
     {
         if (is_string($name)) {
-            $transport = $this->_constructTransport($name);
+            $transport = TransportFactory::get($name);
         } elseif (is_object($name)) {
             $transport = $name;
         } else {
@@ -1612,61 +1593,6 @@ class Email implements JsonSerializable, Serializable
     }
 
     /**
-     * Build a transport instance from configuration data.
-     *
-     * @param string $name The transport configuration name to build.
-     * @return \Cake\Mailer\AbstractTransport
-     * @throws \InvalidArgumentException When transport configuration is missing or invalid.
-     */
-    protected function _constructTransport($name)
-    {
-        if (!isset(static::$_transportConfig[$name])) {
-            throw new InvalidArgumentException(sprintf('Transport config "%s" is missing.', $name));
-        }
-
-        if (!isset(static::$_transportConfig[$name]['className'])) {
-            throw new InvalidArgumentException(
-                sprintf('Transport config "%s" is invalid, the required `className` option is missing', $name)
-            );
-        }
-
-        $config = static::$_transportConfig[$name];
-
-        if (is_object($config['className'])) {
-            if (!$config['className'] instanceof AbstractTransport) {
-                throw new InvalidArgumentException(sprintf(
-                    'Transport object must be of type "AbstractTransport". Found invalid type: "%s".',
-                    get_class($config['className'])
-                ));
-            }
-
-            return $config['className'];
-        }
-
-        $className = App::className($config['className'], 'Mailer/Transport', 'Transport');
-        if (!$className) {
-            $className = App::className($config['className'], 'Network/Email', 'Transport');
-            if ($className) {
-                trigger_error(
-                    'Transports in "Network/Email" are deprecated, use "Mailer/Transport" instead.',
-                    E_USER_DEPRECATED
-                );
-            }
-        }
-
-        if (!$className) {
-            throw new InvalidArgumentException(sprintf('Transport class "%s" not found.', $config['className']));
-        }
-        if (!method_exists($className, 'send')) {
-            throw new InvalidArgumentException(sprintf('The "%s" does not have a send() method.', $className));
-        }
-
-        unset($config['className']);
-
-        return new $className($config);
-    }
-
-    /**
      * Sets message ID.
      *
      * @param bool|string $message True to generate a new Message-ID, False to ignore (not send in email), String to set as Message-ID.
@@ -1994,33 +1920,13 @@ class Email implements JsonSerializable, Serializable
      * @param array|\Cake\Mailer\AbstractTransport|null $config Either an array of configuration
      *   data, or a transport instance. Null when using key as array.
      * @return void
-     * @throws \BadMethodCallException When modifying an existing configuration.
+     * @deprecated 3.7.0 Use TransportFactory::setConfig() instead.
      */
     public static function setConfigTransport($key, $config = null)
     {
-        if (is_array($key)) {
-            foreach ($key as $name => $settings) {
-                static::setConfigTransport($name, $settings);
-            }
+        deprecationWarning('Email::setConfigTransport() is deprecated. Use TransportFactory::setConfig() instead.');
 
-            return;
-        }
-
-        if (isset(static::$_transportConfig[$key])) {
-            throw new BadMethodCallException(sprintf('Cannot modify an existing config "%s"', $key));
-        }
-
-        if (is_object($config)) {
-            $config = ['className' => $config];
-        }
-
-        if (isset($config['url'])) {
-            $parsed = static::parseDsn($config['url']);
-            unset($config['url']);
-            $config = $parsed + $config;
-        }
-
-        static::$_transportConfig[$key] = $config;
+        TransportFactory::setConfig($key, $config);
     }
 
     /**
@@ -2028,10 +1934,13 @@ class Email implements JsonSerializable, Serializable
      *
      * @param string $key The configuration name to read.
      * @return array|null Transport config.
+     * @deprecated 3.7.0 Use TransportFactory::getConfig() instead.
      */
     public static function getConfigTransport($key)
     {
-        return isset(static::$_transportConfig[$key]) ? static::$_transportConfig[$key] : null;
+        deprecationWarning('Email::getConfigTransport() is deprecated. Use TransportFactory::getConfig() instead.');
+
+        return TransportFactory::getConfig($key);
     }
 
     /**
@@ -2048,7 +1957,7 @@ class Email implements JsonSerializable, Serializable
      * The `className` is used to define the class to use for a transport.
      * It can either be a short name, or a fully qualified classname
      *
-     * @deprecated 3.4.0 Use setConfigTransport()/getConfigTransport() instead.
+     * @deprecated 3.4.0 Use TransportFactory::setConfig()/getConfig() instead.
      * @param string|array $key The configuration name to read/write. Or
      *   an array of multiple transports to set.
      * @param array|\Cake\Mailer\AbstractTransport|null $config Either an array of configuration
@@ -2058,28 +1967,31 @@ class Email implements JsonSerializable, Serializable
      */
     public static function configTransport($key, $config = null)
     {
-        deprecationWarning('Email::configTransport() is deprecated. Use Email::setConfigTransport() or Email::getConfigTransport() instead.');
+        deprecationWarning('Email::configTransport() is deprecated. Use TransportFactory::setConfig() or TransportFactory::getConfig() instead.');
 
         if ($config === null && is_string($key)) {
-            return static::getConfigTransport($key);
+            return TransportFactory::getConfig($key);
         }
         if ($config === null && is_array($key)) {
-            static::setConfigTransport($key);
+            TransportFactory::setConfig($key);
 
             return null;
         }
 
-        static::setConfigTransport($key, $config);
+        TransportFactory::setConfig($key, $config);
     }
 
     /**
      * Returns an array containing the named transport configurations
      *
      * @return array Array of configurations.
+     * @deprecated 3.7.0 Use TransportFactory::configured() instead.
      */
     public static function configuredTransport()
     {
-        return array_keys(static::$_transportConfig);
+        deprecationWarning('Email::configuredTransport() is deprecated. Use TransportFactory::configured().');
+
+        return TransportFactory::configured();
     }
 
     /**
@@ -2087,10 +1999,13 @@ class Email implements JsonSerializable, Serializable
      *
      * @param string $key The transport name to remove.
      * @return void
+     * @deprecated 3.7.0 Use TransportFactory::drop() instead.
      */
     public static function dropTransport($key)
     {
-        unset(static::$_transportConfig[$key]);
+        deprecationWarning('Email::dropTransport() is deprecated. Use TransportFactory::drop().');
+
+        TransportFactory::drop($key);
     }
 
     /**

+ 114 - 0
src/Mailer/TransportFactory.php

@@ -0,0 +1,114 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link          https://cakephp.org CakePHP(tm) Project
+ * @since         3.7.0
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Mailer;
+
+use Cake\Core\StaticConfigTrait;
+use InvalidArgumentException;
+
+/**
+ * Factory class for generating email transport instances.
+ */
+class TransportFactory
+{
+    use StaticConfigTrait;
+
+    /**
+     * Transport Registry used for creating and using transport instances.
+     *
+     * @var \Cake\Core\ObjectRegistry
+     */
+    protected static $_registry;
+
+    /**
+     * An array mapping url schemes to fully qualified Transport class names
+     *
+     * @var array
+     */
+    protected static $_dsnClassMap = [
+        'debug' => 'Cake\Mailer\Transport\DebugTransport',
+        'mail' => 'Cake\Mailer\Transport\MailTransport',
+        'smtp' => 'Cake\Mailer\Transport\SmtpTransport',
+    ];
+
+    /**
+     * Returns the Transport Registry used for creating and using transport instances.
+     *
+     * @return \Cake\Core\ObjectRegistry
+     */
+    public static function getRegistry()
+    {
+        if (!static::$_registry) {
+            static::$_registry = new TransportRegistry();
+        }
+
+        return static::$_registry;
+    }
+
+    /**
+     * Sets the Transport Registry instance used for creating and using transport instances.
+     *
+     * Also allows for injecting of a new registry instance.
+     *
+     * @param \Cake\Core\ObjectRegistry $registry Injectable registry object.
+     * @return void
+     */
+    public static function setRegistry(ObjectRegistry $registry)
+    {
+        static::$_registry = $registry;
+    }
+
+    /**
+     * Finds and builds the instance of the required tranport class.
+     *
+     * @param string $name Name of the config array that needs a tranport instance built
+     * @return void
+     * @throws \InvalidArgumentException When a tranport cannot be created.
+     */
+    protected static function _buildTransport($name)
+    {
+        if (!isset(static::$_config[$name])) {
+            throw new InvalidArgumentException(
+                sprintf('The "%s" transport configuration does not exist', $name)
+            );
+        }
+
+        if (is_array(static::$_config[$name]) && empty(static::$_config[$name]['className'])) {
+            throw new InvalidArgumentException(
+                sprintf('Transport config "%s" is invalid, the required `className` option is missing', $name)
+            );
+        }
+
+        static::getRegistry()->load($name, static::$_config[$name]);
+    }
+
+    /**
+     * Get transport instance.
+     *
+     * @param string $name Config name.
+     * @return \Cake\Email\AbstractTransport
+     */
+    public static function get($name)
+    {
+        $registry = static::getRegistry();
+
+        if (isset($registry->{$name})) {
+            return $registry->{$name};
+        }
+
+        static::_buildTransport($name);
+
+        return $registry->{$name};
+    }
+}

+ 66 - 40
tests/TestCase/Mailer/EmailTest.php

@@ -19,6 +19,7 @@ use Cake\Core\Plugin;
 use Cake\Log\Log;
 use Cake\Mailer\Email;
 use Cake\Mailer\Transport\DebugTransport;
+use Cake\Mailer\TransportFactory;
 use Cake\TestSuite\TestCase;
 use Exception;
 use SimpleXmlElement;
@@ -136,7 +137,10 @@ class EmailTest extends TestCase
                 'className' => 'TestFalse'
             ]
         ];
-        Email::setConfigTransport($this->transports);
+
+        $this->deprecated(function () {
+            Email::setConfigTransport($this->transports);
+        });
     }
 
     /**
@@ -149,9 +153,11 @@ class EmailTest extends TestCase
         parent::tearDown();
         Log::drop('email');
         Email::drop('test');
-        Email::dropTransport('debug');
-        Email::dropTransport('badClassName');
-        Email::dropTransport('test_smtp');
+        $this->deprecated(function () {
+            Email::dropTransport('debug');
+            Email::dropTransport('badClassName');
+            Email::dropTransport('test_smtp');
+        });
     }
 
     /**
@@ -409,8 +415,8 @@ class EmailTest extends TestCase
      */
     public function testClassNameException()
     {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionMessage('Transport class "TestFalse" not found.');
+        $this->expectException(\BadMethodCallException::class);
+        $this->expectExceptionMessage('Mailer transport TestFalse is not available.');
         $email = new Email();
         $email->setTransport('badClassName');
     }
@@ -936,7 +942,7 @@ class EmailTest extends TestCase
     public function testTransportInvalid()
     {
         $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionMessage('Transport config "Invalid" is missing.');
+        $this->expectExceptionMessage('The "Invalid" transport configuration does not exist');
         $this->Email->setTransport('Invalid');
     }
 
@@ -969,8 +975,10 @@ class EmailTest extends TestCase
     {
         $this->expectException(\InvalidArgumentException::class);
         $this->expectExceptionMessage('Transport config "debug" is invalid, the required `className` option is missing');
-        Email::dropTransport('debug');
-        Email::setConfigTransport('debug', []);
+        $this->deprecated(function () {
+            Email::dropTransport('debug');
+            Email::setConfigTransport('debug', []);
+        });
 
         $this->Email->setTransport('debug');
     }
@@ -982,16 +990,18 @@ class EmailTest extends TestCase
      */
     public function testConfigTransport()
     {
-        Email::dropTransport('debug');
         $settings = [
             'className' => 'Debug',
             'log' => true
         ];
-        $result = Email::setConfigTransport('debug', $settings);
-        $this->assertNull($result, 'No return.');
+        $this->deprecated(function () use ($settings) {
+            Email::dropTransport('debug');
+            $result = Email::setConfigTransport('debug', $settings);
+            $this->assertNull($result, 'No return.');
 
-        $result = Email::getConfigTransport('debug');
-        $this->assertEquals($settings, $result);
+            $result = Email::getConfigTransport('debug');
+            $this->assertEquals($settings, $result);
+        });
     }
 
     /**
@@ -999,7 +1009,6 @@ class EmailTest extends TestCase
      */
     public function testConfigTransportMultiple()
     {
-        Email::dropTransport('debug');
         $settings = [
             'debug' => [
                 'className' => 'Debug',
@@ -1012,9 +1021,12 @@ class EmailTest extends TestCase
                 'host' => 'example.com'
             ]
         ];
-        Email::setConfigTransport($settings);
-        $this->assertEquals($settings['debug'], Email::getConfigTransport('debug'));
-        $this->assertEquals($settings['test_smtp'], Email::getConfigTransport('test_smtp'));
+        $this->deprecated(function () use ($settings) {
+            Email::dropTransport('debug');
+            Email::setConfigTransport($settings);
+            $this->assertEquals($settings['debug'], Email::getConfigTransport('debug'));
+            $this->assertEquals($settings['test_smtp'], Email::getConfigTransport('test_smtp'));
+        });
     }
 
     /**
@@ -1024,13 +1036,15 @@ class EmailTest extends TestCase
     public function testConfigTransportErrorOnDuplicate()
     {
         $this->expectException(\BadMethodCallException::class);
-        Email::dropTransport('debug');
         $settings = [
             'className' => 'Debug',
             'log' => true
         ];
-        Email::setConfigTransport('debug', $settings);
-        Email::setConfigTransport('debug', $settings);
+        $this->deprecated(function () use ($settings) {
+            Email::setConfigTransport('debug', $settings);
+            Email::setConfigTransport('debug', $settings);
+            Email::dropTransport('debug');
+        });
     }
 
     /**
@@ -1040,10 +1054,12 @@ class EmailTest extends TestCase
      */
     public function testConfigTransportInstance()
     {
-        Email::dropTransport('debug');
-        $instance = new DebugTransport();
-        Email::setConfigTransport('debug', $instance);
-        $this->assertEquals(['className' => $instance], Email::getConfigTransport('debug'));
+        $this->deprecated(function () {
+            Email::dropTransport('debug');
+            $instance = new DebugTransport();
+            Email::setConfigTransport('debug', $instance);
+            $this->assertEquals(['className' => $instance], Email::getConfigTransport('debug'));
+        });
     }
 
     /**
@@ -1053,13 +1069,15 @@ class EmailTest extends TestCase
      */
     public function testConfiguredTransport()
     {
-        $result = Email::configuredTransport();
-        $this->assertInternalType('array', $result, 'Should have config keys');
-        $this->assertEquals(
-            array_keys($this->transports),
-            $result,
-            'Loaded transports should be present in enumeration.'
-        );
+        $this->deprecated(function () {
+            $result = Email::configuredTransport();
+            $this->assertInternalType('array', $result, 'Should have config keys');
+            $this->assertEquals(
+                array_keys($this->transports),
+                $result,
+                'Loaded transports should be present in enumeration.'
+            );
+        });
     }
 
     /**
@@ -1069,10 +1087,12 @@ class EmailTest extends TestCase
      */
     public function testDropTransport()
     {
-        $result = Email::getConfigTransport('debug');
-        $this->assertInternalType('array', $result, 'Should have config data');
-        Email::dropTransport('debug');
-        $this->assertNull(Email::getConfigTransport('debug'), 'Should not exist.');
+        $this->deprecated(function () {
+            $result = Email::getConfigTransport('debug');
+            $this->assertInternalType('array', $result, 'Should have config data');
+            Email::dropTransport('debug');
+            $this->assertNull(Email::getConfigTransport('debug'), 'Should not exist.');
+        });
     }
 
     /**
@@ -2037,8 +2057,10 @@ class EmailTest extends TestCase
      */
     public function testDeliver()
     {
-        Email::dropTransport('default');
-        Email::setConfigTransport('default', ['className' => 'Debug']);
+        $this->deprecated(function () {
+            Email::dropTransport('default');
+            Email::setConfigTransport('default', ['className' => 'Debug']);
+        });
 
         $instance = Email::deliver('all@cakephp.org', 'About', 'Everything ok', ['from' => 'root@cakephp.org'], false);
         $this->assertInstanceOf('Cake\Mailer\Email', $instance);
@@ -2829,13 +2851,17 @@ HTML;
      */
     public function testMockTransport()
     {
-        Email::dropTransport('default');
+        $this->deprecated(function () {
+            Email::dropTransport('default');
+        });
 
         $mock = $this->getMockBuilder('\Cake\Mailer\AbstractTransport')->getMock();
         $config = ['from' => 'tester@example.org', 'transport' => 'default'];
 
         Email::setConfig('default', $config);
-        Email::setConfigTransport('default', $mock);
+        $this->deprecated(function () use ($mock) {
+            Email::setConfigTransport('default', $mock);
+        });
 
         $em = new Email('default');