diff --git a/src/AppBuilder.php b/src/AppBuilder.php
index e7b6817a..c67310ed 100644
--- a/src/AppBuilder.php
+++ b/src/AppBuilder.php
@@ -13,7 +13,7 @@ public static function build(string|null $path = null, string|null $env = null):
{
$app = new App($path ?? dirname(__DIR__));
- Environment::load($env);
+ Environment::load('.env', $env);
putenv('PHENIX_BASE_PATH=' . base_path());
$_ENV['PHENIX_BASE_PATH'] = base_path();
diff --git a/src/Console/Commands/ViewCache.php b/src/Console/Commands/ViewCache.php
index 1b258de2..e912784e 100644
--- a/src/Console/Commands/ViewCache.php
+++ b/src/Console/Commands/ViewCache.php
@@ -45,7 +45,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->compile(Config::get('view.path'));
- WorkerPool::batch($this->tasks);
+ WorkerPool::awaitAll($this->tasks);
$output->writeln('All views were compiled successfully!.');
diff --git a/src/Crypto/Crypto.php b/src/Crypto/Crypto.php
index e198e916..2a550c80 100644
--- a/src/Crypto/Crypto.php
+++ b/src/Crypto/Crypto.php
@@ -11,7 +11,6 @@
use Phenix\Crypto\Tasks\Decrypt;
use Phenix\Crypto\Tasks\Encrypt;
use Phenix\Tasks\Result;
-use Phenix\Tasks\Worker;
use SensitiveParameter;
class Crypto implements CipherContract, StringCipher
@@ -26,14 +25,14 @@ public function __construct(
public function encrypt(#[SensitiveParameter] object|array|string $value, bool $serialize = true): string
{
+ $task = new Encrypt(
+ key: $this->key,
+ value: $value,
+ serialize: $serialize
+ );
+
/** @var Result $result */
- [$result] = Worker::batch([
- new Encrypt(
- key: $this->key,
- value: $value,
- serialize: $serialize
- ),
- ]);
+ $result = $task->output();
if ($result->isFailure()) {
throw new EncryptException($result->message());
@@ -49,14 +48,14 @@ public function encryptString(#[SensitiveParameter] string $value): string
public function decrypt(string $payload, bool $unserialize = true): object|array|string
{
+ $task = new Decrypt(
+ key: $this->key,
+ value: $payload,
+ unserialize: $unserialize
+ );
+
/** @var Result $result */
- [$result] = Worker::batch([
- new Decrypt(
- key: $this->key,
- value: $payload,
- unserialize: $unserialize
- ),
- ]);
+ $result = $task->output();
if ($result->isFailure()) {
throw new DecryptException($result->message());
diff --git a/src/Crypto/Hash.php b/src/Crypto/Hash.php
index d7c17aaf..d8c03cdf 100644
--- a/src/Crypto/Hash.php
+++ b/src/Crypto/Hash.php
@@ -9,37 +9,36 @@
use Phenix\Crypto\Tasks\GeneratePasswordHash;
use Phenix\Crypto\Tasks\VerifyPasswordHash;
use Phenix\Tasks\Result;
-use Phenix\Tasks\Worker;
use SensitiveParameter;
class Hash implements HasherContract
{
public function make(#[SensitiveParameter] string $password): string
{
+ $task = new GeneratePasswordHash($password);
+
/** @var Result $result */
- [$result] = Worker::batch([
- new GeneratePasswordHash($password),
- ]);
+ $result = $task->output();
return $result->output();
}
public function verify(string $hash, #[SensitiveParameter] string $password): bool
{
+ $task = new VerifyPasswordHash($hash, $password);
+
/** @var Result $result */
- [$result] = Worker::batch([
- new VerifyPasswordHash($hash, $password),
- ]);
+ $result = $task->output();
return $result->output();
}
public function needsRehash(string $hash): bool
{
+ $task = new CheckNeedsRehash($hash);
+
/** @var Result $result */
- [$result] = Worker::batch([
- new CheckNeedsRehash($hash),
- ]);
+ $result = $task->output();
return $result->output();
}
diff --git a/src/Facades/Mail.php b/src/Facades/Mail.php
index 7387ee98..d486ee61 100644
--- a/src/Facades/Mail.php
+++ b/src/Facades/Mail.php
@@ -4,6 +4,7 @@
namespace Phenix\Facades;
+use Amp\Future;
use Phenix\Mail\Constants\MailerType;
use Phenix\Mail\Contracts\Mailable as MailableContract;
use Phenix\Mail\Contracts\Mailer;
@@ -15,7 +16,7 @@
* @method static Mailer mailer(MailerType|null $mailerType = null)
* @method static Mailer using(MailerType $mailerType)
* @method static Mailer to(array|string $to)
- * @method static void send(MailableContract $mailable)
+ * @method static Future send(MailableContract $mailable)
* @method static Mailer fake(MailerType|null $mailerType = null)
* @method static array getSendingLog(MailerType|null $mailerType = null)
* @method static void resetSendingLog(MailerType|null $mailerType = null)
diff --git a/src/Facades/Worker.php b/src/Facades/Worker.php
new file mode 100644
index 00000000..84b61d04
--- /dev/null
+++ b/src/Facades/Worker.php
@@ -0,0 +1,35 @@
+mailer()->to($to);
}
- public function send(Mailable $mailable): void
+ public function send(Mailable $mailable): Future
{
- $this->mailer()->send($mailable);
+ return $this->mailer()->send($mailable);
}
public function fake(MailerType|null $mailerType = null): void
diff --git a/src/Mail/Mailer.php b/src/Mail/Mailer.php
index 0a0ffcf2..0bd1bfd4 100644
--- a/src/Mail/Mailer.php
+++ b/src/Mail/Mailer.php
@@ -4,11 +4,11 @@
namespace Phenix\Mail;
+use Amp\Future;
use Phenix\Mail\Contracts\Mailable;
use Phenix\Mail\Contracts\Mailer as MailerContract;
use Phenix\Mail\Tasks\SendEmail;
-use Phenix\Tasks\Result;
-use Phenix\Tasks\Worker;
+use Phenix\Tasks\WorkerPool;
use SensitiveParameter;
use Symfony\Component\Mime\Address;
@@ -57,7 +57,7 @@ public function bcc(array|string $bcc): self
return $this;
}
- public function send(Mailable $mailable): void
+ public function send(Mailable $mailable): Future
{
$mailable->from($this->from)
->to($this->to)
@@ -67,22 +67,22 @@ public function send(Mailable $mailable): void
$email = $mailable->toMail();
- /** @var Result $result */
- [$result] = Worker::batch([
+ $future = WorkerPool::submit(
new SendEmail(
$email,
$this->config,
$this->serviceConfig,
- ),
- ]);
+ )
+ );
if ($this->config['transport'] === 'log') {
$this->sendingLog[] = [
'mailable' => $mailable::class,
'email' => $email,
- 'success' => $result->isSuccess(),
];
}
+
+ return $future;
}
public function getSendingLog(): array
diff --git a/src/Mail/TransportFactory.php b/src/Mail/TransportFactory.php
index 81eefb42..2fedfa6d 100644
--- a/src/Mail/TransportFactory.php
+++ b/src/Mail/TransportFactory.php
@@ -7,6 +7,7 @@
use InvalidArgumentException;
use Phenix\Mail\Constants\MailerType;
use Phenix\Mail\Transports\LogTransport;
+use Phenix\Util\Arr;
use SensitiveParameter;
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesSmtpTransport;
use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendApiTransport;
@@ -34,7 +35,7 @@ private static function createSmtpTransport(#[SensitiveParameter] array $config)
$scheme = 'smtp';
if (! empty($config['encryption']) && $config['encryption'] === 'tls') {
- $scheme = ($config['port'] === 465) ? 'smtps' : 'smtp';
+ $scheme = (Arr::get($config, 'port') === 465) ? 'smtps' : 'smtp';
}
$dsn = new Dsn(
@@ -42,7 +43,7 @@ private static function createSmtpTransport(#[SensitiveParameter] array $config)
$config['host'],
$config['username'] ?? null,
$config['password'] ?? null,
- $config['port'] ?? null,
+ Arr::has($config, 'port') ? (int) Arr::get($config, 'port') : null,
$config
);
diff --git a/src/Runtime/Environment.php b/src/Runtime/Environment.php
index 798a207b..c51b8978 100644
--- a/src/Runtime/Environment.php
+++ b/src/Runtime/Environment.php
@@ -5,6 +5,7 @@
namespace Phenix\Runtime;
use Dotenv\Dotenv;
+use Phenix\Util\Str;
class Environment
{
@@ -12,7 +13,7 @@ public static function load(string|null $fileName = null, string|null $environme
{
$fileName ??= '.env';
$fileName .= $environment ? ".{$environment}" : '';
- $fileNamePath = base_path() . DIRECTORY_SEPARATOR . $fileName;
+ $fileNamePath = Str::finish(base_path(), DIRECTORY_SEPARATOR) . $fileName;
if (file_exists($fileNamePath)) {
Dotenv::createImmutable(base_path(), $fileName)->load();
diff --git a/src/Tasks/AbstractWorker.php b/src/Tasks/AbstractWorker.php
index 2886026b..3aeb0c1f 100644
--- a/src/Tasks/AbstractWorker.php
+++ b/src/Tasks/AbstractWorker.php
@@ -26,12 +26,12 @@ public function __construct()
* @param array $tasks
* @return array
*/
- public static function batch(array $tasks): array
+ public static function awaitAll(array $tasks): array
{
$pool = new static();
foreach ($tasks as $task) {
- $pool->submit($task);
+ $pool->push($task);
}
$results = $pool->run();
@@ -41,9 +41,9 @@ public static function batch(array $tasks): array
return $results;
}
- public function submit(Task $parallelTask): self
+ public function push(Task $parallelTask): self
{
- $this->tasks[] = $this->submitTask($parallelTask);
+ $this->tasks[] = $this->prepareTask($parallelTask);
return $this;
}
@@ -56,7 +56,7 @@ public function run(): array
));
}
- abstract protected function submitTask(Task $parallelTask): Worker\Execution;
+ abstract protected function prepareTask(Task $parallelTask): Worker\Execution;
protected function finalize(): void
{
diff --git a/src/Tasks/Contracts/Worker.php b/src/Tasks/Contracts/Worker.php
index ed332ab2..30818ee6 100644
--- a/src/Tasks/Contracts/Worker.php
+++ b/src/Tasks/Contracts/Worker.php
@@ -6,7 +6,7 @@
interface Worker
{
- public function submit(Task $parallelTask): self;
+ public function push(Task $parallelTask): self;
public function run(): array;
@@ -14,5 +14,5 @@ public function run(): array;
* @param Task[] $tasks
* @return array
*/
- public static function batch(array $tasks): array;
+ public static function awaitAll(array $tasks): array;
}
diff --git a/src/Tasks/Task.php b/src/Tasks/Task.php
index 2c0d1094..727607bf 100644
--- a/src/Tasks/Task.php
+++ b/src/Tasks/Task.php
@@ -53,12 +53,7 @@ public function run(Channel $channel, Cancellation $cancellation): mixed
public function output(): Result
{
- /** @var Result $result */
- [$result] = Worker::batch([
- $this,
- ]);
-
- return $result;
+ return WorkerPool::submit($this)->await();
}
public function setTimeout(int $timeout): void
diff --git a/src/Tasks/Worker.php b/src/Tasks/Worker.php
index 9b2f32c7..1af6b8e9 100644
--- a/src/Tasks/Worker.php
+++ b/src/Tasks/Worker.php
@@ -19,7 +19,7 @@ public function __construct()
$this->worker = Workers\createWorker();
}
- protected function submitTask(Task $parallelTask): Workers\Execution
+ protected function prepareTask(Task $parallelTask): Workers\Execution
{
$timeout = new TimeoutCancellation($parallelTask->getTimeout());
diff --git a/src/Tasks/WorkerPool.php b/src/Tasks/WorkerPool.php
index 3bbf6ff1..7eb6365c 100644
--- a/src/Tasks/WorkerPool.php
+++ b/src/Tasks/WorkerPool.php
@@ -4,21 +4,27 @@
namespace Phenix\Tasks;
-use Amp\Parallel\Worker;
-use Amp\Parallel\Worker\WorkerPool as Pool;
+use Amp\Future;
+use Amp\Parallel\Worker\Execution;
use Amp\TimeoutCancellation;
-use Phenix\App;
+use Phenix\Facades\Worker;
use Phenix\Tasks\Contracts\Task;
class WorkerPool extends AbstractWorker
{
- protected function submitTask(Task $parallelTask): Worker\Execution
+ protected function prepareTask(Task $parallelTask): Execution
{
- /** @var Pool $pool */
- $pool = App::make(Pool::class);
+ $timeout = new TimeoutCancellation($parallelTask->getTimeout());
+
+ return Worker::submit($parallelTask, $timeout);
+ }
+ public static function submit(Task $parallelTask): Future
+ {
$timeout = new TimeoutCancellation($parallelTask->getTimeout());
- return $pool->submit($parallelTask, $timeout);
+ $execution = Worker::submit($parallelTask, $timeout);
+
+ return $execution->getFuture();
}
}
diff --git a/src/Testing/TestCase.php b/src/Testing/TestCase.php
index b8f2a99f..547f67d0 100644
--- a/src/Testing/TestCase.php
+++ b/src/Testing/TestCase.php
@@ -12,6 +12,7 @@
use Phenix\Facades\Event;
use Phenix\Facades\Mail;
use Phenix\Facades\Queue;
+use Phenix\Facades\View;
use Phenix\Testing\Concerns\InteractWithResponses;
use Phenix\Testing\Concerns\RefreshDatabase;
use Symfony\Component\Console\Tester\CommandTester;
@@ -41,6 +42,8 @@ protected function setUp(): void
if (in_array(RefreshDatabase::class, $uses, true) && method_exists($this, 'refreshDatabase')) {
$this->refreshDatabase();
}
+
+ View::clearCache();
}
protected function tearDown(): void
diff --git a/src/Testing/TestMail.php b/src/Testing/TestMail.php
index d8cf98f5..e8f207d0 100644
--- a/src/Testing/TestMail.php
+++ b/src/Testing/TestMail.php
@@ -7,6 +7,7 @@
use Closure;
use Phenix\Data\Collection;
use Phenix\Mail\Contracts\Mailable;
+use Phenix\Util\Arr;
use PHPUnit\Framework\Assert;
class TestMail
@@ -32,7 +33,7 @@ public function toBeSent(Closure|null $closure = null): void
$matches = $this->filterByMailable($this->mailable);
if ($closure) {
- Assert::assertTrue($closure($matches->first()));
+ Assert::assertTrue($closure($matches));
} else {
Assert::assertNotEmpty($matches, "Failed asserting that mailable '{$this->mailable}' was sent at least once.");
}
@@ -43,10 +44,8 @@ public function toNotBeSent(Closure|null $closure = null): void
$matches = $this->filterByMailable($this->mailable);
if ($closure) {
- Assert::assertFalse($closure($matches->first()));
+ Assert::assertTrue($closure($matches));
} else {
- $matches = $matches->filter(fn (array $item): bool => $item['success'] === false);
-
Assert::assertEmpty($matches, "Failed asserting that mailable '{$this->mailable}' was NOT sent.");
}
}
@@ -65,7 +64,7 @@ private function filterByMailable(string $mailable): Collection
$filtered = [];
foreach ($this->log as $record) {
- if (($record['mailable'] ?? null) === $mailable) {
+ if (Arr::get($record, 'mailable') === $mailable) {
$filtered[] = $record;
}
}
diff --git a/tests/Unit/Mail/MailTest.php b/tests/Unit/Mail/MailTest.php
index c1e65b4e..ff820ccd 100644
--- a/tests/Unit/Mail/MailTest.php
+++ b/tests/Unit/Mail/MailTest.php
@@ -2,6 +2,7 @@
declare(strict_types=1);
+use Phenix\Data\Collection;
use Phenix\Facades\Config;
use Phenix\Facades\Mail;
use Phenix\Mail\Constants\MailerType;
@@ -13,6 +14,7 @@
use Phenix\Mail\TransportFactory;
use Phenix\Mail\Transports\LogTransport;
use Phenix\Tasks\Result;
+use Phenix\Util\Arr;
use Symfony\Component\Mailer\Bridge\Amazon\Transport\SesSmtpTransport;
use Symfony\Component\Mailer\Bridge\Resend\Transport\ResendApiTransport;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
@@ -140,19 +142,27 @@ public function build(): self
}
};
- Mail::to($email)->send($mailable);
+ $missingMailable = new class () extends Mailable {
+ public function build(): self
+ {
+ return $this->view('emails.welcome')
+ ->subject('It will not be sent');
+ }
+ };
- Mail::expect($mailable)->toBeSent();
+ $future = Mail::to($email)->send($mailable);
- Mail::expect($mailable)->toBeSent(function (array $matches): bool {
- return $matches['success'] === true;
- });
+ /** @var Result $result */
+ $result = $future->await();
+
+ expect($result->isSuccess())->toBeTrue();
+ Mail::expect($mailable)->toBeSent();
+ Mail::expect($mailable)->toBeSent(fn (Collection $matches): bool => Arr::get($matches->first(), 'mailable') === $mailable::class);
Mail::expect($mailable)->toBeSentTimes(1);
- Mail::expect($mailable)->toNotBeSent();
- Mail::expect($mailable)->toNotBeSent(function (array $matches): bool {
- return $matches['success'] === false;
- });
+
+ Mail::expect($missingMailable)->toNotBeSent();
+ Mail::expect($missingMailable)->toNotBeSent(fn (Collection $matches): bool => $matches->isEmpty());
Mail::resetSendingLog();
@@ -181,18 +191,16 @@ public function build(): self
}
};
- Mail::to($email)->send($mailable);
+ $future = Mail::to($email)->send($mailable);
- Mail::expect($mailable)->toBeSent();
+ /** @var Result $result */
+ $result = $future->await();
- Mail::expect($mailable)->toBeSent(function (array $matches): bool {
- return $matches['success'] === true;
- });
+ expect($result->isSuccess())->toBeTrue();
+ Mail::expect($mailable)->toBeSent();
+ Mail::expect($mailable)->toBeSent(fn (Collection $matches): bool => Arr::get($matches->first(), 'mailable') === $mailable::class);
Mail::expect($mailable)->toBeSentTimes(1);
- Mail::expect($mailable)->toNotBeSent(function (array $matches): bool {
- return $matches['success'] === false;
- });
});
it('send email successfully using smtp mailer with sender defined in mailable', function (): void {
@@ -216,7 +224,12 @@ public function build(): self
}
};
- Mail::send($mailable);
+ $future = Mail::send($mailable);
+
+ /** @var Result $result */
+ $result = $future->await();
+
+ expect($result->isSuccess())->toBeTrue();
Mail::expect($mailable)->toBeSent();
});
@@ -244,10 +257,16 @@ public function build(): self
}
};
- Mail::to($email)->send($mailable);
+ $future = Mail::to($email)->send($mailable);
- Mail::expect($mailable)->toBeSent(function (array $matches): bool {
- $email = $matches['email'] ?? null;
+ /** @var Result $result */
+ $result = $future->await();
+
+ expect($result->isSuccess())->toBeTrue();
+
+ Mail::expect($mailable)->toBeSent(function (Collection $matches): bool {
+ $firstMatch = $matches->first();
+ $email = $firstMatch['email'] ?? null;
if (! $email) {
return false;
@@ -283,12 +302,18 @@ public function build(): self
}
};
- Mail::to($to)
+ $future = Mail::to($to)
->cc($cc)
->send($mailable);
- Mail::expect($mailable)->toBeSent(function (array $matches) use ($cc): bool {
- $email = $matches['email'] ?? null;
+ /** @var Result $result */
+ $result = $future->await();
+
+ expect($result->isSuccess())->toBeTrue();
+
+ Mail::expect($mailable)->toBeSent(function (Collection $matches) use ($cc): bool {
+ $firstMatch = $matches->first();
+ $email = $firstMatch['email'] ?? null;
if (! $email) {
return false;
@@ -325,12 +350,18 @@ public function build(): self
}
};
- Mail::to($to)
+ $future = Mail::to($to)
->bcc($bcc)
->send($mailable);
- Mail::expect($mailable)->toBeSent(function (array $matches) use ($bcc): bool {
- $email = $matches['email'] ?? null;
+ /** @var Result $result */
+ $result = $future->await();
+
+ expect($result->isSuccess())->toBeTrue();
+
+ Mail::expect($mailable)->toBeSent(function (Collection $matches) use ($bcc): bool {
+ $firstMatch = $matches->first();
+ $email = $firstMatch['email'] ?? null;
if (! $email) {
return false;
@@ -367,11 +398,17 @@ public function build(): self
}
};
- Mail::to($to)
+ $future = Mail::to($to)
->send($mailable);
- Mail::expect($mailable)->toBeSent(function (array $matches): bool {
- $email = $matches['email'] ?? null;
+ /** @var Result $result */
+ $result = $future->await();
+
+ expect($result->isSuccess())->toBeTrue();
+
+ Mail::expect($mailable)->toBeSent(function (Collection $matches): bool {
+ $firstMatch = $matches->first();
+ $email = $firstMatch['email'] ?? null;
if (! $email) {
return false;
@@ -413,10 +450,16 @@ public function build(): self
}
};
- Mail::to($to)->send($mailable);
+ $future = Mail::to($to)->send($mailable);
+
+ /** @var Result $result */
+ $result = $future->await();
- Mail::expect($mailable)->toBeSent(function (array $matches): bool {
- $email = $matches['email'] ?? null;
+ expect($result->isSuccess())->toBeTrue();
+
+ Mail::expect($mailable)->toBeSent(function (Collection $matches): bool {
+ $firstMatch = $matches->first();
+ $email = $firstMatch['email'] ?? null;
if (! $email) {
return false;
}
@@ -459,7 +502,12 @@ public function build(): self
}
};
- Mail::to($to)->send($mailable);
+ $future = Mail::to($to)->send($mailable);
+
+ /** @var Result $result */
+ $result = $future->await();
+
+ expect($result->isSuccess())->toBeFalse();
Mail::expect($mailable)->toNotBeSent();
})->throws(InvalidArgumentException::class);
@@ -535,8 +583,9 @@ public function build(): self
Mail::to($to)->send($mailable);
- Mail::expect($mailable)->toBeSent(function (array $matches): bool {
- $email = $matches['email'] ?? null;
+ Mail::expect($mailable)->toBeSent(function (Collection $matches): bool {
+ $firstMatch = $matches->first();
+ $email = $firstMatch['email'] ?? null;
if (! $email) {
return false;
diff --git a/tests/Unit/Queue/ParallelQueueTest.php b/tests/Unit/Queue/ParallelQueueTest.php
index 5f492347..b4f75bd1 100644
--- a/tests/Unit/Queue/ParallelQueueTest.php
+++ b/tests/Unit/Queue/ParallelQueueTest.php
@@ -251,9 +251,6 @@
// Processor should still be running
expect($parallelQueue->isProcessing())->ToBeTrue();
-
- $parallelQueue->clear();
- $parallelQueue->stop();
});
it('automatically disables processing when no tasks are available to reserve', function (): void {
@@ -357,8 +354,6 @@
// All tasks should eventually be processed or re-enqueued appropriately
$this->assertGreaterThanOrEqual(0, $parallelQueue->size());
-
- $parallelQueue->clear();
});
it('handles concurrent task reservation attempts correctly', function (): void {
diff --git a/tests/Unit/Tasks/WorkerTest.php b/tests/Unit/Tasks/WorkerTest.php
new file mode 100644
index 00000000..0c4f91f2
--- /dev/null
+++ b/tests/Unit/Tasks/WorkerTest.php
@@ -0,0 +1,17 @@
+push($task)->run();
+
+ expect($result->isSuccess())->toBeTrue();
+ expect($result->output())->toBe('Task completed successfully');
+});