Skip to content

Commit cf33b74

Browse files
committed
Fix empty Phalcon message handling
1 parent 9835593 commit cf33b74

6 files changed

Lines changed: 81 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ history, the old changelog, and committed file changes. Older Zemit-era entries
1515
are summarized where the commit history is too granular to be useful as
1616
release notes.
1717

18+
## 3.1.3 - 2026-06-12
19+
20+
### Fixed
21+
22+
- Fixed REST action failure status resolution so empty
23+
`Phalcon\Messages\Messages` collections are treated as no-message failures
24+
instead of generic validation failures.
25+
1826
## 3.1.2 - 2026-06-11
1927

2028
### Added

src/Modules/Cli/Tasks/DataLifeCycleTask.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public function modelsAction(string ...$tables): array
161161
$response[$source]['deleted'] += $deleted ? 1 : 0;
162162

163163
$messages = $record->getMessages();
164-
if (!empty($messages)) {
164+
if (count($messages) > 0) {
165165
$response[$source]['messages'] = array_merge($response[$source]['messages'], $messages);
166166
}
167167
};

src/Mvc/Controller/Traits/RestResponse.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Phalcon\Http\ResponseInterface;
1717
use Phalcon\Messages\MessageInterface;
1818
use Phalcon\Mvc\Dispatcher;
19+
use Countable;
1920
use PhalconKit\Http\StatusCode as HttpStatusCode;
2021
use PhalconKit\Mvc\Controller\Traits\Abstracts\AbstractDebug;
2122
use PhalconKit\Mvc\Controller\Traits\Abstracts\AbstractInjectable;
@@ -184,7 +185,7 @@ protected function getRestActionFailureStatusCode(
184185
int $emptyStatusCode = 400,
185186
int $defaultStatusCode = 422
186187
): int {
187-
if (empty($messages)) {
188+
if (!$this->hasRestActionMessages($messages)) {
188189
return $emptyStatusCode;
189190
}
190191

@@ -198,6 +199,35 @@ protected function getRestActionFailureStatusCode(
198199
return $defaultStatusCode;
199200
}
200201

202+
/**
203+
* Determine whether a REST action failure carried any message payload.
204+
*
205+
* PHP objects are never empty for `empty()`, even when they implement
206+
* `Countable` and contain zero messages. Phalcon validation returns
207+
* `Phalcon\Messages\Messages`, so status resolution must check the
208+
* collection count instead of relying on PHP object truthiness.
209+
*/
210+
protected function hasRestActionMessages(mixed $messages): bool
211+
{
212+
if ($messages instanceof Countable) {
213+
return count($messages) > 0;
214+
}
215+
216+
if (is_array($messages)) {
217+
return count($messages) > 0;
218+
}
219+
220+
if ($messages instanceof \Traversable) {
221+
foreach ($messages as $_message) {
222+
return true;
223+
}
224+
225+
return false;
226+
}
227+
228+
return (bool) $messages;
229+
}
230+
201231
/**
202232
* Return a normalized REST error response for an action failure.
203233
*

src/Mvc/Model/Behavior/Position.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ public function afterSave(ModelInterface $model, string $field, bool $rawSql): v
185185
$modelPrimaryKeys,
186186
));
187187
$messages = $save->getMessages();
188-
if (!empty($messages)) {
188+
if (count($messages) > 0) {
189189
$model->appendMessages($messages, 'afterSave');
190190
}
191191
}

tests/Unit/Mvc/Controller/Traits/Fixtures/RestResponseControllerDouble.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,15 @@ public function exposeRestViewVars(): array
5454
{
5555
return $this->getRestViewVars();
5656
}
57+
58+
/**
59+
* Expose failure status resolution for message payload regression tests.
60+
*/
61+
public function exposeRestActionFailureStatusCode(
62+
mixed $messages,
63+
int $emptyStatusCode = 400,
64+
int $defaultStatusCode = 422
65+
): int {
66+
return $this->getRestActionFailureStatusCode($messages, $emptyStatusCode, $defaultStatusCode);
67+
}
5768
}

tests/Unit/Mvc/Controller/Traits/RestResponseTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
namespace PhalconKit\Tests\Unit\Mvc\Controller\Traits;
1515

16+
use Phalcon\Messages\Message;
17+
use Phalcon\Messages\Messages;
1618
use PhalconKit\Mvc\Controller\Traits\Interfaces\RestResponseInterface;
1719
use PhalconKit\Tests\Unit\AbstractUnit;
1820
use PhalconKit\Tests\Unit\Mvc\Controller\Traits\Fixtures\RestResponseControllerDouble;
@@ -84,6 +86,33 @@ public function testRestResponseConstantsMatchPublicInterfaceContract(): void
8486
}
8587
}
8688

89+
public function testRestActionFailureStatusTreatsEmptyPhalconMessagesCollectionAsEmpty(): void
90+
{
91+
$controller = $this->newController();
92+
93+
$this->assertSame(400, $controller->exposeRestActionFailureStatusCode(new Messages()));
94+
}
95+
96+
public function testRestActionFailureStatusTreatsPopulatedPhalconMessagesCollectionAsMessages(): void
97+
{
98+
$controller = $this->newController();
99+
$messages = new Messages([
100+
new Message('Invalid value'),
101+
]);
102+
103+
$this->assertSame(422, $controller->exposeRestActionFailureStatusCode($messages));
104+
}
105+
106+
public function testRestActionFailureStatusKeepsExplicitMessageHttpCode(): void
107+
{
108+
$controller = $this->newController();
109+
$messages = new Messages([
110+
new Message('Forbidden', code: 403),
111+
]);
112+
113+
$this->assertSame(403, $controller->exposeRestActionFailureStatusCode($messages));
114+
}
115+
87116
private function newController(): RestResponseControllerDouble
88117
{
89118
$controller = new RestResponseControllerDouble();

0 commit comments

Comments
 (0)