Skip to content

Commit 1239661

Browse files
committed
fix(postgresql): enforce public properties to fix postgreSQL serialization
1 parent a129aff commit 1239661

File tree

6 files changed

+189
-0
lines changed

6 files changed

+189
-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 contrcutor 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
@@ -28,6 +28,8 @@
2828
use Illuminate\Support\Facades\Validator;
2929
use Illuminate\Support\Str;
3030
use Opis\Closure\SerializableClosure;
31+
use ReflectionClass;
32+
use RuntimeException;
3133
use Throwable;
3234
use TypeError;
3335

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

107109
public function mailable(Mailable $mailable): self
108110
{
111+
if (config('mailator.serialization.enforce_public_properties') && $this->hasNonPublicConstructorProps($mailable)) {
112+
throw new RuntimeException('Mailable contains non-public constructor properties which cannot be safely serialized');
113+
}
114+
109115
if ($mailable instanceof Constraintable) {
110116
collect($mailable->constraints())
111117
->filter(fn ($constraint) => $constraint instanceof SendScheduleConstraint)
@@ -598,4 +604,19 @@ public function save(array $options = [])
598604

599605
return parent::save($options);
600606
}
607+
608+
private function hasNonPublicConstructorProps(Mailable $mailable): bool
609+
{
610+
$reflection = new ReflectionClass($mailable);
611+
$constructor = $reflection->getConstructor();
612+
613+
if (! $constructor) {
614+
return false;
615+
}
616+
617+
return collect($constructor->getParameters())
618+
->filter(fn ($param) => $reflection->getProperty($param->getName())->isPrivate()
619+
|| $reflection->getProperty($param->getName())->isProtected())
620+
->isNotEmpty();
621+
}
601622
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
16+
protected function setUp(): void
17+
{
18+
parent::setUp();
19+
20+
Mail::fake();
21+
}
22+
23+
public function test_can_send_email_with_private_property(): void
24+
{
25+
config()->set('mailator.serialization.enforce_public_properties', false);
26+
27+
MailatorSchedule::init('private')
28+
->mailable(new PrivatePropertyMailable('test'))
29+
->execute();
30+
31+
Mail::assertSent(PrivatePropertyMailable::class);
32+
}
33+
34+
public function test_can_not_send_email_with_private_property(): void
35+
{
36+
config()->set('mailator.serialization.enforce_public_properties', true);
37+
38+
$this->expectException(RuntimeException::class);
39+
$this->expectExceptionMessage('Mailable contains non-public constructor properties which cannot be safely serialized');
40+
41+
MailatorSchedule::init('private')
42+
->mailable(new PrivatePropertyMailable('test'))
43+
->execute();
44+
}
45+
46+
public function test_can_send_email_with_protected_property(): void
47+
{
48+
config()->set('mailator.serialization.enforce_public_properties', false);
49+
50+
MailatorSchedule::init('protected')
51+
->mailable(new ProtectedPropertyMailable('test'))
52+
->execute();
53+
54+
Mail::assertSent(ProtectedPropertyMailable::class);
55+
}
56+
57+
public function test_can_not_send_email_with_protected_property(): void
58+
{
59+
config()->set('mailator.serialization.enforce_public_properties', true);
60+
61+
$this->expectException(RuntimeException::class);
62+
$this->expectExceptionMessage('Mailable contains non-public constructor properties which cannot be safely serialized');
63+
64+
MailatorSchedule::init('protected')
65+
->mailable(new ProtectedPropertyMailable('test'))
66+
->execute();
67+
}
68+
69+
public function test_can_send_email_with_public_property(): void
70+
{
71+
config()->set('mailator.serialization.enforce_public_properties', true);
72+
73+
MailatorSchedule::init('protected')
74+
->mailable(new PublicPropertyMailable('test'))
75+
->execute();
76+
77+
Mail::assertSent(PublicPropertyMailable::class);
78+
79+
config()->set('mailator.serialization.enforce_public_properties', false);
80+
81+
MailatorSchedule::init('protected')
82+
->mailable(new PublicPropertyMailable('test'))
83+
->execute();
84+
}
85+
}
86+
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)