Skip to content

Commit 726cb69

Browse files
authored
Fix Model::__get() swallowing \Throwable (#309)
2 parents ee34715 + 79132ba commit 726cb69

3 files changed

Lines changed: 85 additions & 82 deletions

File tree

‎src/Phaseolies/Database/Entity/Model.php‎

Lines changed: 73 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,96 +1008,88 @@ public function reload(string $relation)
10081008
*/
10091009
public function __get($name)
10101010
{
1011-
try {
1012-
if (array_key_exists($name, $this->attributes)) {
1013-
return $this->castForGet($name, $this->attributes[$name]);
1014-
}
1011+
if (array_key_exists($name, $this->attributes)) {
1012+
return $this->castForGet($name, $this->attributes[$name]);
1013+
}
10151014

1016-
if (array_key_exists($name, $this->relations)) {
1017-
return $this->relations[$name];
1018-
}
1015+
if (array_key_exists($name, $this->relations)) {
1016+
return $this->relations[$name];
1017+
}
10191018

1020-
if ($this->isComputed($name)) {
1021-
return $this->resolveComputed($name);
1022-
}
1019+
if ($this->isComputed($name)) {
1020+
return $this->resolveComputed($name);
1021+
}
10231022

1024-
if (method_exists($this, $name)) {
1025-
$relation = $this->$name();
1026-
1027-
if ($relation instanceof Builder) {
1028-
$relationType = $this->getLastRelationType();
1029-
1030-
switch ($relationType) {
1031-
case 'linkOne':
1032-
$result = $relation->first();
1033-
$this->setRelation($name, $result);
1034-
return $result;
1035-
1036-
case 'bindTo':
1037-
$result = $relation->first();
1038-
$this->setRelation($name, $result);
1039-
return $result;
1040-
1041-
case 'linkMany':
1042-
$results = $relation->get();
1043-
$this->setRelation($name, $results);
1044-
return $results;
1045-
1046-
case 'bindToMany':
1047-
$relatedModel = app($this->getLastRelatedModel());
1048-
$relatedModelClass = get_class($relatedModel);
1049-
$pivotColumns = app('db')->getTableColumns($this->getLastPivotTable());
1050-
$pivotTable = $this->getLastPivotTable();
1051-
$pivotSelects = array_map(function ($column) use ($pivotTable) {
1052-
return "{$pivotTable}.{$column} as pivot_{$column}";
1053-
}, $pivotColumns);
1054-
1055-
$query = $relatedModel->query()
1056-
->select(array_merge(
1057-
["{$relatedModel->getTable()}.*"],
1058-
$pivotSelects
1059-
))
1060-
->join(
1061-
$this->getLastPivotTable(),
1062-
"{$this->getLastPivotTable()}.{$this->getLastRelatedKey()}",
1063-
'=',
1064-
"{$relatedModel->getTable()}.{$relatedModel->getKeyName()}"
1065-
)
1066-
->where("{$this->getLastPivotTable()}.{$this->getLastForeignKey()}", '=', $this->getKey());
1067-
1068-
$results = $query->get();
1069-
$grouped = [];
1070-
foreach ($results as $result) {
1071-
$pivot = [];
1072-
foreach ($pivotColumns as $column) {
1073-
$pivot[$column] = $result["pivot_{$column}"];
1074-
unset($result["pivot_{$column}"]);
1075-
}
1076-
$pivotObj = (object) $pivot;
1077-
$result->pivot = $pivotObj;
1078-
$grouped[$pivot[$this->getLastForeignKey()]][] = $result;
1023+
if (method_exists($this, $name)) {
1024+
$relation = $this->$name();
1025+
1026+
if ($relation instanceof Builder) {
1027+
$relationType = $this->getLastRelationType();
1028+
1029+
switch ($relationType) {
1030+
case 'linkOne':
1031+
$result = $relation->first();
1032+
$this->setRelation($name, $result);
1033+
return $result;
1034+
1035+
case 'bindTo':
1036+
$result = $relation->first();
1037+
$this->setRelation($name, $result);
1038+
return $result;
1039+
1040+
case 'linkMany':
1041+
$results = $relation->get();
1042+
$this->setRelation($name, $results);
1043+
return $results;
1044+
1045+
case 'bindToMany':
1046+
$relatedModel = app($this->getLastRelatedModel());
1047+
$relatedModelClass = get_class($relatedModel);
1048+
$pivotColumns = app('db')->getTableColumns($this->getLastPivotTable());
1049+
$pivotTable = $this->getLastPivotTable();
1050+
$pivotSelects = array_map(function ($column) use ($pivotTable) {
1051+
return "{$pivotTable}.{$column} as pivot_{$column}";
1052+
}, $pivotColumns);
1053+
1054+
$query = $relatedModel->query()
1055+
->select(array_merge(
1056+
["{$relatedModel->getTable()}.*"],
1057+
$pivotSelects
1058+
))
1059+
->join(
1060+
$this->getLastPivotTable(),
1061+
"{$this->getLastPivotTable()}.{$this->getLastRelatedKey()}",
1062+
'=',
1063+
"{$relatedModel->getTable()}.{$relatedModel->getKeyName()}"
1064+
)
1065+
->where("{$this->getLastPivotTable()}.{$this->getLastForeignKey()}", '=', $this->getKey());
1066+
1067+
$results = $query->get();
1068+
$grouped = [];
1069+
foreach ($results as $result) {
1070+
$pivot = [];
1071+
foreach ($pivotColumns as $column) {
1072+
$pivot[$column] = $result["pivot_{$column}"];
1073+
unset($result["pivot_{$column}"]);
10791074
}
1075+
$pivotObj = (object) $pivot;
1076+
$result->pivot = $pivotObj;
1077+
$grouped[$pivot[$this->getLastForeignKey()]][] = $result;
1078+
}
1079+
1080+
$this->setRelation(
1081+
$name,
1082+
new Collection($relatedModelClass, $grouped[$this->getKey()] ?? [])
1083+
);
10801084

1081-
$this->setRelation(
1082-
$name,
1083-
new Collection($relatedModelClass, $grouped[$this->getKey()] ?? [])
1084-
);
1085-
1086-
return $results;
1087-
}
1085+
return $results;
10881086
}
1089-
1090-
return $relation;
10911087
}
10921088

1093-
if (!isset($this->attributes[$name])) {
1094-
throw new \Exception("Property or relation '$name' does not exist on " . static::class);
1095-
}
1096-
1097-
return $this->attributes[$name];
1098-
} catch (\Throwable) {
1099-
return;
1089+
return $relation;
11001090
}
1091+
1092+
return null;
11011093
}
11021094

11031095
/**

‎tests/API/Presenter/SQLitePresenterTest.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ protected function setUp(): void
2626
$container = new Container();
2727
$container->bind('request', fn() => new Request());
2828
$container->bind('url', fn() => UrlGenerator::class);
29+
$container->bind('db', fn() => new Database('default'));
2930

3031
$this->pdo = new PDO('sqlite::memory:');
3132
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

‎tests/Model/CastSystemTest.php‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -603,11 +603,21 @@ public function testEnumSetCastSerializesUnitEnum(): void
603603

604604
public function testInvalidEnumValueThrows(): void
605605
{
606-
// __get swallows all Throwables, so test the handler directly
607606
$this->expectException(\ValueError::class);
608607

609608
CastManager::resolve(CastTestColor::class)->get('Purple');
610609
}
610+
611+
public function testInvalidEnumValuePropagatesThroughModelGetter(): void
612+
{
613+
// Model::__get() must let real errors (not just "property missing")
614+
// propagate, instead of silently returning null.
615+
$model = new MockEnumCastModel(['color' => 'Purple']);
616+
617+
$this->expectException(\ValueError::class);
618+
619+
$model->color;
620+
}
611621
public function testCustomCastGetUppercases(): void
612622
{
613623
$model = new MockCustomCastModel(['name' => 'doppar']);

0 commit comments

Comments
 (0)