Skip to content

Commit 4f2b4c8

Browse files
authored
Merge pull request #7 from PhantPHP/email-builder
Email builder service
2 parents ca060aa + 49e259f commit 4f2b4c8

12 files changed

+363
-13
lines changed

component/Port/EmailBuilder.php

-12
This file was deleted.

component/Service/EmailBuilder.php

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phant\EmailSender\Service;
6+
7+
use Twig\Environment as Twig;
8+
use Twig\Loader\FilesystemLoader;
9+
10+
class EmailBuilder
11+
{
12+
public const TemplateEmail = 'layout.twig';
13+
public const TemplateComponentCta = 'component/cta.twig';
14+
public const TemplateComponentOtp = 'component/otp.twig';
15+
public const TemplateComponentMeta = 'component/meta.twig';
16+
17+
protected Twig $twig;
18+
19+
public function __construct()
20+
{
21+
$loader = new FilesystemLoader(__DIR__ . '/../Template/');
22+
23+
$this->twig = new Twig($loader, [
24+
'debug' => true,
25+
'auto_reload' => true,
26+
]);
27+
}
28+
29+
public function build(
30+
string $bodyHtml,
31+
?string $headerLogo,
32+
?string $footerLogo,
33+
?string $footerHtml
34+
): string {
35+
return $this->twig->render(
36+
self::TemplateEmail,
37+
[
38+
'body_html' => $bodyHtml,
39+
'header_logo' => $headerLogo,
40+
'footer_logo' => $footerLogo,
41+
'footer_html' => $footerHtml,
42+
]
43+
);
44+
}
45+
46+
public function buildCta(
47+
string $url,
48+
string $label,
49+
?string $backgroundColor = '#1E88E5',
50+
?string $textColor = '#ffffff'
51+
): string {
52+
return $this->twig->render(
53+
self::TemplateComponentCta,
54+
[
55+
'url' => $url,
56+
'label' => $label,
57+
'background_color' => $backgroundColor,
58+
'text_color' => $textColor,
59+
]
60+
);
61+
}
62+
63+
public function buildOtp(
64+
string $otp
65+
): string {
66+
return $this->twig->render(
67+
self::TemplateComponentOtp,
68+
[
69+
'otp' => $otp,
70+
]
71+
);
72+
}
73+
74+
public function buildMeta(
75+
array $metas
76+
): string {
77+
return $this->twig->render(
78+
self::TemplateComponentMeta,
79+
[
80+
'metas' => $metas,
81+
]
82+
);
83+
}
84+
}

component/Template/component/cta.twig

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<table width="100%" cellspacing="0" cellpadding="0">
2+
<tr>
3+
<td width="100%" height="15"></td>
4+
</tr>
5+
<tr>
6+
<td>
7+
<table cellspacing="0" cellpadding="0">
8+
<tr>
9+
<td bgcolor="{{ background_color }}" style="border-radius:5px;">
10+
<a href="{{ url }}" target="_blank" style="display:inline-block;padding:5px 15px;border:1px solid {{ background_color }};border-radius:5px;font-family:Helvetica,Arial,sans-serif,sans-serif;font-size:14px;color:{{ text_color }};text-decoration:none;font-weight:bold">
11+
{{ label }}
12+
</a>
13+
</td>
14+
</tr>
15+
</table>
16+
</td>
17+
</tr>
18+
<tr>
19+
<td width="100%" height="30"></td>
20+
</tr>
21+
</table>
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{% if metas %}
2+
<table style="margin:20px 0 0 0;border-collapse:separate;border-spacing:5px;font-size:12px;">
3+
{% for key, value in metas %}
4+
<tr>
5+
<th align="left" valign="top">{{ key }}</th>
6+
<td align="left" valign="top">{{ value }}</td>
7+
</tr>
8+
{% endfor %}
9+
</table>
10+
{% endif %}

