├── .editorconfig
├── .env.example
├── .env.testing
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature.md
│ └── help.md
└── workflows
│ └── ci.yml
├── CHANGELOG.md
├── CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── config
└── mailer.php
├── data
└── mysql.sql
├── docs
├── advanced-usage
│ ├── bt.md
│ ├── pdo.md
│ ├── rabbitmq.md
│ ├── redis.md
│ └── sqs.md
└── queue
│ └── methods.md
├── phpcs.xml
├── phpunit.xml.dist.bak
└── src
├── Builder
├── Buildable.php
├── MailJobBuilder.php
├── MailerBuilder.php
├── MessageBuilder.php
└── QueueBuilder.php
├── Enum
├── MessageBrokerEnum.php
└── TransportType.php
├── Event
├── Event.php
└── EventHandlerTrait.php
├── Exception
├── InvalidCallException.php
├── InvalidCallbackArgumentException.php
├── InvalidTransportTypeArgumentException.php
├── UndefinedMessageBrokerException.php
└── UnknownPropertyException.php
├── Helper
├── ArrayHelper.php
├── ConfigReader.php
├── PhpViewFileHelper.php
└── RecipientsHelper.php
├── Mail
└── Dto
│ ├── EmailAddress.php
│ └── File.php
├── Mailer.php
├── Model
├── AbstractMailObject.php
├── MailJob.php
└── MailMessage.php
├── Queue
├── Backend
│ ├── AbstractQueueStoreConnection.php
│ ├── Beanstalkd
│ │ ├── BeanstalkdMailJob.php
│ │ ├── BeanstalkdQueueStoreAdapter.php
│ │ └── BeanstalkdQueueStoreConnection.php
│ ├── MailJobInterface.php
│ ├── Pdo
│ │ ├── PdoMailJob.php
│ │ ├── PdoQueueStoreAdapter.php
│ │ └── PdoQueueStoreConnection.php
│ ├── QueueStoreAdapterInterface.php
│ ├── RabbitMq
│ │ ├── RabbitMqJob.php
│ │ ├── RabbitMqQueueConnection.php
│ │ └── RabbitMqQueueStoreAdapter.php
│ ├── Redis
│ │ ├── RedisMailJob.php
│ │ ├── RedisQueueStoreAdapter.php
│ │ └── RedisQueueStoreConnection.php
│ └── Sqs
│ │ ├── SqsMailJob.php
│ │ ├── SqsQueueStoreAdapter.php
│ │ └── SqsQueueStoreConnection.php
├── Cli
│ └── MailMessageWorker.php
└── MailQueue.php
├── Security
├── Cypher.php
└── CypherInterface.php
└── Transport
├── AbstractTransportFactory.php
├── MailTransport.php
├── MailTransportFactory.php
├── SendMailTransport.php
├── SendMailTransportFactory.php
├── SmtpTransport.php
├── SmtpTransportFactory.php
├── TransportFactory.php
└── TransportInterface.php
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; This file is for unifying the coding style for different editors and IDEs.
2 | ; More information at http://editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | indent_size = 4
9 | indent_style = space
10 | end_of_line = lf
11 | insert_final_newline = true
12 | trim_trailing_whitespace = true
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | MESSAGE_BROKER=redis
2 | MAIL_TRANSPORT=smtp
3 |
4 | REDIS_HOST=null
5 | REDS_PORT=
6 | REDIS_USER=
7 | REDIS_PASSWORD=
8 |
9 | SQS_KEY=
10 | SQS_SECRET=
11 | SQS_REGION=
12 |
13 | BEANSTALKD_HOST=
14 | BEANSTALKD_PORT=
15 |
16 | PDO_USER=
17 | PDO_PASSWORD=
18 | PDO_HOST=localhost
19 | PDO_PORT=3306
20 | PDO_DBNAME=
21 |
22 | RABBITMQ_HOST=localhost
23 | RABBITMQ_PORT=5672
24 | RABBITMQ_USER=guest
25 | RABBITMQ_PASSWORD=guest
26 |
27 | MAILER_SMTP_HOST=
28 | MAILER_SMTP_PORT=
29 | MAILER_SMTP_USER=
30 | MAILER_SMTP_PASSWORD=
31 | MAILER_SMTP_TLS=false
32 |
33 | SENDMAIL_DSN=sendmail://default
34 |
35 | MAILER_DSN=native://default
36 |
37 | MAIL_CHARSET=utf-8
38 |
--------------------------------------------------------------------------------
/.env.testing:
--------------------------------------------------------------------------------
1 | MESSAGE_BROKER=redis
2 | MAIL_TRANSPORT=smtp
3 |
4 | REDIS_HOST=
5 | REDS_PORT=
6 | REDIS_USER=
7 | REDIS_PASSWORD=
8 |
9 | SQS_KEY=xxx
10 | SQS_SECRET=yyy
11 | SQS_REGION=us-west-2
12 |
13 | BEANSTALKD_HOST=
14 | BEANSTALKD_PORT=
15 |
16 | PDO_USER=root
17 | PDO_PASSWORD=root
18 | PDO_HOST=localhost
19 | PDO_PORT=3306
20 | PDO_DBNAME=mail_queue_test
21 |
22 | RABBITMQ_HOST=localhost
23 | RABBITMQ_PORT=5672
24 | RABBITMQ_USER=guest
25 | RABBITMQ_PASSWORD=guest
26 |
27 | MAILER_SMTP_HOST=127.0.0.1
28 | MAILER_SMTP_PORT=25
29 | MAILER_SMTP_USER=mailing@test.me
30 | MAILER_SMTP_PASSWORD=1234
31 | MAILER_SMTP_TLS=false
32 |
33 | SENDMAIL_DSN=sendmail://null
34 |
35 | MAIL_DSN=native://null
36 |
37 | MAIL_CHARSET=utf-8
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: sJonatas
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 |
16 | **Code**
17 | Provide code sample and context where the bug occurred, as related configurations for your case.
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Desktop (please complete the following information):**
23 | - OS: [e.g. Ubuntu 22.04]
24 | - PHP Version: [e.g. 8.1.6]
25 | - Version [e.g. 1.0]
26 |
27 | **Additional context**
28 | Add any other context about the problem here.
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: sJonatas
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/help.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Help
3 | about: Help wanted
4 | title: "[HELP]"
5 | labels: help wanted
6 | assignees: sJonatas
7 |
8 | ---
9 |
10 | **Subject**
11 | What's the subject do you need help with?
12 |
13 | **Description**
14 | Give a clear description of what you're trying to achieve and what's the blocker.
15 |
16 | **Code**
17 | Provide meaningful peace of code and related configurations you have set on your project.
18 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 | on: [push]
3 |
4 | jobs:
5 | test:
6 | name: PHPUnit
7 | runs-on: ubuntu-latest
8 | env:
9 | DB_DATABASE: mail_queue_test
10 | DB_USER: root
11 | DB_PASSWORD: root
12 |
13 | steps:
14 | - name: checkout repo
15 | uses: actions/checkout@v3
16 |
17 | - name: setup enviroment
18 | uses: shivammathur/setup-php@v2
19 | with:
20 | php-version: '8.1'
21 |
22 | - name: Install dependencies
23 | run: composer install
24 |
25 |
26 | - name: Set up MySQL
27 | run: |
28 | sudo /etc/init.d/mysql start
29 | mysql -e 'CREATE DATABASE ${{ env.DB_DATABASE }};' -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }}
30 |
31 | - name: Run unit tests
32 | run: ./vendor/bin/phpunit --coverage-clover ./tests/_output/coverage.xml
33 |
34 | - name: Upload coverage reports to Codacy
35 | uses: codacy/codacy-coverage-reporter-action@v1
36 | with:
37 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
38 | coverage-reports: ./tests/_output/coverage.xml
39 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Mailer Library
2 | ## Version 1.0.0
3 | - Initial release
4 |
5 | [](http://www.2amigos.us)
6 |
--------------------------------------------------------------------------------
/CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4 |
5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
6 |
7 | Examples of unacceptable behavior by participants include:
8 |
9 | * The use of sexualized language or imagery
10 | * Personal attacks
11 | * Trolling or insulting/derogatory comments
12 | * Public or private harassment
13 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission
14 | * Other unethical or unprofessional conduct.
15 |
16 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
17 |
18 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community in a direct capacity. Personal views, beliefs and values of individuals do not necessarily reflect those of the organisation or affiliated individuals and organisations.
19 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | We accept contributions via Pull Requests on [Github](https://github.com/2amigos/mailer-library).
6 |
7 |
8 | ## Pull Requests
9 |
10 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
11 |
12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
13 |
14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
15 |
16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
17 |
18 | - **Create feature branches** - Don't ask us to pull from your master branch.
19 |
20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
21 |
22 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.
23 |
24 |
25 | ## Running Tests
26 |
27 | ``` bash
28 | $ ./vendor/bin/phpunit
29 | ```
30 |
31 |
32 | **Happy coding**!
33 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The Mailer library is free software. It is released under the terms of the following BSD license.
2 |
3 | Copyright (c) 2014-16, [2amigOS! Consulting Group LLC.](http://2amigos.us). All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification,
6 | are permitted provided that the following conditions are met:
7 |
8 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following
9 | disclaimer.
10 | - Redistributions in binary form must reproduce the above copyright notice, this
11 | list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 | - Neither the name of 2amigOS! Consulting Group, LLC. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
28 | [](http://www.2amigos.us)
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mailer
2 | [](https://github.com/2amigos/mailer-library/actions/workflows/ci.yml)
3 | [](https://app.codacy.com/gh/2amigos/mailer-library/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
4 | [](https://app.codacy.com/gh/2amigos/mailer-library/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
5 | [](https://packagist.org/packages/2amigos/mailer)
6 | [](https://packagist.org/packages/2amigos/mailer)
7 | [](https://packagist.org/packages/2amigos/mailer)
8 |
9 | Many times we face a requirement to implement queue mail functionality in our projects. There are queue and
10 | mailing libraries, but there seemed to be none that could actually suit our needs and moreover, we always had to sync their
11 | functionality together.
12 |
13 | The `Mailer` library was built to fill the gaps that we have faced when implementing queue and/or mailing systems. It
14 | features:
15 |
16 | - message encryption/decryption just in case a mail message contains data that should not be publicly exposed. Perfect
17 | for SAS systems.
18 | - queueing on different backends (currently supporting beanstalkd, pdo, redis, sqs and rabbitmq) so we are not forced to use a
19 | queue storage due to the narrowed capabilities of the framework and/or libraries
20 | - unified system. Basic to Middle size projects do have mailing but they do not require another type of queue system.
21 | That's the reason the queue system is not standalone and is coupled with this system.
22 |
23 |
24 | ## Installation
25 |
26 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
27 |
28 | Either run
29 |
30 | ```bash
31 | $ composer require 2amigos/mailer
32 | ```
33 |
34 | or add
35 |
36 | ```
37 | "2amigos/mailer": "^2.0"
38 | ```
39 |
40 | to the `require` section of your `composer.json` file.
41 |
42 | Usage
43 | ---
44 |
45 | ## Configuration
46 |
47 | All the configuration needed to set up the message broker connections as
48 | the mailer transport should be performed on the .env file. A .env.example file
49 | is provided, you can just copy it and start over!
50 |
51 | ```bash
52 | $ cp .evn.example .env
53 | ```
54 |
55 | The keys `MESSAGE_BROKER` and `MAIL_TRANSPORT` defines the default message broker
56 | and mail transport, and they are mandatory to be filled. By default, its
57 | set to use Redis broker with SMTP transport.
58 |
59 | You can access the related configuration values by calling:
60 | ```php
61 | $values = \Da\Mailer\Helper\ConfigReader::get(); // array
62 | ```
63 |
64 | ## Mail Messages
65 |
66 | The `MailMessage` class is an abstraction for an email
67 | content. Beside the attachments, you can specify the email content
68 | directly by the constructor or directly accessor.
69 |
70 | ```php
71 | $message = new \Da\Mailer\Model\MailMessage([
72 | 'from' => 'sarah.connor@gmail.com',
73 | 'to' => 'john.connor@gmail.com',
74 | 'subject' => 'What is up?',
75 | 'textBody' => 'I hope to find you well...'
76 | ]);
77 |
78 | // or
79 | $message->bodyHtml = "I hope I'm finding doing well."
80 | // body html takes priority over body text with both were set.
81 | ```
82 |
83 | You can also use our `EmailAddress` class to define emails with related name:
84 |
85 | ```php
86 | $message->cc = [
87 | \Da\Mailer\Mail\Dto\EmailAddress::make('Samn@email.com', 'Samantha');
88 | \Da\Mailer\Mail\Dto\EmailAddress::make('oliver@email.com', 'Oliver');
89 | ];
90 | ```
91 |
92 | And to add attachments, you can make use of the method `addAttachment(path, name)`:
93 |
94 | ```php
95 | $message->addAttachment(__DIR__ . DIRECTORY_SEPARATOR . 'file-test.pdf', 'Important File.png');
96 | ```
97 |
98 | Also, you can set text or html body as a resource path.
99 |
100 | ```php
101 | $message->bodyHtml = __DIR__ . DIRECTORY_SEPARATOR . 'html-template.html';
102 | ```
103 |
104 | ### Available public properties:
105 | | Property | Type |
106 | |:--------:|:-------------:|
107 | | from | string, array |
108 | | to | string, array |
109 | | cc | string, array |
110 | | bcc | string, array |
111 | | subject | string |
112 | | bodyText | string |
113 | | bodyHtml | string |
114 |
115 | ### enqueue MailMessage
116 |
117 | You can easily assess the message enqueue by calling the method `enqueue`.
118 | ```php
119 | $message->enqueue();
120 | ```
121 | The message will enqueued to the default message broker, and use the default
122 | transport.
123 |
124 | ## MailJob
125 |
126 | The MailJob class will abstract the message behavior for our queue application.
127 | You can create a new MailJob with the `MailJobBuilder` class:
128 |
129 | ```php
130 | $mailJob = \Da\Mailer\Builder\MailJobBuilder::make([
131 | 'message' => json_encode($message)
132 | ]);
133 | ```
134 |
135 | Behind the hoods, the builder will build the MailJob specialized to the
136 | default broker you've defined on your .env file. If you ever want a
137 | mail job to be created to a different broker than your default, you
138 | can set it as the second argument, using one value
139 | from the `\Da\Mailer\Enum\MessageBrokerEnum` enum:
140 |
141 | ```php
142 | $mailJob = \Da\Mailer\Builder\MailJobBuilder::make([
143 | 'message' => json_encode($message)
144 | ],
145 | \Da\Mailer\Enum\MessageBrokerEnum::BROKER_SQS
146 | );
147 | ```
148 |
149 | The MailJob class has a set of methods to manipulate it's content
150 | and also to check its status. The next piece of code cover them all:
151 |
152 | ```php
153 | $mailJob->getMessage(); // returns the MailJob message
154 | $mailJob->markAsCompleted(); // void, mark the job as completed
155 | $mailJob->isCompleted(); // returns true if the job has been complete
156 | $mailJob->setMessage(new \Da\Mailer\Model\MailMessage()); // change the job's message
157 | ```
158 |
159 | ## Mailer
160 | The Mailer class is the one we use for sending the emails.
161 |
162 | ```php
163 | $message = new \Da\Mailer\Model\MailMessage([
164 | 'from' => 'sarah.connor@gmail.com',
165 | 'to' => 'john.connor@gmail.com',
166 | 'subject' => 'What is up?',
167 | 'textBody' => 'I hope to find you well...'
168 | ]);
169 |
170 | $mailer = \Da\Mailer\Builder\MailerBuilder::make();
171 | // or if you want to set a transport different from the default
172 | $mailer = \Da\Mailer\Builder\MailerBuilder::make(\Da\Mailer\Enum\TransportType::SEND_MAIL);
173 | $mailer->send($message); // returns \Symfony\Component\Mailer\SentMessage::class|null
174 | ```
175 |
176 | ## Queues
177 |
178 | To create a queue, you can make use of our `QueueBuilder` class. It will return
179 | a queue object with a few methods to handle the queue. They are:
180 |
181 | - [enqueue(MailJob $job)](docs/queue/methods.md#enqueue): bool
182 | - [dequeue()](docs/queue/methods.md#dequeue): mailjob
183 | - [ack(MailJob $job)](docs/queue/methods.md#ack): void
184 | - [isEmpty()](docs/queue/methods.md#isempty): bool
185 |
186 | ```php
187 | $queue = \Da\Mailer\Builder\QueueBuilder::make();
188 |
189 | // if you want to use a different broker than the default
190 | $queue = \Da\Mailer\Builder\QueueBuilder::make(\Da\Mailer\Enum\MessageBrokerEnum::BROKER_RABBITMQ);
191 | ```
192 |
193 | ## Advanced usage
194 |
195 | If you want to handle your message broker and smtp manually, you can follow
196 | through the following topics:
197 |
198 | - [Beanstalkd Backend](docs/advanced-usage/bt.md)
199 | - [Pdo Backend](docs/advanced-usage/pdo.md)
200 | - [RabbitMq Backend](docs/advanced-usage/rabbitmq.md)
201 | - [Redis Backend](docs/advanced-usage/redis.md)
202 | - [SQS Backend](docs/advanced-usage/sqs.md)
203 |
204 | ## Contributing
205 |
206 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
207 |
208 | ## Clean code
209 |
210 | We have added some development tools for you to contribute to the library with clean code:
211 |
212 | - PHP mess detector: Takes a given PHP source code base and look for several potential problems within that source.
213 | - PHP code sniffer: Tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.
214 | - PHP code fixer: Analyzes some PHP source code and tries to fix coding standards issues.
215 |
216 | And you should use them in that order.
217 |
218 | ### Using php mess detector
219 |
220 | Sample with all options available:
221 |
222 | ```bash
223 | ./vendor/bin/phpmd ./src text codesize,unusedcode,naming,design,controversial,cleancode
224 | ```
225 |
226 | ### Using code sniffer
227 |
228 | ```bash
229 | ./vendor/bin/phpcs -s --report=source --standard=PSR2 ./src
230 | ```
231 |
232 | ### Using code fixer
233 |
234 | We have added a PHP code fixer to standardize our code. It includes Symfony, [PSR-12](https://www.php-fig.org/psr/psr-12/) and some contributors rules.
235 |
236 | ```bash
237 | ./vendor/bin/php-cs-fixer --config-file=.php_cs fix ./src
238 | ```
239 |
240 | ## Testing
241 |
242 | ```bash
243 | $ ./vendor/bin/phpunit
244 | ```
245 |
246 |
247 | ## Credits
248 |
249 | - [Antonio Ramirez](https://github.com/tonydspaniard)
250 | - [All Contributors](https://github.com/2amigos/mailer-library/graphs/contributors)
251 |
252 | ## License
253 |
254 | The BSD License (BSD). Please see [License File](LICENSE.md) for more information.
255 |
256 |
257 | 
258 | web development has never been so fun
259 | www.2amigos.us
260 |
261 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "2amigos/mailer",
3 | "description": "Mailer adapter for the Symphony Mailer library with queuing capabilities.",
4 | "config": {
5 | "discard-changes": "true"
6 | },
7 | "authors": [
8 | {
9 | "name": "2amigOS! Consulting Group",
10 | "email": "hola@2amigos.us",
11 | "homepage": "https://2am.tech",
12 | "role": "Developer"
13 | }
14 | ],
15 | "require": {
16 | "php": ">=7.4",
17 | "phpseclib/phpseclib": "^3.0",
18 | "aws/aws-sdk-php": "^3.296",
19 | "predis/predis": "^2.2",
20 | "pda/pheanstalk": "^4.0",
21 | "php-amqplib/php-amqplib": "2.*",
22 | "vlucas/phpdotenv": "^5.6",
23 | "marc-mabe/php-enum": "^4.7",
24 | "symfony/mailer": "^5.4",
25 | "symfony/event-dispatcher": "^5.4"
26 | },
27 | "require-dev": {
28 | "phpunit/phpunit": "^8.5 || ^10.5",
29 | "mockery/mockery": "^1.6.7",
30 | "squizlabs/php_codesniffer": "^3.8"
31 | },
32 | "autoload": {
33 | "psr-4": {
34 | "Da\\Mailer\\": "src"
35 | }
36 | },
37 | "autoload-dev": {
38 | "psr-4": {
39 | "Da\\Mailer\\Test\\": "tests"
40 | }
41 | },
42 | "scripts": {
43 | "test": "phpunit"
44 | },
45 | "suggest": {
46 | "aws/aws-sdk-php": "Allows the use of Amazon SQS as a mail queue system",
47 | "predis/predis": "Allows the use of Redis as a mail queue system",
48 | "pda/pheanstalk": "Allows the use of Beanstalkd as mail queue system",
49 | "php-amqplib/php-amqplib": "Allows the use of AMQP protocol required to use RabbitMq as a mail queue system"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/config/mailer.php:
--------------------------------------------------------------------------------
1 | load();
9 |
10 | return [
11 | 'config' => [
12 | 'message_broker' => $_ENV['MESSAGE_BROKER'],
13 | 'transport' => $_ENV['MAIL_TRANSPORT']
14 | ],
15 |
16 | 'brokers' => [
17 | 'redis' => [
18 | 'host' => $_ENV['REDIS_HOST'],
19 | 'port' => $_ENV['REDS_PORT'],
20 | 'user' => $_ENV['REDIS_USER'],
21 | 'password' => $_ENV['REDIS_PASSWORD']
22 | ],
23 |
24 | 'sqs' => [
25 | 'key' => $_ENV['SQS_KEY'],
26 | 'secret' => $_ENV['SQS_SECRET'],
27 | 'region' => $_ENV['SQS_REGION']
28 | ],
29 |
30 | 'beanstalkd' => [
31 | 'host' => $_ENV['BEANSTALKD_HOST'],
32 | 'port' => $_ENV['BEANSTALKD_PORT']
33 | ],
34 |
35 | 'pdo' => [
36 | 'username' => $_ENV['PDO_USER'],
37 | 'password' => $_ENV['PDO_PASSWORD'],
38 | 'host' => $_ENV['PDO_HOST'],
39 | 'port' => $_ENV['PDO_PORT'] ?: 3306,
40 | 'db' => $_ENV['PDO_DBNAME']
41 | ],
42 |
43 | 'rabbitmq' => [
44 | 'host' => $_ENV['RABBITMQ_HOST'],
45 | 'port' => $_ENV['RABBITMQ_PORT'],
46 | 'user' => $_ENV['RABBITMQ_USER'],
47 | 'password' => $_ENV['RABBITMQ_PASSWORD']
48 | ]
49 | ],
50 |
51 | 'transports' => [
52 | 'smtp' => [
53 | 'host' => $_ENV['MAILER_SMTP_HOST'],
54 | 'port' => $_ENV['MAILER_SMTP_PORT'],
55 | 'options' => [
56 | 'username' => $_ENV['MAILER_SMTP_USER'],
57 | 'password' => $_ENV['MAILER_SMTP_PASSWORD'],
58 | 'tls' => $_ENV['MAILER_SMTP_TLS']
59 | ]
60 | ],
61 | 'sendMail' => [
62 | 'dsn' => $_ENV['SENDMAIL_DSN']
63 | ],
64 | 'mail' => [
65 | 'dsn' => $_ENV['MAIL_DSN']
66 | ]
67 | ],
68 | 'mail-charset' => $_ENV['MAIL_CHARSET'] ?: 'utf-8'
69 | ];
70 |
--------------------------------------------------------------------------------
/data/mysql.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS mail_queue;
2 |
3 | CREATE TABLE mail_queue (
4 | id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
5 | message MEDIUMTEXT NOT NULL,
6 | attempt TINYINT(1) UNSIGNED NOT NULL DEFAULT 0,
7 | state CHAR(1) NOT NULL DEFAULT 'N', /* 'N': queued (new), 'A': processing (active), 'C': completed */
8 | sentTime TIMESTAMP NULL DEFAULT NULL,
9 | timeToSend TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
10 | createdTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
11 | updatedTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
12 | PRIMARY KEY (id),
13 | KEY id (id),
14 | KEY time_to_send (timeToSend)
15 | );
16 |
--------------------------------------------------------------------------------
/docs/advanced-usage/bt.md:
--------------------------------------------------------------------------------
1 | # Beanstalkd Backend Usage
2 |
3 | This assumes you have beanstalkd running on your computer and on port 11300.
4 |
5 | ## Add email job to the queue
6 |
7 | ```php
8 | use Da\Mailer\Model\MailMessage;
9 | use Da\Mailer\Queue\MailQueue;
10 | use Da\Mailer\Queue\Backend\Beanstalkd\BeanstalkdMailJob;
11 | use Da\Mailer\Queue\Backend\Beanstalkd\BeanstalkdQueueStoreAdapter;
12 | use Da\Mailer\Queue\Backend\Beanstalkd\BeanstalkdQueueStoreConnection;
13 | use PDO;
14 |
15 | $message = new MailMessage([
16 | 'from' => 'sarah.connor@gmail.com',
17 | 'to' => 'john.connor@gmail.com',
18 | 'subject' => 'What is up?',
19 | 'bodyText' => 'New mailing'
20 | ]);
21 |
22 | $conn = new BeanstalkdQueueStoreConnection([
23 | 'host' => 'localhost',
24 | 'port' => 'root']);
25 |
26 | $adapter = new BeanstalkdQueueStoreAdapter($conn);
27 |
28 | $queue = new MailQueue($adapter);
29 |
30 | $job = new BeanstalkdMailJob([
31 | 'message' => json_encode($message),
32 | ]);
33 |
34 | if (!$queue->enqueue($job)) {
35 | // ... queue operation failed
36 | }
37 | ```
38 |
39 | ## Fetch email job from queue
40 |
41 | ```php
42 | use Da\Mailer\Queue\MailQueue;
43 | use Da\Mailer\Queue\Backend\Beanstalkd\BeanstalkdQueueStoreAdapter;
44 | use Da\Mailer\Queue\Backend\Beanstalkd\BeanstalkdQueueStoreConnection;
45 |
46 | $conn = new BeanstalkdQueueStoreConnection([
47 | 'connectionString' => 'mysql:host=localhost;dbname=test',
48 | 'username' => 'root',
49 | 'password' => 'password'
50 | ], [PDO::ATTR_PERSISTENT => true]);
51 |
52 | $adapter = new BeanstalkdQueueStoreAdapter($conn);
53 |
54 | $queue = new MailQueue($adapter);
55 |
56 | if (($job = $queue->dequeue()) !== null) {
57 | // ... do something with received job
58 | // ... send it using `mail()` function for example
59 | // ... or by using MailMessageWorker
60 |
61 | $job->markAsCompleted();
62 | $queue->ack($job);
63 | }
64 | ```
65 |
66 | ## Send email job with `MailMessageWorker`
67 |
68 | This could be an example using `MailMessageWorker` and a `MailJob` with specific transport configurations in it. Very
69 | useful if the mail jobs in the queue do belong to your customers and they can specify their SMTP configuration details
70 | for sending emails.
71 |
72 |
73 | ```php
74 | use Da\Mailer\Mailer;
75 | use Da\Mailer\Model\MailMessage;
76 | use Da\Mailer\Transport\TransportFactory
77 | use Da\Mailer\Queue\Cli\MailMessageWorker;
78 |
79 | $mailJob = /* ... get mail job here (see above) ... */;
80 | $mailMessage = json_decode($mailJob->getMessage()); /* ... if you have json encoded ... */
81 | $transport = TransportFactory::create($mailMessage->transportOptions, $mailMessage->transportType);
82 | $mailer = new Mailer($transport);
83 |
84 | $worker = new MailMessageWorker($mailer, $mailMessage);
85 |
86 | // you could set the event handlers for `onFailure` or `onSuccess` here to do a different action according to the
87 | // results of the work
88 | $worker->attach('onSuccess', $callableHere);
89 | $worker->attach('onFailure', $anotherCallable);
90 |
91 | $worker->run();
92 |
93 | // this could be added in the 'onSuccess' event handler
94 | $mailJob->markAsCompleted();
95 | $queue->ack($job);
96 | ```
97 |
--------------------------------------------------------------------------------
/docs/advanced-usage/pdo.md:
--------------------------------------------------------------------------------
1 | # Pdo Backend Usage
2 |
3 | ## Add the storage table to your database
4 |
5 | This backend requires that you create a table like the one specified in the `data` folder. It defaults to `mail_queue`
6 | name but you can name it the way you want it as long as its structure remains the same and then you specify that
7 | custom name on the adapter.
8 |
9 | Currently we have a `mysql.sql` only, feel free to add your PDO compatible database to it.
10 |
11 | ## Add email job to the queue
12 |
13 | ```php
14 | use Da\Mailer\Model\MailMessage;
15 | use Da\Mailer\Queue\MailQueue;
16 | use Da\Mailer\Queue\Backend\Pdo\PdoMailJob;
17 | use Da\Mailer\Queue\Backend\Pdo\PdoQueueStoreAdapter;
18 | use Da\Mailer\Queue\Backend\Pdo\PdoQueueStoreConnection;
19 | use PDO;
20 |
21 | $message = new MailMessage([
22 | 'from' => 'sarah.connor@gmail.com',
23 | 'to' => 'john.connor@gmail.com',
24 | 'subject' => 'What is up?',
25 | 'bodyText' => 'New mailing'
26 | ]);
27 |
28 | $conn = new PdoQueueStoreConnection([
29 | 'connectionString' => 'mysql:host=localhost;dbname=test',
30 | 'username' => 'root',
31 | 'password' => 'password'
32 | ], [PDO::ATTR_PERSISTENT => true]);
33 |
34 | $adapter = new PdoQueueStoreAdapter($conn);
35 |
36 | $queue = new MailQueue($adapter);
37 |
38 | $job = new PdoMailJob([
39 | 'message' => json_encode($message),
40 | ]);
41 |
42 | if (!$queue->enqueue($job)) {
43 | // ... queue operation failed
44 | }
45 | ```
46 |
47 | ## Fetch email job from queue
48 |
49 | ```php
50 | use Da\Mailer\Queue\MailQueue;
51 | use Da\Mailer\Queue\Backend\Pdo\PdoQueueStoreAdapter;
52 | use Da\Mailer\Queue\Backend\Pdo\PdoQueueStoreConnection;
53 |
54 | $conn = new PdoQueueStoreConnection([
55 | 'connectionString' => 'mysql:host=localhost;dbname=test',
56 | 'username' => 'root',
57 | 'password' => 'password'
58 | ], [PDO::ATTR_PERSISTENT => true]);
59 |
60 | $adapter = new PdoQueueStoreAdapter($conn);
61 |
62 | $queue = new MailQueue($adapter);
63 |
64 | if (($job = $queue->dequeue()) !== null) {
65 | // ... do something with received job
66 | // ... send it using `mail()` function for example
67 | // ... or by using MailMessageWorker
68 |
69 | $job->markAsCompleted();
70 | $queue->ack($job);
71 | }
72 | ```
73 |
74 | ## Send email job with `MailMessageWorker`
75 |
76 | This could be an example using `MailMessageWorker` and a `MailJob` with specific transport configurations in it. Very
77 | useful if the mail jobs in the queue do belong to your customers and they can specify their SMTP configuration details
78 | for sending emails.
79 |
80 |
81 | ```php
82 | use Da\Mailer\Mailer;
83 | use Da\Mailer\Model\MailMessage;
84 | use Da\Mailer\Transport\TransportFactory
85 | use Da\Mailer\Queue\Cli\MailMessageWorker;
86 |
87 | $mailJob = /* ... get mail job here (see above) ... */;
88 | $mailMessage = json_decode($mailJob->getMessage()); /* ... if you have json encoded ... */
89 | $transport = TransportFactory::create($mailMessage->transportOptions, $mailMessage->transportType);
90 | $mailer = new Mailer($transport);
91 |
92 | $worker = new MailMessageWorker($mailer, $mailMessage);
93 |
94 | // you could set the event handlers for `onFailure` or `onSuccess` here to do a different action according to the
95 | // results of the work
96 | $worker->attach('onSuccess', $callableHere);
97 | $worker->attach('onFailure', $anotherCallable);
98 |
99 | $worker->run();
100 |
101 | // this could be added in the 'onSuccess' event handler
102 | $mailJob->markAsCompleted();
103 | $queue->ack($job);
104 | ```
105 |
--------------------------------------------------------------------------------
/docs/advanced-usage/rabbitmq.md:
--------------------------------------------------------------------------------
1 | # RabbitMq Backend Usage
2 |
3 | ## Add an Email to the Queue
4 |
5 | ```php
6 | $message = new MailMessage([
7 | 'from' => 'sarah.connor@gmail.com',
8 | 'to' => 'john.connor@gmail.com',
9 | 'subject' => 'What is up?',
10 | 'bodyText' => 'New mailing'
11 | ]);
12 |
13 | $connection = new \Da\Mailer\Queue\Backend\RabbitMq\RabbitMqQueueConnection([
14 | 'host' => 'localhost',
15 | 'port' => 5672,
16 | 'user' => 'guest',
17 | 'password' => 'guest'
18 | ]);
19 |
20 | $adapter = new \Da\Mailer\Queue\Backend\RabbitMq\RabbitMqQueueStoreAdapter($connection);
21 | $queue = new \Da\Mailer\Queue\MailQueue($adapter);
22 |
23 | $job = new \Da\Mailer\Queue\Backend\RabbitMq\RabbitMqJob([
24 | 'message' => json_encode($message);
25 | ]);
26 |
27 | if (! $queue->enqueue($job)) {
28 | //something wrong happened
29 | }
30 | ```
31 |
32 | ## Fetch email from the queue
33 |
34 | ```php
35 | $connection = new \Da\Mailer\Queue\Backend\RabbitMq\RabbitMqQueueConnection([
36 | 'host' => 'localhost',
37 | 'port' => 5672,
38 | 'user' => 'guest',
39 | 'password' => 'guest'
40 | ]);
41 |
42 | $adapter = new \Da\Mailer\Queue\Backend\RabbitMq\RabbitMqQueueStoreAdapter($connection);
43 | $queue = new \Da\Mailer\Queue\MailQueue($adapter);
44 |
45 | $job = $queue->dequeue();
46 | if ($job !== null) {
47 | // perform some action with the job e.g send email
48 | $job->markAsCompleted();
49 | $queue->ack($job);
50 | }
51 | ```
52 |
53 | ## Send email with the mail() function
54 |
55 | ```php
56 | $transport = new \Da\Mailer\Transport\SmtpTransport($host, $user, $options);
57 | $mailer = new \Da\Mailer\Model\MailMessage($transport);
58 |
59 | $mailJob = /* ... get mail job here ... */;
60 |
61 | $status = null;
62 |
63 | try {
64 | $status = $mailer->send(
65 | new MailMessage(json_decode($mailJob->getMessage(), true))
66 | );
67 | } catch(Exception $e) {
68 | // log exception;
69 | }
70 |
71 | if (is_null($status)) {
72 | // ... cannot send email
73 | /* ... ack here with job not completed - will be set for later processing ... */
74 | } else {
75 | $mailJob->markAsCompleted();
76 | /* ... ack here with job completed - will be ack on the queue backend storage ... */
77 | }
78 | ```
79 |
--------------------------------------------------------------------------------
/docs/advanced-usage/redis.md:
--------------------------------------------------------------------------------
1 | # Redis Backend Usage
2 |
3 | ## Add email job to queue
4 |
5 | ```php
6 | use Da\Mailer\Model\MailMessage;
7 | use Da\Mailer\Queue\MailQueue;
8 | use Da\Mailer\Queue\Backend\Redis\RedisMailJob;
9 | use Da\Mailer\Queue\Backend\Redis\RedisQueueStoreAdapter;
10 | use Da\Mailer\Queue\Backend\Redis\RedisQueueStoreConnection;
11 |
12 | $message = new MailMessage([
13 | 'from' => 'sarah.connor@gmail.com',
14 | 'to' => 'john.connor@gmail.com',
15 | 'subject' => 'What is up?',
16 | 'bodyText' => 'New mailing'
17 | ]);
18 |
19 | $conn = new RedisQueueStoreConnection([
20 | 'host' => 'localhost',
21 | 'port' => 9367,
22 | ]);
23 |
24 | $adapter = new RedisQueueStoreAdapter($conn);
25 |
26 | $queue = new MailQueue($adapter);
27 |
28 | $job = new RedisMailJob([
29 | 'message' => json_encode($message),
30 | ]);
31 |
32 | if (!$queue->enqueue($job)) {
33 | // ... queue operation failed
34 | }
35 | ```
36 |
37 | ## Fetch email job from queue
38 |
39 | ```php
40 | use Da\Mailer\Queue\MailQueue;
41 | use Da\Mailer\Queue\Backend\Redis\RedisQueueStoreAdapter;
42 | use Da\Mailer\Queue\Backend\Redis\RedisQueueStoreConnection;
43 |
44 | $conn = new RedisQueueStoreConnection([
45 | 'host' => 'localhost',
46 | 'port' => 9367,
47 | ]);
48 |
49 | $adapter = new RedisQueueStoreAdapter($conn);
50 |
51 | $queue = new MailQueue($adapter);
52 |
53 | if (($job = $queue->dequeue()) !== null) {
54 | // ... do something with received job
55 | // ... send it using `mail()` function for example
56 |
57 | $job->markAsCompleted();
58 | $queue->ack($job);
59 | }
60 | ```
61 |
62 | ## Send email job with `mail()` function
63 |
64 | ```php
65 | use Da\Mailer\Mailer;
66 | use Da\Mailer\Model\MailMessage;
67 | use Da\Mailer\Transport\MailTransport;
68 |
69 | $transport = new MailTransport();
70 |
71 | $mailer = new Mailer($transport);
72 |
73 | $mailJob = /* ... get mail job here ... */;
74 |
75 | $status = null;
76 |
77 | try {
78 | $status = $mailer->send(
79 | new MailMessage(json_decode($mailJob->getMessage(), true))
80 | );
81 | } catch(Exception $e) {
82 | // log exception;
83 | }
84 |
85 | if (is_null($status)) {
86 | // ... cannot send email
87 | /* ... ack here with job not completed - will be set for later processing ... */
88 | } else {
89 | $mailJob->markAsCompleted();
90 | /* ... ack here with job completed - will be ack on the queue backend storage ... */
91 | }
92 | ```
93 |
--------------------------------------------------------------------------------
/docs/advanced-usage/sqs.md:
--------------------------------------------------------------------------------
1 | # SQS Backend Usage
2 |
3 | ## Add email job to queue
4 |
5 | ```php
6 | use Da\Mailer\Model\MailMessage;
7 | use Da\Mailer\Queue\MailQueue;
8 | use Da\Mailer\Queue\Backend\Sqs\SqsMailJob;
9 | use Da\Mailer\Queue\Backend\Sqs\SqsQueueStoreAdapter;
10 | use Da\Mailer\Queue\Backend\Sqs\SqsQueueStoreConnection;
11 |
12 | $message = new MailMessage([
13 | 'from' => 'sarah.connor@gmail.com',
14 | 'to' => 'john.connor@gmail.com',
15 | 'subject' => 'What is up?',
16 | 'bodyText' => 'New mailing'
17 | ]);
18 |
19 | $conn = new SqsQueueStoreConnection([
20 | 'key' => 'AKIA...',
21 | 'secret' => '...',
22 | 'region' => 'eu-west-1', // https://docs.aws.amazon.com/general/latest/gr/rande.html
23 | ]);
24 |
25 | $adapter = new SqsQueueStoreAdapter($conn);
26 |
27 | $queue = new MailQueue($adapter);
28 |
29 | $job = new SqsMailJob([
30 | 'message' => json_encode($message),
31 | ]);
32 |
33 | if (!$queue->enqueue($job)) {
34 | // ... queue operation failed
35 | }
36 | ```
37 |
38 | ## Fetch email job from queue
39 |
40 | ```php
41 | use Da\Mailer\Queue\MailQueue;
42 | use Da\Mailer\Queue\Backend\Sqs\SqsQueueStoreAdapter;
43 | use Da\Mailer\Queue\Backend\Sqs\SqsQueueStoreConnection;
44 |
45 | $conn = new SqsQueueStoreConnection([
46 | 'key' => 'AKIA...',
47 | 'secret' => '...',
48 | 'region' => 'eu-west-1', // https://docs.aws.amazon.com/general/latest/gr/rande.html
49 | ]);
50 |
51 | $adapter = new SqsQueueStoreAdapter($conn);
52 |
53 | $queue = new MailQueue($adapter);
54 |
55 | if (($job = $queue->dequeue()) !== null) {
56 | // ... do something with received job
57 | // ... send it using `mail()` function for example
58 |
59 | $job->setDeleted(true);
60 | $queue->ack($job);
61 | }
62 | ```
63 |
64 | ## Send email job with `mail()` function
65 |
66 | ```php
67 | use Da\Mailer\Mailer;
68 | use Da\Mailer\Model\MailMessage;
69 | use Da\Mailer\Transport\MailTransport;
70 |
71 | $transport = new MailTransport();
72 |
73 | $mailer = new Mailer($transport);
74 |
75 | $mailJob = /* ... get mail job here ... */;
76 |
77 | $status = null;
78 |
79 | try {
80 | $result = $mailer->send(
81 | new MailMessage(json_decode($mailJob->getMessage(), true))
82 | );
83 | } catch (Exception $e) {
84 | // log exception
85 | }
86 |
87 | if (is_null($status)) {
88 | // ... cannot send email
89 | }
90 | ```
91 |
--------------------------------------------------------------------------------
/docs/queue/methods.md:
--------------------------------------------------------------------------------
1 | ## Methods
2 |
3 | enqueue
4 | ---
5 | The enqueue method takes a object from `\Da\Mailer\Model\MailJob` class as
6 | parameter. It pushes the object to the end of the queue.
7 |
8 | ```php
9 | $message = new \Da\Mailer\Model\MailMessage([
10 | 'from' => 'sarah.connor@gmail.com',
11 | 'to' => 'john.connor@gmail.com',
12 | 'subject' => 'What is up?',
13 | 'textBody' => 'I hope to find you well...'
14 | ]);
15 |
16 | $queue = \Da\Mailer\Queue\MailQueue::make();
17 | $mailJob = \Da\Mailer\Builder\MailJobBuilder::make([
18 | 'message' => $message
19 | ]);
20 |
21 | if (! $queue->enqueue($mailJob)) {
22 | // something wrong happened
23 | }
24 | ```
25 |
26 | dequeue
27 | ---
28 | The dequeue method fetches the very next job on the queue.
29 |
30 | ```php
31 | $mailJob = \Da\Mailer\Queue\MailQueue::make()->dequeue();
32 | var_dump($mailJob);
33 |
34 | // output:
35 | // class Da\Mailer\Queue\Backend\RabbitMq\RabbitMqJob#32 (5) {
36 | // private $deliveryTag =>
37 | // ...
38 | ```
39 |
40 | ack
41 | ---
42 | The ack method takes an object from the `\Da\Mailer\Model\MailJob` class. It is responsible to inform the broker about a message status.
43 | If the message is full processed and completed (literally have to MailJob with the property `isCompleted` as true. You can check how to assess it [here](../../README.md#mailjob)), the broker will remove it from new rounds,
44 | otherwise, it'll requeue it.
45 |
46 | ```php
47 | $queue->ack($mailJob);
48 | ```
49 |
50 | isEmpty()
51 | ---
52 | Return true if there is no messages in the queue, otherwise, return false.
53 |
54 | ```php
55 | $queue->isEmpty();
56 | // false
57 | ```
58 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PHP_CodeSniffer configuration
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/phpunit.xml.dist.bak:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | tests
15 |
16 |
17 |
18 |
19 | src/
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/Builder/Buildable.php:
--------------------------------------------------------------------------------
1 | create();
23 |
24 | return new Mailer($transport);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Builder/MessageBuilder.php:
--------------------------------------------------------------------------------
1 | subject($mailMessage->subject);
22 |
23 | self::setFrom($mailMessage, $message);
24 | self::setTo($mailMessage, $message);
25 | self::setCc($mailMessage, $message);
26 | self::setBcc($mailMessage, $message);
27 | self::setHtml($mailMessage, $message);
28 | self::setText($mailMessage, $message);
29 | self::setAttachments($mailMessage, $message);
30 |
31 | return $message;
32 | }
33 |
34 | /**
35 | * @param string|array|EmailAddress $emails
36 | * @param string $method
37 | * @param Email $message
38 | * @return void
39 | */
40 | protected static function setEmail($emails, string $method, Email $message)
41 | {
42 | if (is_string($emails)) {
43 | $message->{$method}($emails);
44 |
45 | return;
46 | }
47 |
48 | if (is_array($emails)) {
49 | foreach ($emails as $email) {
50 | if ($email instanceof EmailAddress) {
51 | $email = $email->parseToMailer();
52 | }
53 |
54 | $message->{'add' . strtoupper($method)}($email);
55 | }
56 |
57 | return;
58 | }
59 |
60 | $message->{$method}($emails->parseToMailer());
61 | }
62 |
63 | /**
64 | * @param MailMessage $mailMessage
65 | * @param Email $message
66 | * @return void
67 | */
68 | public static function setFrom(MailMessage $mailMessage, Email $message): void
69 | {
70 | self::setEmail($mailMessage->from, 'from', $message);
71 | }
72 |
73 | /**
74 | * @param MailMessage $mailMessage
75 | * @param Email $message
76 | * @return void
77 | */
78 | public static function setTo(MailMessage $mailMessage, Email $message): void
79 | {
80 | self::setEmail($mailMessage->to, 'to', $message);
81 | }
82 |
83 | /**
84 | * @param MailMessage $mailMessage
85 | * @param Email $message
86 | * @return void
87 | */
88 | protected static function setCc(MailMessage $mailMessage, Email $message)
89 | {
90 | if (! is_null($mailMessage->cc)) {
91 | self::setEmail($mailMessage->cc, 'cc', $message);
92 | }
93 | }
94 |
95 | /**
96 | * @param MailMessage $mailMessage
97 | * @param Email $message
98 | * @return void
99 | */
100 | protected static function setBcc(MailMessage $mailMessage, Email $message)
101 | {
102 | if (! is_null($mailMessage->bcc)) {
103 | self::setEmail($mailMessage->bcc, 'bcc', $message);
104 | }
105 | }
106 |
107 | /**
108 | * @param MailMessage $mailMessage
109 | * @param Email $message
110 | * @return void
111 | * @throws \Exception
112 | */
113 | protected static function setHtml(MailMessage $mailMessage, Email $message)
114 | {
115 | $config = self::getConfig();
116 | $html = $mailMessage->bodyHtml;
117 |
118 | if (isset($html)) {
119 | $html = self::extractBodyMessage($html);
120 |
121 | $message->html($html, $config['mail-charset']);
122 | }
123 | }
124 |
125 | /**
126 | * @param MailMessage $mailMessage
127 | * @param Email $message
128 | * @return void
129 | * @throws \Exception
130 | */
131 | protected static function setText(MailMessage $mailMessage, Email $message)
132 | {
133 | $config = self::getConfig();
134 | $text = $mailMessage->bodyText;
135 |
136 | if (isset($mailMessage->bodyText)) {
137 | $text = self::extractBodyMessage($text);
138 |
139 | $message->text($text, $config['mail-charset']);
140 | }
141 | }
142 |
143 | /**
144 | * @param MailMessage $mailMessage
145 | * @param Email $message
146 | * @return void
147 | */
148 | protected static function setAttachments(MailMessage $mailMessage, Email $message)
149 | {
150 | /** @var File $attachment */
151 | foreach ($mailMessage->getAttachments() as $attachment) {
152 | $message->attachFromPath($attachment->getPath(), $attachment->getName());
153 | }
154 | }
155 |
156 | /**
157 | * @param string $message
158 | * @return false|resource|string
159 | */
160 | protected static function extractBodyMessage(string $message)
161 | {
162 | return realpath($message)
163 | ? fopen($message, 'r')
164 | : $message;
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/Builder/QueueBuilder.php:
--------------------------------------------------------------------------------
1 | handler = $handler;
32 | }
33 |
34 | /**
35 | * @return mixed
36 | */
37 | public function __invoke()
38 | {
39 | $this->data = func_get_args();
40 | return call_user_func($this->handler, $this);
41 | }
42 |
43 | /**
44 | * @return array the collected arguments passed to the event
45 | */
46 | public function getData()
47 | {
48 | return $this->data;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Event/EventHandlerTrait.php:
--------------------------------------------------------------------------------
1 | events[$name])) {
20 | $this->events[$name] = [];
21 | }
22 |
23 | $this->events[$name][] = $event;
24 | }
25 |
26 | /**
27 | * Removes the handlers of stack by its name.
28 | *
29 | * @param string $name
30 | */
31 | public function detach($name)
32 | {
33 | if (array_key_exists($name, $this->events)) {
34 | unset($this->events[$name]);
35 | }
36 | }
37 |
38 | /**
39 | * Fires the handlers of a stack by its name.
40 | *
41 | * @param string $name the name of the stack to fire
42 | * @param array $data
43 | */
44 | public function trigger($name, array $data = [])
45 | {
46 | if (isset($this->events[$name])) {
47 | foreach ($this->events[$name] as $event) {
48 | $event->owner = $this;
49 | call_user_func_array($event, $data);
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Exception/InvalidCallException.php:
--------------------------------------------------------------------------------
1 | x->y->z` (if `$array` is an object). If `$array['x']`
16 | * or `$array->x` is neither an array nor an object, the default value will be returned.
17 | * Note that if the array already has an element `x.y.z`, then its value will be returned
18 | * instead of going through the sub-arrays. So it is better to be done specifying an array of key names
19 | * like `['x', 'y', 'z']`.
20 | *
21 | * Below are some usage examples,
22 | *
23 | * ~~~
24 | * // working with array
25 | * $username = ArrayHelper::getValue($_POST, 'username');
26 | * // working with object
27 | * $username = ArrayHelper::getValue($user, 'username');
28 | * // working with anonymous function
29 | * $fullName = ArrayHelper::getValue($user, function ($user, $defaultValue) {
30 | * return $user->firstName . ' ' . $user->lastName;
31 | * });
32 | * // using dot format to retrieve the property of embedded object
33 | * $street = ArrayHelper::getValue($users, 'address.street');
34 | * // using an array of keys to retrieve the value
35 | * $value = ArrayHelper::getValue($versions, ['1.0', 'date']);
36 | * ~~~
37 | *
38 | * @param array|object $array array or object to extract value from
39 | * @param string|\Closure|array $key key name of the array element, an array of keys or property name of the object,
40 | * or an anonymous function returning the value. The anonymous function signature should be:
41 | * `function($array, $defaultValue)`.
42 | * @param mixed $default the default value to be returned if the specified array key does not exist. Not used when
43 | * getting value from an object.
44 | *
45 | * @return mixed the value of the element if found, default value otherwise
46 | */
47 | public static function getValue($array, $key, $default = null)
48 | {
49 | if ($key instanceof Closure) {
50 | return $key($array, $default);
51 | }
52 | if (is_array($key)) {
53 | $lastKey = array_pop($key);
54 | foreach ($key as $keyPart) {
55 | $array = static::getValue($array, $keyPart);
56 | }
57 | $key = $lastKey;
58 | }
59 | if (is_array($array) && array_key_exists($key, $array)) {
60 | return $array[$key];
61 | }
62 | if (($pos = strrpos($key, '.')) !== false) {
63 | $array = static::getValue($array, substr($key, 0, $pos), $default);
64 | $key = substr($key, $pos + 1);
65 | }
66 | if (is_object($array)) {
67 | return $array->$key;
68 | } elseif (is_array($array)) {
69 | return array_key_exists($key, $array) ? $array[$key] : $default;
70 | } else {
71 | return $default;
72 | }
73 | }
74 |
75 | /**
76 | * Removes an item from an array and returns the value. If the key does not exist in the array, the default value
77 | * will be returned instead.
78 | *
79 | * Usage examples,
80 | *
81 | * ~~~
82 | * // $array = ['type' => 'A', 'options' => [1, 2]];
83 | * // working with array
84 | * $type = ArrayHelper::remove($array, 'type');
85 | * // $array content
86 | * // $array = ['options' => [1, 2]];
87 | * ~~~
88 | *
89 | * @param array $array the array to extract value from
90 | * @param string $key key name of the array element
91 | * @param mixed $default the default value to be returned if the specified key does not exist
92 | *
93 | * @return mixed|null the value of the element if found, default value otherwise
94 | */
95 | public static function remove(&$array, $key, $default = null)
96 | {
97 | if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
98 | $value = $array[$key];
99 | unset($array[$key]);
100 | return $value;
101 | }
102 |
103 | return $default;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/Helper/ConfigReader.php:
--------------------------------------------------------------------------------
1 | email = $email;
25 | $this->name = $name;
26 | }
27 |
28 | /**
29 | * @param string $email
30 | * @param string|null $name
31 | * @return static
32 | */
33 | public static function make(string $email, ?string $name = null): self
34 | {
35 | return new self($email, $name);
36 | }
37 |
38 | /**
39 | * @return string
40 | */
41 | public function getEmail(): string
42 | {
43 | return $this->email;
44 | }
45 |
46 | /**
47 | * @return string|null
48 | */
49 | public function getName(): ?string
50 | {
51 | return $this->name;
52 | }
53 |
54 | /**
55 | * @return Address
56 | */
57 | public function parseToMailer(): Address
58 | {
59 | return new Address($this->getEmail(), $this->getName());
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Mail/Dto/File.php:
--------------------------------------------------------------------------------
1 | path = $path;
23 | $this->name = $name;
24 | }
25 |
26 | /**
27 | * @param string $path
28 | * @param string|null $name
29 | * @return File
30 | */
31 | public static function make(string $path, ?string $name = ''): self
32 | {
33 | return new self($path, $name);
34 | }
35 |
36 | /**
37 | * @return string
38 | */
39 | public function getPath(): string
40 | {
41 | return $this->path;
42 | }
43 |
44 | /**
45 | * @return string|null
46 | */
47 | public function getName(): ?string
48 | {
49 | return $this->name;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Mailer.php:
--------------------------------------------------------------------------------
1 | transport = $transport;
31 | $this->logging = $logging;
32 | }
33 |
34 | /**
35 | * Returns the mail transport used.
36 | *
37 | * @return null|TransportInterface
38 | */
39 | public function getTransport(): TransportInterface
40 | {
41 | return $this->transport;
42 | }
43 |
44 | /**
45 | * Returns the Symfony Mailer Transport instance.
46 | *
47 | * @return null|\Symfony\Component\Mailer\Transport\TransportInterface
48 | */
49 | public function getTransportInstance()
50 | {
51 | return $this->getTransport()->getInstance();
52 | }
53 |
54 | /**
55 | * @return null|string logged messages as string
56 | */
57 | public function getLog()
58 | {
59 | // TODO Add log mechanism
60 | return null;
61 | }
62 |
63 | /**
64 | * Modifies the transport used.
65 | *
66 | * @param \Symfony\Component\Mailer\Transport\TransportInterface $transport
67 | */
68 | public function setTransport(TransportInterface $transport)
69 | {
70 | $this->transport = $transport;
71 | }
72 |
73 | /**
74 | * Sends a MailMessage instance.
75 | *
76 | * View files can be added to the `$views` array argument and if set, they will be parsed via the `PhpViewFileHelper`
77 | * helper that this library contains.
78 | *
79 | * The `$view` argument has the following syntax:
80 | *
81 | * ```
82 | * $view = [
83 | * 'text' => '/path/to/plain/text/email.php',
84 | * 'html' => '/path/to/html/email.php'
85 | * ];
86 | * ```
87 | * The `PhpViewFileHelper` will use the `$data` array argument to parse the templates.
88 | *
89 | * The template files must be of `php` type if you wish to use internal system. Otherwise, is highly recommended to
90 | * use your own template parser and set the `bodyHtml` and `bodyText` of the `MailMessage` class.
91 | *
92 | * @param MailMessage $message the MailMessage instance to send
93 | * @param array $views the view files for `text` and `html` templates
94 | * @param array $data the data to be used for parsing the templates
95 | *
96 | * @return SentMessage|null
97 | *
98 | * @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface
99 | * @see MailMessage::$bodyHtml
100 | * @see MailMessage::$bodyText
101 | * @see PhpViewFileHelper::render()
102 | */
103 | public function send(MailMessage $message, array $views = [], array $data = []): ?SentMessage
104 | {
105 | $message = MessageBuilder::make($message);
106 | return $this->getTransportInstance()->send($message);
107 | }
108 |
109 | /**
110 | * Factory method to create an instance of the mailer based on the configuration of a `MailMessage` instance.
111 | *
112 | * @param MailMessage $mailMessage the instance to create the Mailer from
113 | *
114 | * @return Mailer instance
115 | */
116 | public static function fromMailMessage(MailMessage $mailMessage)
117 | {
118 | $options = [
119 | 'host' => $mailMessage->host,
120 | 'port' => $mailMessage->port,
121 | 'options' => $mailMessage->transportOptions,
122 | ];
123 | $factory = TransportFactory::create($options, $mailMessage->transportType);
124 | return new Mailer($factory->create());
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Model/AbstractMailObject.php:
--------------------------------------------------------------------------------
1 | $value) {
21 | if (is_string($name) && $value !== null) {
22 | $this->{$name} = $value;
23 | }
24 | }
25 | }
26 |
27 | /**
28 | * Helper function to work as a builder for collection items iterations.
29 | *
30 | * @param $array
31 | *
32 | * @return static
33 | */
34 | public static function fromArray(array $array)
35 | {
36 | return new static($array);
37 | }
38 |
39 | /**
40 | * Sets value of an object property.
41 | *
42 | * Do not call this method directly as it is a PHP magic method that
43 | * will be implicitly called when executing `$object->property = $value;`.
44 | *
45 | * @param string $name the property name or the event name
46 | * @param mixed $value the property value
47 | *
48 | * @throws Exception if the property is not defined or read-only
49 | */
50 | public function __set($name, $value)
51 | {
52 | $setter = 'set' . $name;
53 | if (method_exists($this, $setter)) {
54 | $this->$setter($value);
55 | } elseif (method_exists($this, 'get' . $name)) {
56 | throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
57 | } else {
58 | throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
59 | }
60 | }
61 |
62 | /**
63 | * Returns the value of an object property.
64 | *
65 | * @param string $name
66 | *
67 | * @throws Exception if the property is not defined or write-only
68 | *
69 | * @return mixed the property value
70 | *
71 | */
72 | public function __get($name)
73 | {
74 | $getter = 'get' . $name;
75 | if (method_exists($this, $getter)) {
76 | return $this->$getter();
77 | } elseif (method_exists($this, 'set' . $name)) {
78 | throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
79 | } else {
80 | throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
81 | }
82 | }
83 |
84 | /**
85 | * Checks if a property is set, i.e. defined and not null.
86 | *
87 | * Do not call this method directly as it is a PHP magic method that
88 | * will be implicitly called when executing `isset($object->property)`.
89 | *
90 | * Note that if the property is not defined, false will be returned.
91 | *
92 | * @param string $name the property name or the event name
93 | *
94 | * @return bool whether the named property is set (not null).
95 | *
96 | * @see http://php.net/manual/en/function.isset.php
97 | */
98 | public function __isset($name)
99 | {
100 | $getter = 'get' . $name;
101 | if (method_exists($this, $getter)) {
102 | return $this->$getter() !== null;
103 | } else {
104 | return false;
105 | }
106 | }
107 |
108 | /**
109 | * Sets an object property to null.
110 | *
111 | * Do not call this method directly as it is a PHP magic method that
112 | * will be implicitly called when executing `unset($object->property)`.
113 | *
114 | * Note that if the property is not defined, this method will do nothing.
115 | * If the property is read-only, it will throw an exception.
116 | *
117 | * @param string $name the property name
118 | *
119 | * @throws InvalidCallException if the property is read only.
120 | *
121 | * @see http://php.net/manual/en/function.unset.php
122 | */
123 | public function __unset($name)
124 | {
125 | $setter = 'set' . $name;
126 | if (method_exists($this, $setter)) {
127 | $this->$setter(null);
128 | } elseif (method_exists($this, 'get' . $name)) {
129 | throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/Model/MailJob.php:
--------------------------------------------------------------------------------
1 | getId() === null;
41 | }
42 |
43 | /**
44 | * @return mixed
45 | */
46 | public function getId()
47 | {
48 | return $this->id;
49 | }
50 |
51 | /**
52 | * @param mixed $id
53 | */
54 | public function setId($id)
55 | {
56 | $this->id = $id;
57 | }
58 |
59 | /**
60 | * @return MailMessage|string
61 | */
62 | public function getMessage()
63 | {
64 | return $this->message;
65 | }
66 |
67 | /**
68 | * @param MailMessage|string $message
69 | */
70 | public function setMessage($message)
71 | {
72 | $this->message = $message;
73 | }
74 |
75 | /**
76 | * @return int
77 | */
78 | public function getAttempt()
79 | {
80 | return $this->attempt;
81 | }
82 |
83 | /**
84 | * @param $attempt
85 | */
86 | public function setAttempt($attempt)
87 | {
88 | $this->attempt = $attempt;
89 | }
90 |
91 | /**
92 | * Increments attempt by one.
93 | */
94 | public function incrementAttempt()
95 | {
96 | $this->attempt += 1;
97 | }
98 |
99 | /**
100 | * @return bool
101 | */
102 | public function markAsCompleted()
103 | {
104 | return $this->completed = true;
105 | }
106 |
107 | /**
108 | * @return bool
109 | */
110 | public function isCompleted()
111 | {
112 | return $this->completed === true;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Model/MailMessage.php:
--------------------------------------------------------------------------------
1 | 'Obiwoan',
20 | * 'password' => 'Kenobi',
21 | * 'encryption' => 'ssl',
22 | * 'authMode' => 'Plain'
23 | * ]
24 | * ```
25 | *
26 | * MailTransport may contain:
27 | * ```
28 | * ['f%s']
29 | * ```
30 | *
31 | * SendMailTransport may contain:
32 | * ```
33 | * ['/usr/sbin/sendmail -bs']
34 | * ```
35 | */
36 | public $transportOptions = [];
37 | /**
38 | * @var string the transport type. It could be TransportInterface::TYPE_SMTP, TransportInterface::TYPE_MAIL, o
39 | * TransportInterface::TYPE_SEND_MAIL.
40 | */
41 | public $transportType;
42 | /**
43 | * @var string the mail server address.
44 | */
45 | public $host;
46 | /**
47 | * @var int the mail server port.
48 | */
49 | public $port;
50 | /**
51 | * @var array|string the from address/es
52 | */
53 | public $from;
54 | /**
55 | * @var array|string the to address/es
56 | */
57 | public $to;
58 | /**
59 | * @var array|string the cc address/es
60 | */
61 | public $cc;
62 | /**
63 | * @var array|string the bcc address/es
64 | */
65 | public $bcc;
66 | /**
67 | * @var string the subject of the mail message
68 | */
69 | public $subject;
70 | /**
71 | * @var string the body html of the mail message
72 | */
73 | public $bodyHtml;
74 | /**
75 | * @var string the body
76 | */
77 | public $bodyText;
78 | /**
79 | * @var array|null the file paths to attach to the Swift_Message instance if `asSwiftMessage()` is called
80 | */
81 | protected $attachments;
82 | /**
83 | * {@inheritdoc}
84 | */
85 | public function __construct(array $config = [])
86 | {
87 | parent::__construct($config);
88 | }
89 |
90 | /**
91 | * @param array $config
92 | * @return MailMessage
93 | */
94 | public static function make(array $config)
95 | {
96 | return new self($config);
97 | }
98 |
99 | /**
100 | * Specify data which should be serialized to JSON.
101 | *
102 | * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
103 | *
104 | * @return mixed data which can be serialized by json_encode,
105 | * which is a value of any type other than a resource.
106 | *
107 | * @since 5.4.0
108 | */
109 | public function jsonSerialize()
110 | {
111 | return get_object_vars($this);
112 | }
113 |
114 | /**
115 | * @return void
116 | * @throws \Da\Mailer\Exception\UndefinedMessageBrokerException
117 | */
118 | public function enqueue()
119 | {
120 | $job = MailJobBuilder::make(['message' => json_encode($this)]);
121 | QueueBuilder::make()->enqueue($job);
122 | }
123 |
124 | /**
125 | * @param string $path
126 | * @param string|null $name
127 | * @return void
128 | */
129 | public function addAttachment(string $path, ?string $name = null): void
130 | {
131 | if (is_null($this->attachments)) {
132 | $this->attachments = [File::make($path, $name)];
133 | return;
134 | }
135 |
136 | $this->attachments[] = File::make($path, $name);
137 | }
138 |
139 | /**
140 | * @return array
141 | */
142 | public function getAttachments(): array
143 | {
144 | return $this->attachments ?? [];
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/Queue/Backend/AbstractQueueStoreConnection.php:
--------------------------------------------------------------------------------
1 | configuration = $configuration;
25 | }
26 |
27 | /**
28 | * @param $key
29 | * @param null $default
30 | *
31 | * @return mixed
32 | */
33 | protected function getConfigurationValue($key, $default = null)
34 | {
35 | return ArrayHelper::getValue($this->configuration, $key, $default);
36 | }
37 |
38 | /**
39 | * Disconnects previous connection.
40 | */
41 | public function disconnect()
42 | {
43 | $this->instance = null;
44 | }
45 |
46 | /**
47 | * @return AbstractQueueStoreConnection
48 | */
49 | abstract public function connect();
50 | /**
51 | * @return mixed
52 | */
53 | abstract public function getInstance();
54 | }
55 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Beanstalkd/BeanstalkdMailJob.php:
--------------------------------------------------------------------------------
1 | timeToSend;
24 | }
25 |
26 | /**
27 | * @param int $timestamp
28 | */
29 | public function setTimeToSend($timestamp)
30 | {
31 | $this->timeToSend = $timestamp;
32 | }
33 |
34 | /**
35 | * @param PheanstalkJob $job
36 | */
37 | public function setPheanstalkJob(PheanstalkJob $job)
38 | {
39 | $this->pheanstalkJob = $job;
40 | }
41 |
42 | /**
43 | * @return PheanstalkJob
44 | */
45 | public function getPheanstalkJob()
46 | {
47 | return $this->pheanstalkJob;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Beanstalkd/BeanstalkdQueueStoreAdapter.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
45 | $this->queueName = $queueName;
46 | $this->timeToRun = $timeToRun;
47 | $this->reserveTimeout = $reserveTimeOut;
48 | $this->init();
49 | }
50 |
51 | /**
52 | * @return BeanstalkdQueueStoreAdapter
53 | */
54 | public function init()
55 | {
56 | $this->getConnection()->connect();
57 | return $this;
58 | }
59 |
60 | /**
61 | * @return BeanstalkdQueueStoreConnection
62 | */
63 | public function getConnection()
64 | {
65 | return $this->connection;
66 | }
67 |
68 | /**
69 | * @param BeanstalkdMailJob|MailJobInterface $mailJob
70 | *
71 | * @return \Pheanstalk\Job
72 | */
73 | public function enqueue(MailJobInterface $mailJob)
74 | {
75 | $timestamp = $mailJob->getTimeToSend();
76 | $payload = $this->createPayload($mailJob);
77 | $delay = (int) max(Pheanstalk::DEFAULT_DELAY, $timestamp - time());
78 | return $this->getConnection()
79 | ->getInstance()
80 | ->useTube($this->queueName)
81 | ->put($payload, Pheanstalk::DEFAULT_PRIORITY, $delay, $this->timeToRun);
82 | }
83 |
84 | /**
85 | * @return BeanstalkdMailJob|null
86 | * @throws \Pheanstalk\Exception\DeadlineSoonException
87 | */
88 | public function dequeue()
89 | {
90 | $job = $this->getConnection()->getInstance()->watch($this->queueName)->reserveWithTimeout($this->reserveTimeout);
91 | if ($job instanceof PheanstalkJob) {
92 | $data = json_decode($job->getData(), true);
93 | return new BeanstalkdMailJob([
94 | 'id' => $data['id'],
95 | 'attempt' => $data['attempt'],
96 | 'message' => $data['message'],
97 | 'pheanstalkJob' => $job,
98 | ]);
99 | }
100 |
101 | return null;
102 | }
103 |
104 | /**
105 | * @param BeanstalkdMailJob|MailJobInterface $mailJob
106 | * @return null
107 | */
108 | public function ack(MailJobInterface $mailJob)
109 | {
110 | if ($mailJob->isNewRecord()) {
111 | throw new InvalidCallException('BeanstalkdMailJob cannot be a new object to be acknowledged');
112 | }
113 |
114 | $pheanstalk = $this->getConnection()->getInstance()->useTube($this->queueName);
115 | if ($mailJob->isCompleted()) {
116 | $pheanstalk->delete($mailJob->getPheanstalkJob());
117 | return null;
118 | }
119 |
120 | $timestamp = $mailJob->getTimeToSend();
121 | $delay = max(0, $timestamp - time());
122 | // add back to the queue as it wasn't completed maybe due to some transitory error
123 | // could also be failed.
124 | $pheanstalk->release($mailJob->getPheanstalkJob(), Pheanstalk::DEFAULT_PRIORITY, $delay);
125 | return null;
126 | }
127 |
128 | /**
129 | *
130 | * @return bool
131 | */
132 | public function isEmpty()
133 | {
134 | $stats = $this->getConnection()->getInstance()->statsTube($this->queueName);
135 | return (int) $stats->current_jobs_delayed === 0
136 | && (int) $stats->current_jobs_urgent === 0
137 | && (int) $stats->current_jobs_ready === 0;
138 | }
139 |
140 | /**
141 | * @param BeanstalkdMailJob|MailJobInterface $mailJob
142 | *
143 | * @return string
144 | */
145 | protected function createPayload(MailJobInterface $mailJob)
146 | {
147 | return json_encode([
148 | 'id' => $mailJob->isNewRecord() ? sha1(Random::string(32)) : $mailJob->getId(),
149 | 'attempt' => $mailJob->getAttempt(),
150 | 'message' => $mailJob->getMessage(),
151 | ]);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Beanstalkd/BeanstalkdQueueStoreConnection.php:
--------------------------------------------------------------------------------
1 | disconnect();
30 | $host = $this->getConfigurationValue('host', '127.0.0.1');
31 | $port = $this->getConfigurationValue('port', Pheanstalk::DEFAULT_PORT);
32 | $connectionTimeout = $this->getConfigurationValue('connectionTimeout');
33 | $connectPersistent = $this->getConfigurationValue('connectPersistent', false);
34 | $connection = new Connection(new SocketFactory($host, $port ?: Pheanstalk::DEFAULT_PORT, $connectionTimeout ?? 0, $connectPersistent ?? SocketFactory::AUTODETECT));
35 | $this->instance = new Pheanstalk($connection);
36 | return $this;
37 | }
38 |
39 | /**
40 | * @return Pheanstalk
41 | */
42 | public function getInstance()
43 | {
44 | if ($this->instance === null) {
45 | $this->connect();
46 | }
47 |
48 | return $this->instance;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Queue/Backend/MailJobInterface.php:
--------------------------------------------------------------------------------
1 | timeToSend ?: date('Y-m-d H:i:s', time());
49 | }
50 |
51 | /**
52 | * @param string $date
53 | */
54 | public function setTimeToSend($date)
55 | {
56 | $this->timeToSend = $date;
57 | }
58 |
59 | /**
60 | * @return string
61 | */
62 | public function getState()
63 | {
64 | return $this->state;
65 | }
66 |
67 | /**
68 | * Marks the state as completed. After marking the instance as completed, we should call the
69 | * `PdoQueueStoreAdapter::ack()` method to update the database with new status.
70 | */
71 | public function markAsCompleted()
72 | {
73 | $this->state = self::STATE_COMPLETED;
74 | parent::markAsCompleted();
75 | }
76 |
77 | /**
78 | * Marks the state as new. If we update the status back to 'N'ew, we could send it back to queue by using the
79 | * `PdoQueueStoreAdapter::ack()` method. That means even including a new time to be processed in the future by
80 | * setting the `$timeToSend` in a future date.
81 | */
82 | public function markAsNew()
83 | {
84 | $this->state = self::STATE_NEW;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Pdo/PdoQueueStoreAdapter.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
29 | $this->tableName = $tableName;
30 | $this->init();
31 | }
32 |
33 | /**
34 | * @return PdoQueueStoreAdapter|QueueStoreAdapterInterface
35 | */
36 | public function init()
37 | {
38 | $this->getConnection()->connect();
39 | return $this;
40 | }
41 |
42 | /**
43 | * @return PdoQueueStoreConnection
44 | */
45 | public function getConnection()
46 | {
47 | return $this->connection;
48 | }
49 |
50 | /**
51 | * Adds a MailJob to the queue.
52 | *
53 | * @param MailJobInterface|PdoMailJob $mailJob
54 | *
55 | * @return bool whether it has been successfully inserted or not
56 | */
57 | public function enqueue(MailJobInterface $mailJob)
58 | {
59 | $sql = sprintf('INSERT INTO `%s` (`message`, `timeToSend`) VALUES (:message, :timeToSend)', $this->tableName);
60 | $query = $this->getConnection()->getInstance()->prepare($sql);
61 | $query->bindValue(':message', $mailJob->getMessage());
62 | $query->bindValue(':timeToSend', $mailJob->getTimeToSend());
63 | return $query->execute();
64 | }
65 |
66 | /**
67 | * Returns a MailJob extracted from the database. The row at the database is marked as 'A'ctive or in process.
68 | *
69 | * @return MailJobInterface|PdoMailJob
70 | */
71 | public function dequeue()
72 | {
73 | $this->getConnection()->getInstance()->beginTransaction();
74 | $mailJob = null;
75 | $sqlText = 'SELECT `id`, `message`, `attempt`
76 | FROM `%s` WHERE `timeToSend` <= :timeToSend AND `state`=:state
77 | ORDER BY id ASC LIMIT 1 FOR UPDATE';
78 | $sql = sprintf($sqlText, $this->tableName);
79 | $query = $this->getConnection()->getInstance()->prepare($sql);
80 | $query->bindValue(':state', PdoMailJob::STATE_NEW);
81 | $query->bindValue(':timeToSend', date('Y-m-d H:i:s'), time());
82 | $query->execute();
83 | $queryResult = $query->fetch(PDO::FETCH_ASSOC);
84 | if ($queryResult) {
85 | //
86 | $sqlText = 'UPDATE `%s` SET `state`=:state WHERE `id`=:id';
87 | $sql = sprintf($sqlText, $this->tableName);
88 | $query = $this->getConnection()->getInstance()->prepare($sql);
89 | $query->bindValue(':state', PdoMailJob::STATE_ACTIVE);
90 | $query->bindValue(':id', $queryResult['id'], PDO::PARAM_INT);
91 | $query->execute();
92 | $mailJob = new PdoMailJob($queryResult);
93 | }
94 |
95 | $this->getConnection()->getInstance()->commit();
96 | return $mailJob;
97 | }
98 |
99 | /**
100 | * 'Ack'knowledge the MailJob. Once a MailJob as been processed it could be:.
101 | *
102 | * - Updated its status to 'C'ompleted
103 | * - Updated its status to 'N'ew and set its `timeToSend` attribute to a future date
104 | *
105 | * @param MailJobInterface|PdoMailJob $mailJob
106 | *
107 | * @return bool
108 | */
109 | public function ack(MailJobInterface $mailJob)
110 | {
111 | if ($mailJob->isNewRecord()) {
112 | throw new InvalidCallException('PdoMailJob cannot be a new object to be acknowledged');
113 | }
114 |
115 | $sqlText = 'UPDATE `%s`
116 | SET `attempt`=:attempt, `state`=:state, `timeToSend`=:timeToSend, `sentTime`=:sentTime
117 | WHERE `id`=:id';
118 | $sql = sprintf($sqlText, $this->tableName);
119 | $sentTime = $mailJob->isCompleted() ? date('Y-m-d H:i:s', time()) : null;
120 | $query = $this->getConnection()->getInstance()->prepare($sql);
121 | $query->bindValue(':id', $mailJob->getId(), PDO::PARAM_INT);
122 | $query->bindValue(':attempt', $mailJob->getAttempt(), PDO::PARAM_INT);
123 | $query->bindValue(':state', $mailJob->getState());
124 | $query->bindValue(':timeToSend', $mailJob->getTimeToSend());
125 | $query->bindValue(':sentTime', $sentTime);
126 | return $query->execute();
127 | }
128 |
129 | /**
130 | * {@inheritdoc}
131 | */
132 | public function isEmpty()
133 | {
134 | $sql = sprintf('SELECT COUNT(`id`) FROM `%s` WHERE `timeToSend` <= :timeToSend AND `state`=:state ORDER BY id ASC LIMIT 1', $this->tableName);
135 | $query = $this->getConnection()->getInstance()->prepare($sql);
136 | $query->bindValue(':state', PdoMailJob::STATE_NEW);
137 | $query->bindValue(':timeToSend', date('Y-m-d H:i:s'), time());
138 | $query->execute();
139 | return intval($query->fetchColumn(0)) === 0;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Pdo/PdoQueueStoreConnection.php:
--------------------------------------------------------------------------------
1 | defineConnectionString();
19 | }
20 |
21 | protected function defineConnectionString()
22 | {
23 | if (! isset($this->configuration['dsn'])) {
24 | $this->configuration['dsn'] = sprintf("mysql:host=%s;dbname=%s;port=%s", $this->configuration['host'] ?? '', $this->configuration['db'] ?? '', $this->configuration['port'] ?? 3306);
25 | }
26 | }
27 |
28 | /**
29 | * @return PdoQueueStoreConnection
30 | */
31 | public function connect()
32 | {
33 | $this->disconnect();
34 | $username = $this->getConfigurationValue('username');
35 | $password = $this->getConfigurationValue('password');
36 | $options = $this->getConfigurationValue('options');
37 | $this->instance = new PDO($this->getConfigurationValue('dsn'), $username, $password, $options);
38 | $this->instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
39 | return $this;
40 | }
41 |
42 | /**
43 | * Returns the connection instance.
44 | *
45 | * @return PDO
46 | */
47 | public function getInstance()
48 | {
49 | if ($this->instance === null) {
50 | $this->connect();
51 | }
52 |
53 | return $this->instance;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Queue/Backend/QueueStoreAdapterInterface.php:
--------------------------------------------------------------------------------
1 | deliveryTag;
18 | }
19 |
20 | /**
21 | * @param $delivery_tag
22 | * @return void
23 | */
24 | public function setDeliveryTag($deliveryTag)
25 | {
26 | $this->deliveryTag = $deliveryTag;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Queue/Backend/RabbitMq/RabbitMqQueueConnection.php:
--------------------------------------------------------------------------------
1 | connection)) {
32 | $this->disconnect();
33 | }
34 |
35 | $this->connection = new AMQPStreamConnection(
36 | $this->configuration['host'],
37 | $this->configuration['port'],
38 | $this->configuration['user'],
39 | $this->configuration['password']
40 | );
41 |
42 | $this->instance = $this->connection->channel();
43 | $this->instance->confirm_select();
44 |
45 | return $this;
46 | }
47 |
48 | /**
49 | * @inheritDoc
50 | */
51 | public function getInstance()
52 | {
53 | if (is_null($this->instance)) {
54 | $this->connect();
55 | }
56 |
57 | return $this->instance;
58 | }
59 |
60 | public function disconnect()
61 | {
62 | if (is_null($this->connection)) {
63 | return;
64 | }
65 |
66 | $this->instance->close();
67 | $this->connection->close();
68 | $this->instance = null;
69 | $this->connection = null;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Queue/Backend/RabbitMq/RabbitMqQueueStoreAdapter.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
36 | $this->expireTime = $expireTime;
37 | $this->queueName = $queueName;
38 |
39 | $this->init();
40 | }
41 |
42 | /**
43 | * @return
44 | */
45 | public function init()
46 | {
47 | $this->getConnection()
48 | ->connect();
49 |
50 | return $this;
51 | }
52 |
53 | /**
54 | * @inheritDoc
55 | */
56 | public function getConnection()
57 | {
58 | return $this->connection;
59 | }
60 |
61 | /**
62 | * @var RabbitMqJob|MailJobInterface $mailJob
63 | *
64 | * @return bool;
65 | */
66 | public function enqueue(MailJobInterface $mailJob)
67 | {
68 | try {
69 | /** @var AMQPChannel $chanel */
70 | $chanel = $this->getConnection()->getInstance();
71 | $chanel->queue_declare($this->queueName, false, false, false, false);
72 | $message = new AMQPMessage($this->createPayload($mailJob));
73 | $chanel->basic_publish($message, '', $this->queueName);
74 |
75 | return true;
76 | } catch (\Exception $exception) {
77 | return false;
78 | }
79 | }
80 |
81 | /**
82 | * @inheritDoc
83 | * @return RabbitMqJob|MailJobInterface|null
84 | */
85 | public function dequeue()
86 | {
87 | if ($this->isEmpty()) {
88 | return null;
89 | }
90 |
91 | /** @var AMQPChannel $chanel */
92 | $chanel = $this->getConnection()->getInstance();
93 |
94 | /** @var AMQPMessage $message */
95 | $message = $chanel->basic_get($this->queueName);
96 |
97 | $data = json_decode($message->body, true);
98 |
99 | return new RabbitMqJob([
100 | 'id' => $data['id'],
101 | 'message' => $data['message'],
102 | 'attempt' => $data['attempt'],
103 | 'deliveryTag' => $message->delivery_info['delivery_tag'],
104 | ]);
105 | }
106 |
107 | /**
108 | * @param RabbitMqJob $mailJob
109 | */
110 | public function ack(MailJobInterface $mailJob)
111 | {
112 | /** @var AMQPChannel $chanel */
113 | $chanel = $this->getConnection()->getInstance();
114 | if ($mailJob->isCompleted()) {
115 | $chanel->basic_ack($mailJob->getDeliveryTag(), false);
116 | return;
117 | }
118 |
119 | $chanel->basic_nack($mailJob->getDeliveryTag(), false, true);
120 | }
121 |
122 | /**
123 | * @inheritDoc
124 | */
125 | public function isEmpty()
126 | {
127 | /** @var AMQPChannel $chanel */
128 | $chanel = $this->getConnection()->getInstance();
129 | $queueProperties = $chanel->queue_declare($this->queueName, false, false, false, false);
130 |
131 | return is_array($queueProperties) && $queueProperties[1] === 0;
132 | }
133 |
134 | /**
135 | * @param MailJobInterface $mailJob
136 | * @return false|string
137 | */
138 | protected function createPayload(MailJobInterface $mailJob)
139 | {
140 | return json_encode([
141 | 'id' => $mailJob->isNewRecord() ? sha1(Random::string(32)) : $mailJob->getId(),
142 | 'attempt' => $mailJob->getAttempt(),
143 | 'message' => $mailJob->getMessage(),
144 | 'delivery_tag' => null,
145 | ]);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Redis/RedisMailJob.php:
--------------------------------------------------------------------------------
1 | timeToSend;
24 | }
25 |
26 | /**
27 | * @param int $timestamp
28 | */
29 | public function setTimeToSend($timestamp)
30 | {
31 | $this->timeToSend = $timestamp;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Redis/RedisQueueStoreAdapter.php:
--------------------------------------------------------------------------------
1 | expireTime = $expireTime;
34 | $this->connection = $connection;
35 | $this->queueName = $queueName;
36 | $this->init();
37 | }
38 |
39 | /**
40 | * @return RedisQueueStoreAdapter
41 | */
42 | public function init()
43 | {
44 | $this->getConnection()
45 | ->connect();
46 | return $this;
47 | }
48 |
49 | /**
50 | * @return RedisQueueStoreConnection
51 | */
52 | public function getConnection()
53 | {
54 | return $this->connection;
55 | }
56 |
57 | /**
58 | * @param RedisMailJob|MailJobInterface $mailJob
59 | *
60 | * @return int
61 | */
62 | public function enqueue(MailJobInterface $mailJob)
63 | {
64 | $timestamp = $mailJob->getTimeToSend();
65 | $payload = $this->createPayload($mailJob);
66 | return $timestamp !== null && $timestamp > time()
67 | ? $this->getConnection()->getInstance()->zadd($this->queueName . ':delayed', $timestamp, $payload)
68 | : $this->getConnection()->getInstance()->rpush($this->queueName, $payload);
69 | }
70 |
71 | /**
72 | * @return RedisMailJob|null
73 | */
74 | public function dequeue()
75 | {
76 | $this->migrateExpiredJobs();
77 | $job = $this->getConnection()->getInstance()->lpop($this->queueName);
78 | if ($job !== null) {
79 | $this->getConnection()
80 | ->getInstance()
81 | ->zadd($this->queueName . ':reserved', time() + $this->expireTime, $job);
82 | $data = json_decode($job, true);
83 | return new RedisMailJob([
84 | 'id' => $data['id'],
85 | 'attempt' => $data['attempt'],
86 | 'message' => $data['message'],
87 | ]);
88 | }
89 |
90 | return null;
91 | }
92 |
93 | /**
94 | * @param RedisMailJob|MailJobInterface $mailJob
95 | */
96 | public function ack(MailJobInterface $mailJob)
97 | {
98 | if ($mailJob->isNewRecord()) {
99 | throw new InvalidCallException('RedisMailJob cannot be a new object to be acknowledged');
100 | }
101 |
102 | $this->removeReserved($mailJob);
103 | if (!$mailJob->isCompleted()) {
104 | if ($mailJob->getTimeToSend() === null || $mailJob->getTimeToSend() < time()) {
105 | $mailJob->setTimeToSend(time() + $this->expireTime);
106 | }
107 | $this->enqueue($mailJob);
108 | }
109 | }
110 |
111 | /**
112 | * @param MailJobInterface $mailJob
113 | */
114 | public function removeReserved(MailJobInterface $mailJob)
115 | {
116 | $payload = $this->createPayload($mailJob);
117 | $this->getConnection()->getInstance()->zrem($this->queueName . ':reserved', $payload);
118 | }
119 |
120 | /**
121 | * {@inheritdoc}
122 | */
123 | public function isEmpty()
124 | {
125 | return $this->getConnection()->getInstance()->llen($this->queueName) === 0;
126 | }
127 |
128 | /**
129 | * @param RedisMailJob|MailJobInterface $mailJob
130 | *
131 | * @return string
132 | */
133 | protected function createPayload(MailJobInterface $mailJob)
134 | {
135 | return json_encode([
136 | 'id' => $mailJob->isNewRecord() ? sha1(Random::string(32)) : $mailJob->getId(),
137 | 'attempt' => $mailJob->getAttempt(),
138 | 'message' => $mailJob->getMessage(),
139 | ]);
140 | }
141 |
142 | /**
143 | * Migrates all expired jobs from delayed and reserved queues to the main queue to be processed.
144 | */
145 | protected function migrateExpiredJobs()
146 | {
147 | $this->migrateJobs($this->queueName . ':delayed', $this->queueName);
148 | $this->migrateJobs($this->queueName . ':reserved', $this->queueName);
149 | }
150 |
151 | /**
152 | * Migrates expired jobs from one queue to another.
153 | *
154 | * @param string $from the name of the source queue
155 | * @param string $to the name of the target queue
156 | */
157 | protected function migrateJobs($from, $to)
158 | {
159 | $options = ['cas' => true, 'watch' => $from, 'retry' => 10];
160 | $this->getConnection()->getInstance()->transaction($options, function ($transaction) use ($from, $to) {
161 |
162 | $time = time();
163 | // First we need to get all of jobs that have expired based on the current time
164 | // so that we can push them onto the main queue. After we get them we simply
165 | // remove them from this "delay" queues. All of this within a transaction.
166 | $jobs = $this->getExpiredJobs($transaction, $from, $time);
167 | // If we actually found any jobs, we will remove them from the old queue and we
168 | // will insert them onto the new (ready) "queue". This means they will stand
169 | // ready to be processed by the queue worker whenever their turn comes up.
170 | if (count($jobs) > 0) {
171 | $this->removeExpiredJobs($transaction, $from, $time);
172 | $this->pushExpiredJobsOntoNewQueue($transaction, $to, $jobs);
173 | }
174 | });
175 | }
176 |
177 | /**
178 | * Get the expired jobs from a given queue.
179 | *
180 | * @param \Predis\Transaction\MultiExec $transaction
181 | * @param string $from
182 | * @param int $time
183 | *
184 | * @return array
185 | */
186 | protected function getExpiredJobs($transaction, $from, $time)
187 | {
188 | return $transaction->zrangebyscore($from, '-inf', $time);
189 | }
190 |
191 | /**
192 | * Remove the expired jobs from a given queue.
193 | *
194 | * @param \Predis\Transaction\MultiExec $transaction
195 | * @param string $from
196 | * @param int $time
197 | *
198 | */
199 | protected function removeExpiredJobs($transaction, $from, $time)
200 | {
201 | $transaction->multi();
202 | $transaction->zremrangebyscore($from, '-inf', $time);
203 | }
204 |
205 | /**
206 | * Push all of the given jobs onto another queue.
207 | *
208 | * @param \Predis\Transaction\MultiExec $transaction
209 | * @param string $to
210 | * @param array $jobs
211 | *
212 | */
213 | protected function pushExpiredJobsOntoNewQueue($transaction, $to, $jobs)
214 | {
215 | call_user_func_array([$transaction, 'rpush'], array_merge([$to], $jobs));
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Redis/RedisQueueStoreConnection.php:
--------------------------------------------------------------------------------
1 | disconnect();
29 | $this->instance = new Client($this->configuration);
30 | return $this;
31 | }
32 |
33 | /**
34 | * Returns the client predis instance.
35 | *
36 | * @return Client
37 | */
38 | public function getInstance()
39 | {
40 | if ($this->instance === null) {
41 | $this->connect();
42 | }
43 |
44 | return $this->instance;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Sqs/SqsMailJob.php:
--------------------------------------------------------------------------------
1 | getId() === null || $this->receiptHandle === null;
48 | }
49 |
50 | /**
51 | * @return string
52 | */
53 | public function getReceiptHandle()
54 | {
55 | return $this->receiptHandle;
56 | }
57 |
58 | /**
59 | * @param string $receiptHandle
60 | */
61 | public function setReceiptHandle($receiptHandle)
62 | {
63 | $this->receiptHandle = $receiptHandle;
64 | }
65 |
66 | /**
67 | * @return int
68 | */
69 | public function getDelaySeconds()
70 | {
71 | return $this->delaySeconds;
72 | }
73 |
74 | /**
75 | * @param int $delaySeconds
76 | */
77 | public function setDelaySeconds($delaySeconds)
78 | {
79 | if ($delaySeconds < 0 || $delaySeconds > 900) {
80 | throw new BadMethodCallException('Delay seconds must be between 0 and 900 seconds interval');
81 | }
82 | $this->delaySeconds = $delaySeconds;
83 | }
84 |
85 | /**
86 | * @return int
87 | */
88 | public function getVisibilityTimeout()
89 | {
90 | return $this->visibilityTimeout;
91 | }
92 |
93 | /**
94 | * @param int $visibilityTimeout
95 | */
96 | public function setVisibilityTimeout($visibilityTimeout)
97 | {
98 | $this->visibilityTimeout = $visibilityTimeout;
99 | }
100 |
101 | /**
102 | * @return bool
103 | */
104 | public function getDeleted()
105 | {
106 | return $this->deleted;
107 | }
108 |
109 | /**
110 | * @param bool $deleted
111 | */
112 | public function setDeleted($deleted)
113 | {
114 | $this->deleted = $deleted;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Sqs/SqsQueueStoreAdapter.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
32 | $this->queueName = $queueName;
33 | $this->init();
34 | }
35 |
36 | /**
37 | * @return SqsQueueStoreAdapter
38 | */
39 | public function init()
40 | {
41 | $this->getConnection()->connect();
42 | // create new queue or get existing one
43 | $queue = $this->getConnection()->getInstance()->createQueue([
44 | 'QueueName' => $this->queueName,
45 | ]);
46 | $this->queueUrl = $queue['QueueUrl'];
47 | return $this;
48 | }
49 |
50 | /**
51 | * @return SqsQueueStoreConnection
52 | */
53 | public function getConnection()
54 | {
55 | return $this->connection;
56 | }
57 |
58 | /**
59 | * @param MailJobInterface|SqsMailJob $mailJob
60 | *
61 | * @return bool whether it has been successfully queued or not
62 | */
63 | public function enqueue(MailJobInterface $mailJob)
64 | {
65 | $result = $this->getConnection()->getInstance()->sendMessage([
66 | 'QueueUrl' => $this->queueUrl,
67 | 'MessageBody' => $mailJob->getMessage(),
68 | 'DelaySeconds' => $mailJob->getDelaySeconds(),
69 | 'Attempt' => $mailJob->getAttempt(),
70 | ]);
71 | $messageId = $result['MessageId'];
72 | return $messageId !== null && is_string($messageId);
73 | }
74 |
75 | /**
76 | * Returns a MailJob fetched from Amazon SQS.
77 | *
78 | * @return MailJobInterface|SqsMailJob
79 | */
80 | public function dequeue()
81 | {
82 | $result = $this->getConnection()->getInstance()->receiveMessage([
83 | 'QueueUrl' => $this->queueUrl,
84 | ]);
85 | if (empty($result['Messages'])) {
86 | return null;
87 | }
88 |
89 | $result = array_shift($result['Messages']);
90 | return new SqsMailJob([
91 | 'id' => $result['MessageId'],
92 | 'receiptHandle' => $result['ReceiptHandle'],
93 | 'message' => $result['Body'],
94 | 'attempt' => $result['Attempt'],
95 | ]);
96 | }
97 |
98 | /**
99 | * @param MailJobInterface|SqsMailJob $mailJob
100 | *
101 | * @return bool
102 | */
103 | public function ack(MailJobInterface $mailJob)
104 | {
105 | if ($mailJob->isNewRecord()) {
106 | throw new InvalidCallException('SqsMailJob cannot be a new object to be acknowledged');
107 | }
108 |
109 | if ($mailJob->getDeleted()) {
110 | $this->getConnection()->getInstance()->deleteMessage([
111 | 'QueueUrl' => $this->queueUrl,
112 | 'ReceiptHandle' => $mailJob->getReceiptHandle(),
113 | ]);
114 | return true;
115 | } elseif ($mailJob->getVisibilityTimeout() !== null) {
116 | $this->getConnection()->getInstance()->changeMessageVisibility([
117 | 'QueueUrl' => $this->queueUrl,
118 | 'ReceiptHandle' => $mailJob->getReceiptHandle(),
119 | 'VisibilityTimeout' => $mailJob->getVisibilityTimeout(),
120 | ]);
121 | return true;
122 | }
123 |
124 | return false;
125 | }
126 |
127 | /**
128 | * {@inheritdoc}
129 | */
130 | public function isEmpty(): bool
131 | {
132 | $response = $this->getConnection()->getInstance()->getQueueAttributes([
133 | 'QueueUrl' => $this->queueUrl,
134 | 'AttributeNames' => ['ApproximateNumberOfMessages'],
135 | ]);
136 | return $response['Attributes']['ApproximateNumberOfMessages'] === 0;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/Queue/Backend/Sqs/SqsQueueStoreConnection.php:
--------------------------------------------------------------------------------
1 | disconnect();
26 | $key = $this->getConfigurationValue('key');
27 | $secret = $this->getConfigurationValue('secret');
28 | $region = $this->getConfigurationValue('region');
29 | $this->instance = new SqsClient([
30 | 'key' => $key,
31 | 'secret' => $secret,
32 | 'region' => $region,
33 | ]);
34 | return $this;
35 | }
36 |
37 | /**
38 | * Returns the connection instance.
39 | *
40 | * @return SqsClient
41 | */
42 | public function getInstance()
43 | {
44 | if ($this->instance === null) {
45 | $this->connect();
46 | }
47 |
48 | return $this->instance;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Queue/Cli/MailMessageWorker.php:
--------------------------------------------------------------------------------
1 | mailer = $mailer;
33 | $this->mailMessage = $mailMessage;
34 | }
35 |
36 | /**
37 | * Sends the MailMessage. It does triggers the following events:.
38 | *
39 | * - onSuccess: If the sending has been successful
40 | * - onFailure: If the sending has failed
41 | *
42 | * The events need to be configured by attaching a handler to them.
43 | *
44 | * @see EventHandlerTrait
45 | */
46 | public function run()
47 | {
48 | $event = 'onSuccess';
49 | try {
50 | $sentMessage = $this->mailer->send($this->mailMessage);
51 | if (is_null($sentMessage)) {
52 | $event = 'onFailure';
53 | }
54 | } catch (Exception $e) {
55 | $event = 'onFailure';
56 | }
57 | $this->trigger($event, [$this->mailMessage, $sentMessage ?? null]);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Queue/MailQueue.php:
--------------------------------------------------------------------------------
1 | adapter = $adapter;
30 | }
31 |
32 | /**
33 | * @return MailQueue
34 | * @throws \Da\Mailer\Exception\UndefinedMessageBrokerException
35 | */
36 | public static function make()
37 | {
38 | return QueueBuilder::make();
39 | }
40 |
41 | /**
42 | * @return AbstractQueueStoreConnection
43 | */
44 | public function getConnection()
45 | {
46 | return $this->adapter->getConnection();
47 | }
48 |
49 | /**
50 | * @param CypherInterface $cypher
51 | */
52 | public function setCypher(CypherInterface $cypher)
53 | {
54 | $this->cypher = $cypher;
55 | }
56 |
57 | /**
58 | * @return CypherInterface
59 | */
60 | public function getCypher()
61 | {
62 | return $this->cypher;
63 | }
64 |
65 | /**
66 | * {@inheritdoc}
67 | */
68 | public function enqueue(MailJobInterface $mailJob)
69 | {
70 | $message = $mailJob->getMessage();
71 | if (null !== $this->getCypher() && $message instanceof MailMessage) {
72 | $mailJob->setMessage($this->getCypher()->encodeMailMessage($message));
73 | }
74 |
75 | return $this->adapter->enqueue($mailJob);
76 | }
77 |
78 | /**
79 | * {@inheritdoc}
80 | */
81 | public function dequeue()
82 | {
83 | $mailJob = $this->adapter->dequeue();
84 |
85 | if (null !== $this->getCypher()) {
86 | $mailJob->setMessage($this->getCypher()->decodeMailMessage($mailJob->getMessage()));
87 | }
88 |
89 | return $mailJob;
90 | }
91 |
92 | /**
93 | * {@inheritdoc}
94 | */
95 | public function init()
96 | {
97 | return $this->adapter->init();
98 | }
99 |
100 | /**
101 | * {@inheritdoc}
102 | */
103 | public function ack(MailJobInterface $mailJob)
104 | {
105 | return $this->adapter->ack($mailJob);
106 | }
107 |
108 | /**
109 | * @return bool
110 | */
111 | public function isEmpty()
112 | {
113 | return $this->adapter->isEmpty();
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Security/Cypher.php:
--------------------------------------------------------------------------------
1 | key = $key;
26 | $this->iv = $iv;
27 | // initialize cypher with strongest mode and with AES. Is an anti-pattern and should be passed through the
28 | // constructor as an argument, but this way we ensure the library does have the strongest strategy by default.
29 | $this->strategy = new AES('cbc');
30 | $this->strategy->setKeyLength(256);
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function encodeMailMessage(MailMessage $mailMessage)
37 | {
38 | $jsonEncodedMailMessage = json_encode($mailMessage, JSON_NUMERIC_CHECK);
39 | $this->strategy->setKey($this->key);
40 | $this->strategy->setIV($this->iv);
41 | return base64_encode($this->strategy->encrypt($jsonEncodedMailMessage));
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | public function decodeMailMessage($encodedMailMessage)
48 | {
49 | $this->strategy->setKey($this->key);
50 | $decryptedMailMessage = $this->strategy->decrypt(base64_decode($encodedMailMessage, true));
51 | $jsonDecodedMailMessageAttributes = json_decode($decryptedMailMessage, true);
52 | return new MailMessage($jsonDecodedMailMessageAttributes);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Security/CypherInterface.php:
--------------------------------------------------------------------------------
1 | options = $options;
21 | }
22 |
23 | /**
24 | * @return TransportInterface
25 | */
26 | abstract public function create();
27 | }
28 |
--------------------------------------------------------------------------------
/src/Transport/MailTransport.php:
--------------------------------------------------------------------------------
1 | dsn = $dsn;
19 | }
20 |
21 | /**
22 | * @return \Symfony\Component\Mailer\Transport\TransportInterface
23 | */
24 | public function getInstance(): \Symfony\Component\Mailer\Transport\TransportInterface
25 | {
26 | if ($this->instance === null) {
27 | $dsn = Dsn::fromString($this->dsn);
28 | $this->instance = (new NativeTransportFactory())->create($dsn);
29 | }
30 |
31 | return $this->instance;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Transport/MailTransportFactory.php:
--------------------------------------------------------------------------------
1 | options['dsn']);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Transport/SendMailTransport.php:
--------------------------------------------------------------------------------
1 | dsn = $dsn;
20 | }
21 |
22 | /**
23 | * Returns the Swift_SendmailTransport instance.
24 | *
25 | * @return \Symfony\Component\Mailer\Transport\SendmailTransport instance
26 | */
27 | public function getInstance(): \Symfony\Component\Mailer\Transport\SendmailTransport
28 | {
29 | if ($this->instance === null) {
30 | $sendMailFactory = new \Symfony\Component\Mailer\Transport\SendmailTransportFactory();
31 | $this->instance = $sendMailFactory->create(Dsn::fromString($this->dsn));
32 | }
33 |
34 | return $this->instance;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Transport/SendMailTransportFactory.php:
--------------------------------------------------------------------------------
1 | options['dsn']);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Transport/SmtpTransport.php:
--------------------------------------------------------------------------------
1 | host = $host;
37 | $this->port = $port;
38 | $this->options = $options;
39 | }
40 |
41 | /**
42 | * @return EsmtpTransport
43 | */
44 | public function getInstance(): EsmtpTransport
45 | {
46 | if ($this->instance === null) {
47 | $user = $this->options['username'] ?? null;
48 | $password = $this->options['password'] ?? null;
49 | $this->instance = (new EsmtpTransportFactory())->create(new Dsn('smtp', $this->host, $user, $password, $this->port, $this->options));
50 | }
51 |
52 | return $this->instance;
53 | }
54 |
55 | private function getScheme()
56 | {
57 | return $this->options['tls']
58 | ? 'smtps'
59 | : 'smtp';
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Transport/SmtpTransportFactory.php:
--------------------------------------------------------------------------------
1 | options, 'host');
25 | $port = ArrayHelper::remove($this->options, 'port');
26 | $options = ArrayHelper::getValue($this->options, 'options', []);
27 | return new SmtpTransport($host, $port, $options);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Transport/TransportFactory.php:
--------------------------------------------------------------------------------
1 |