Skip to content

Commit f6d12a5

Browse files
authored
fix(postgresql): enforce public properties for PostgreSQL serialization (#2589)
* fix(postgresql): enforce public properties to fix postgreSQL serialization * Fix styling --------- Co-authored-by: maloun96 <[email protected]>
1 parent 7477a34 commit f6d12a5

6 files changed

+187
-0
lines changed

config/mailator.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,14 @@
6363
Binarcode\LaravelMailator\Replacers\SampleReplacer::class,
6464
],
6565
],
66+
67+
'serialization' => [
68+
/*
69+
> Controls constructor property accessibility in mailable objects for PostgreSQL compatibility.
70+
> When set to false, allows private/protected properties. When true, enforces
71+
> public properties only to prevent PostgreSQL serialization errors caused by
72+
> null bytes (\x00) in non-public properties.
73+
*/
74+
'enforce_public_properties' => false,
75+
]
6676
];

src/Models/MailatorSchedule.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
use Illuminate\Support\Facades\Validator;
3131
use Illuminate\Support\Str;
3232
use Opis\Closure\SerializableClosure;
33+
use ReflectionClass;
34+
use RuntimeException;
3335
use Throwable;
3436
use TypeError;
3537

@@ -113,6 +115,10 @@ public static function init(string $name): self
113115

114116
public function mailable(Mailable $mailable): self
115117
{
118+
if (config('mailator.serialization.enforce_public_properties') && $this->hasNonPublicConstructorProps($mailable)) {
119+
throw new RuntimeException('Mailable contains non-public constructor properties which cannot be safely serialized');
120+
}
121+
116122
if ($mailable instanceof Constraintable) {
117123
collect($mailable->constraints())
118124
->filter(fn ($constraint) => $constraint instanceof SendScheduleConstraint)
@@ -617,4 +623,19 @@ public function save(array $options = [])
617623

618624
return parent::save($options);
619625
}
626+
627+
private function hasNonPublicConstructorProps(Mailable $mailable): bool
628+
{
629+
$reflection = new ReflectionClass($mailable);
630+
$constructor = $reflection->getConstructor();
631+
632+
if (! $constructor) {
633+
return false;
634+
}
635+
636+
return collect($constructor->getParameters())
637+
->filter(fn ($param) => $reflection->getProperty($param->getName())->isPrivate()
638+
|| $reflection->getProperty($param->getName())->isProtected())
639+
->isNotEmpty();
640+
}
620641
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
namespace Binarcode\LaravelMailator\Tests\Feature;
4+
5+
use Binarcode\LaravelMailator\Models\MailatorSchedule;
6+
use Binarcode\LaravelMailator\Tests\Fixtures\PrivatePropertyMailable;
7+
use Binarcode\LaravelMailator\Tests\Fixtures\ProtectedPropertyMailable;
8+
use Binarcode\LaravelMailator\Tests\Fixtures\PublicPropertyMailable;
9+
use Binarcode\LaravelMailator\Tests\TestCase;
10+
use Illuminate\Support\Facades\Mail;
11+
use RuntimeException;
12+
13+
class AllowNonPublicPropertiesTest extends TestCase
14+
{
15+
protected function setUp(): void
16+
{
17+
parent::setUp();
18+
19+
Mail::fake();
20+
}
21+
22+
public function test_can_send_email_with_private_property(): void
23+
{
24+
config()->set('mailator.serialization.enforce_public_properties', false);
25+
26+
MailatorSchedule::init('private')
27+
->mailable(new PrivatePropertyMailable('test'))
28+
->execute();
29+
30+
Mail::assertSent(PrivatePropertyMailable::class);
31+
}
32+
33+
public function test_can_not_send_email_with_private_property(): void
34+
{
35+
config()->set('mailator.serialization.enforce_public_properties', true);
36+
37+
$this->expectException(RuntimeException::class);
38+
$this->expectExceptionMessage('Mailable contains non-public constructor properties which cannot be safely serialized');
39+
40+
MailatorSchedule::init('private')
41+
->mailable(new PrivatePropertyMailable('test'))
42+
->execute();
43+
}
44+
45+
public function test_can_send_email_with_protected_property(): void
46+
{
47+
config()->set('mailator.serialization.enforce_public_properties', false);
48+
49+
MailatorSchedule::init('protected')
50+
->mailable(new ProtectedPropertyMailable('test'))
51+
->execute();
52+
53+
Mail::assertSent(ProtectedPropertyMailable::class);
54+
}
55+
56+
public function test_can_not_send_email_with_protected_property(): void
57+
{
58+
config()->set('mailator.serialization.enforce_public_properties', true);
59+
60+
$this->expectException(RuntimeException::class);
61+
$this->expectExceptionMessage('Mailable contains non-public constructor properties which cannot be safely serialized');
62+
63+
MailatorSchedule::init('protected')
64+
->mailable(new ProtectedPropertyMailable('test'))
65+
->execute();
66+
}
67+
68+
public function test_can_send_email_with_public_property(): void
69+
{
70+
config()->set('mailator.serialization.enforce_public_properties', true);
71+
72+
MailatorSchedule::init('protected')
73+
->mailable(new PublicPropertyMailable('test'))
74+
->execute();
75+
76+
Mail::assertSent(PublicPropertyMailable::class);
77+
78+
config()->set('mailator.serialization.enforce_public_properties', false);
79+
80+
MailatorSchedule::init('protected')
81+
->mailable(new PublicPropertyMailable('test'))
82+
->execute();
83+
}
84+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
4+
namespace Binarcode\LaravelMailator\Tests\Fixtures;
5+
6+
use Illuminate\Bus\Queueable;
7+
use Illuminate\Mail\Mailable;
8+
use Illuminate\Queue\SerializesModels;
9+
10+
class PrivatePropertyMailable extends Mailable
11+
{
12+
use Queueable;
13+
use SerializesModels;
14+
15+
public function __construct(
16+
private string $name
17+
) {
18+
}
19+
20+
public function build()
21+
{
22+
return $this->view('laravel-mailator::mails.stub_invoice_reminder_view');
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
4+
namespace Binarcode\LaravelMailator\Tests\Fixtures;
5+
6+
use Illuminate\Bus\Queueable;
7+
use Illuminate\Mail\Mailable;
8+
use Illuminate\Queue\SerializesModels;
9+
10+
class ProtectedPropertyMailable extends Mailable
11+
{
12+
use Queueable;
13+
use SerializesModels;
14+
15+
public function __construct(
16+
protected string $name
17+
) {
18+
}
19+
20+
public function build()
21+
{
22+
return $this->view('laravel-mailator::mails.stub_invoice_reminder_view');
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
4+
namespace Binarcode\LaravelMailator\Tests\Fixtures;
5+
6+
use Illuminate\Bus\Queueable;
7+
use Illuminate\Mail\Mailable;
8+
use Illuminate\Queue\SerializesModels;
9+
10+
class PublicPropertyMailable extends Mailable
11+
{
12+
use Queueable;
13+
use SerializesModels;
14+
15+
public function __construct(
16+
public string $name
17+
) {
18+
}
19+
20+
public function build()
21+
{
22+
return $this->view('laravel-mailator::mails.stub_invoice_reminder_view');
23+
}
24+
}

0 commit comments

Comments
 (0)