Skip to content

Commit

Permalink
fix(postgresql): enforce public properties to fix postgreSQL serializ…
Browse files Browse the repository at this point in the history
…ation
  • Loading branch information
maloun96 committed Jan 27, 2025
1 parent a129aff commit 0b451af
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 0 deletions.
10 changes: 10 additions & 0 deletions config/mailator.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,14 @@
Binarcode\LaravelMailator\Replacers\SampleReplacer::class,
],
],

'serialization' => [
/*
> Controls constructor property accessibility in mailable objects for PostgreSQL compatibility.
> When set to false, allows private/protected properties. When true, enforces
> public properties only to prevent PostgreSQL serialization errors caused by
> null bytes (\x00) in non-public properties.
*/
'enforce_public_properties' => false,
]
];
21 changes: 21 additions & 0 deletions src/Models/MailatorSchedule.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Opis\Closure\SerializableClosure;
use ReflectionClass;
use RuntimeException;
use Throwable;
use TypeError;

Expand Down Expand Up @@ -106,6 +108,10 @@ public static function init(string $name): self

public function mailable(Mailable $mailable): self
{
if (config('mailator.serialization.enforce_public_properties') && $this->hasNonPublicConstructorProps($mailable)) {
throw new RuntimeException('Mailable contains non-public constructor properties which cannot be safely serialized');
}

if ($mailable instanceof Constraintable) {
collect($mailable->constraints())
->filter(fn ($constraint) => $constraint instanceof SendScheduleConstraint)
Expand Down Expand Up @@ -598,4 +604,19 @@ public function save(array $options = [])

return parent::save($options);
}

private function hasNonPublicConstructorProps(Mailable $mailable): bool
{
$reflection = new ReflectionClass($mailable);
$constructor = $reflection->getConstructor();

if (! $constructor) {
return false;
}

return collect($constructor->getParameters())
->filter(fn ($param) => $reflection->getProperty($param->getName())->isPrivate()
|| $reflection->getProperty($param->getName())->isProtected())
->isNotEmpty();
}
}
86 changes: 86 additions & 0 deletions tests/Feature/AllowNonPublicPropertiesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

namespace Binarcode\LaravelMailator\Tests\Feature;

use Binarcode\LaravelMailator\Models\MailatorSchedule;
use Binarcode\LaravelMailator\Tests\Fixtures\PrivatePropertyMailable;
use Binarcode\LaravelMailator\Tests\Fixtures\ProtectedPropertyMailable;
use Binarcode\LaravelMailator\Tests\Fixtures\PublicPropertyMailable;
use Binarcode\LaravelMailator\Tests\TestCase;
use Illuminate\Support\Facades\Mail;
use RuntimeException;

class AllowNonPublicPropertiesTest extends TestCase
{

protected function setUp(): void
{
parent::setUp();

Mail::fake();
}

public function test_can_send_email_with_private_property(): void
{
config()->set('mailator.serialization.enforce_public_properties', false);

MailatorSchedule::init('private')
->mailable(new PrivatePropertyMailable('test'))
->execute();

Mail::assertSent(PrivatePropertyMailable::class);
}

public function test_can_not_send_email_with_private_property(): void
{
config()->set('mailator.serialization.enforce_public_properties', true);

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Mailable contains non-public constructor properties which cannot be safely serialized');

MailatorSchedule::init('private')
->mailable(new PrivatePropertyMailable('test'))
->execute();
}

public function test_can_send_email_with_protected_property(): void
{
config()->set('mailator.serialization.enforce_public_properties', false);

MailatorSchedule::init('protected')
->mailable(new ProtectedPropertyMailable('test'))
->execute();

Mail::assertSent(ProtectedPropertyMailable::class);
}

public function test_can_not_send_email_with_protected_property(): void
{
config()->set('mailator.serialization.enforce_public_properties', true);

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Mailable contains non-public constructor properties which cannot be safely serialized');

MailatorSchedule::init('protected')
->mailable(new ProtectedPropertyMailable('test'))
->execute();
}

public function test_can_send_email_with_public_property(): void
{
config()->set('mailator.serialization.enforce_public_properties', true);

MailatorSchedule::init('protected')
->mailable(new PublicPropertyMailable('test'))
->execute();

Mail::assertSent(PublicPropertyMailable::class);

config()->set('mailator.serialization.enforce_public_properties', false);

MailatorSchedule::init('protected')
->mailable(new PublicPropertyMailable('test'))
->execute();
}
}

24 changes: 24 additions & 0 deletions tests/Fixtures/PrivatePropertyMailable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php


namespace Binarcode\LaravelMailator\Tests\Fixtures;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class PrivatePropertyMailable extends Mailable
{
use Queueable;
use SerializesModels;

public function __construct(
private string $name
) {
}

public function build()
{
return $this->view('laravel-mailator::mails.stub_invoice_reminder_view');
}
}
24 changes: 24 additions & 0 deletions tests/Fixtures/ProtectedPropertyMailable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php


namespace Binarcode\LaravelMailator\Tests\Fixtures;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class ProtectedPropertyMailable extends Mailable
{
use Queueable;
use SerializesModels;

public function __construct(
protected string $name
) {
}

public function build()
{
return $this->view('laravel-mailator::mails.stub_invoice_reminder_view');
}
}
24 changes: 24 additions & 0 deletions tests/Fixtures/PublicPropertyMailable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php


namespace Binarcode\LaravelMailator\Tests\Fixtures;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class PublicPropertyMailable extends Mailable
{
use Queueable;
use SerializesModels;

public function __construct(
public string $name
) {
}

public function build()
{
return $this->view('laravel-mailator::mails.stub_invoice_reminder_view');
}
}

0 comments on commit 0b451af

Please sign in to comment.