component/Template/component/otp.twig

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
<table width="100%" cellspacing="0" cellpadding="0">
3+
<tr>
4+
<td width="100%" height="30"></td>
5+
</tr>
6+
<tr>
7+
<td align="center">
8+
<div style="text-align:center;color:#000000;font-size:34px;letter-spacing:10px">
9+
<b>{{ otp }}</b>
10+
</div>
11+
</td>
12+
</tr>
13+
<tr>
14+
<td width="100%" height="30"></td>
15+
</tr>
16+
</table>

component/Template/layout.twig

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2+
<html xmlns="http://www.w3.org/1999/xhtml">
3+
<head>
4+
<title>{{ subject }}</title>
5+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6+
<meta name="viewport" content="width=device-width,initial-scale=1">
7+
<style type="text/css">
8+
*{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
9+
body{font-family:arial}
10+
table{margin:0 auto}
11+
div,a,li,td{-webkit-text-size-adjust:none}
12+
@media only screen and (max-width:640px){table[class=full]{width:100%!important}}
13+
</style>
14+
</body>
15+
</head>
16+
<body style="margin:0;background-color:#fcfcfc;">
17+
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%">
18+
<tbody>
19+
<tr>
20+
<td height="30" width="100%"></td>
21+
</tr>
22+
<tr>
23+
<td align="center">
24+
<table align="center" border="0" cellpadding="0" cellspacing="0" class="full" width="570" style="padding:0px 5px">
25+
<tbody>
26+
{% if header_logo %}
27+
<tr>
28+
<td width="100%" valign="middle" align="center" style="text-align:center">
29+
<img src="{{ header_logo }}" alt="" width="150" height="32" style="vertical-align:bottom;border:0px solid;max-width:100%;width:auto;height:32px;outline:none !important"/>
30+
</td>
31+
</tr>
32+
<tr>
33+
<td width="100%" height="20"></td>
34+
</tr>
35+
{% endif %}
36+
<tr>
37+
<td width="100%" valign="middle" align="left" bgcolor="#FFFFFF" style="background-color:#ffffff;border:solid 1px #e8e8e8;border-radius:3px;box-shadow:0 0 15px rgba(0,0,0,.03);padding:20px;font-family:Helvetica,Arial,sans-serif;font-size:14px;line-height:24px;color:#000000">
38+
39+
{{ body_html|raw }}
40+
41+
</td>
42+
</tr>
43+
{% if footer_logo or footer_html %}
44+
<tr>
45+
<td width="100%" height="20"></td>
46+
</tr>
47+
<tr>
48+
<td width="100%" valign="middle" align="center" style="padding:20px;font-family:Helvetica,Arial,sans-serif;font-size:11px;line-height:16px;color:#999999;text-align:center">
49+
50+
{% if footer_logo %}
51+
<img src="{{ footer_logo }}" alt="" width="80" height="16" style="vertical-align:bottom;border:0px solid;max-width:100%;width:auto;height:16px;outline:none !important"/><br/>
52+
{% endif %}
53+
54+
{% if footer_logo and footer_html %}
55+
<br/>
56+
{% endif %}
57+
58+
{{ footer_html|raw }}
59+
60+
</td>
61+
</tr>
62+
{% endif %}
63+
</tbody>
64+
</table>
65+
</td>
66+
</tr>
67+
<tr>
68+
<td height="30" width="100%"></td>
69+
</tr>
70+
</tbody>
71+
</table>
72+
</html>

composer.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"php": ">=8.1",
1414
"phant/data-structure": "4.*",
1515
"phant/domain-name": "2.*",
16-
"guzzlehttp/guzzle": "^7.4"
16+
"guzzlehttp/guzzle": "^7.4",
17+
"twig/twig": "^3.8"
1718
},
1819
"require-dev": {
1920
"friendsofphp/php-cs-fixer": "^3.0",

demo/build-email-cta.php

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
include '../vendor/autoload.php';
4+
5+
use Phant\EmailSender\Service\EmailBuilder;
6+
7+
$emailBuilder = new EmailBuilder();
8+
9+
$bodyHtml = nl2br(
10+
'Hello,
11+
12+
Would you like to find out more about our work?
13+
14+
Visit our Github page:'
15+
);
16+
$bodyHtml .= $emailBuilder->buildCta(
17+
'https://github.com/PhantPHP',
18+
'Github'
19+
);
20+
$bodyHtml .= nl2br(
21+
'Thank you for your trust,
22+
The Phant team'
23+
);
24+
25+
$html = $emailBuilder->build(
26+
$bodyHtml,
27+
'image/logo.png',
28+
'image/logo.png',
29+
nl2br(
30+
'Made with passion, in France
31+
© Phant ' . date('Y')
32+
)
33+
);
34+
35+
echo $html;

demo/build-email-information.php

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
include '../vendor/autoload.php';
4+
5+
use Phant\EmailSender\Service\EmailBuilder;
6+
7+
$emailBuilder = new EmailBuilder();
8+
9+
$bodyHtml = nl2br(
10+
'<b>Lorem ipsum dolor sit amet</b>
11+
12+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
13+
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
14+
15+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
16+
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
17+
);
18+
19+
$html = $emailBuilder->build(
20+
$bodyHtml,
21+
'image/logo.png',
22+
'image/logo.png',
23+
nl2br(
24+
'Made with passion, in France
25+
© Phant ' . date('Y')
26+
)
27+
);
28+
29+
echo $html;

demo/build-email-otp.php

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
include '../vendor/autoload.php';
4+
5+
use Phant\EmailSender\Service\EmailBuilder;
6+
7+
$emailBuilder = new EmailBuilder();
8+
9+
$bodyHtml = nl2br(
10+
'Hello,
11+
12+
Here is the verification code you requested :'
13+
);
14+
$bodyHtml .= $emailBuilder->buildOtp('123456');
15+
$bodyHtml .= nl2br(
16+
'This single-use code will expire in 15 minutes.
17+
18+
Thank you for your trust,
19+
The Phant team'
20+
);
21+
$bodyHtml .= $emailBuilder->buildMeta([
22+
'IP address' => $_SERVER['REMOTE_ADDR'],
23+
'Browser' => $_SERVER['HTTP_USER_AGENT'],
24+
'Request date' => date('Y/m/d H:i:s'),
25+
]);
26+
27+
$html = $emailBuilder->build(
28+
$bodyHtml,
29+
'image/logo.png',
30+
'image/logo.png',
31+
nl2br(
32+
'Made with passion, in France
33+
© Phant ' . date('Y')
34+
)
35+
);
36+
37+
echo $html;

demo/image/logo.png

4.12 KB
Loading

test/Service/EmailBuilderTest.php

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Test\Service;
6+
7+
use Phant\EmailSender\Service\EmailBuilder;
8+
9+
final class EmailBuilderTest extends \PHPUnit\Framework\TestCase
10+
{
11+
protected EmailBuilder $fixture;
12+
13+
public function setUp(): void
14+
{
15+
$this->fixture = new EmailBuilder();
16+
}
17+
18+
public function testBuild(): void
19+
{
20+
$html = $this->fixture->build(
21+
'<b>Lorem ipsum dolor sit amet</b>',
22+
'image/logo.png',
23+
'image/logo.png',
24+
'<b>Lorem ipsum dolor sit amet</b>'
25+
);
26+
27+
$this->assertIsString($html);
28+
}
29+
30+
public function testBuildCta(): void
31+
{
32+
$html = $this->fixture->buildCta(
33+
'https://github.com/PhantPHP',
34+
'Github',
35+
'#000000',
36+
'#FFFFFF'
37+
);
38+
39+
$this->assertIsString($html);
40+
}
41+
42+
public function testBuildOtp(): void
43+
{
44+
$html = $this->fixture->buildOtp('123456');
45+
46+
$this->assertIsString($html);
47+
}
48+
49+
public function testBuildMeta(): void
50+
{
51+
$html = $this->fixture->buildMeta([
52+
'key' => 'value',
53+
]);
54+
55+
$this->assertIsString($html);
56+
}
57+
}

0 commit comments

Comments
 (0)