Skip to content

Commit 9e4f24b

Browse files
author
jakob.bennemann
committed
wip
1 parent d796b02 commit 9e4f24b

File tree

1 file changed

+182
-8
lines changed

1 file changed

+182
-8
lines changed

src/Services/ResponseAnalyzer.php

Lines changed: 182 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2810,16 +2810,17 @@ private function performComprehensiveResourceAnalysis(string $controller, string
28102810
$properties = $this->generateResourceDefaults($resourceClass);
28112811
}
28122812

2813+
// Apply Laravel Resource wrapping for collections
2814+
$wrappedSchema = $this->applyResourceCollectionWrapper($fullResourceClass, $properties);
2815+
28132816
return [
2814-
'type' => 'array',
2815-
'items' => [
2816-
'type' => 'object',
2817-
'properties' => $properties,
2818-
],
2819-
'example' => [$this->generateExampleFromProperties($properties)],
2817+
'type' => 'object',
2818+
'properties' => $wrappedSchema['properties'],
2819+
'example' => $wrappedSchema['example'],
28202820
'enhanced_analysis' => true,
28212821
'detected_resource' => $fullResourceClass,
28222822
'detection_method' => 'comprehensive_pattern_analysis',
2823+
'wrapper_applied' => $wrappedSchema['wrapper_name'],
28232824
];
28242825
} else {
28252826
// Return array schema with defaults even if class isn't found
@@ -2851,13 +2852,17 @@ private function performComprehensiveResourceAnalysis(string $controller, string
28512852
$properties = $this->generateResourceDefaults($resourceClass);
28522853
}
28532854

2855+
// Apply Laravel Resource wrapping
2856+
$wrappedSchema = $this->applyResourceWrapper($fullResourceClass, $properties);
2857+
28542858
return [
28552859
'type' => 'object',
2856-
'properties' => $properties,
2857-
'example' => $this->generateExampleFromProperties($properties),
2860+
'properties' => $wrappedSchema['properties'],
2861+
'example' => $wrappedSchema['example'],
28582862
'enhanced_analysis' => true,
28592863
'detected_resource' => $fullResourceClass,
28602864
'detection_method' => 'resource_make_pattern_analysis',
2865+
'wrapper_applied' => $wrappedSchema['wrapper_name'],
28612866
];
28622867
} else {
28632868
// Return object schema with defaults even if class isn't found
@@ -3506,4 +3511,173 @@ private function analyzeJsonResponsePattern(string $data, string $status, string
35063511
'detection_method' => 'json_response_object',
35073512
];
35083513
}
3514+
3515+
/**
3516+
* Apply Laravel Resource wrapper to schema based on Resource class configuration
3517+
*/
3518+
private function applyResourceWrapper(string $resourceClass, array $properties): array
3519+
{
3520+
try {
3521+
$reflection = new ReflectionClass($resourceClass);
3522+
3523+
// Get the wrapper name from the Resource class
3524+
$wrapperName = $this->getResourceWrapperName($reflection);
3525+
3526+
// Generate example from original properties
3527+
$originalExample = $this->generateExampleFromProperties($properties);
3528+
3529+
// Apply wrapper to both schema and example
3530+
if ($wrapperName) {
3531+
return [
3532+
'properties' => [
3533+
$wrapperName => [
3534+
'type' => 'object',
3535+
'properties' => $properties,
3536+
'description' => 'Resource data wrapped in ' . $wrapperName,
3537+
],
3538+
],
3539+
'example' => [
3540+
$wrapperName => $originalExample,
3541+
],
3542+
'wrapper_name' => $wrapperName,
3543+
];
3544+
}
3545+
3546+
// No wrapper - return as is
3547+
return [
3548+
'properties' => $properties,
3549+
'example' => $originalExample,
3550+
'wrapper_name' => null,
3551+
];
3552+
3553+
} catch (Throwable) {
3554+
// Fallback: assume default Laravel "data" wrapper
3555+
$originalExample = $this->generateExampleFromProperties($properties);
3556+
3557+
return [
3558+
'properties' => [
3559+
'data' => [
3560+
'type' => 'object',
3561+
'properties' => $properties,
3562+
'description' => 'Resource data wrapped in default Laravel data wrapper',
3563+
],
3564+
],
3565+
'example' => [
3566+
'data' => $originalExample,
3567+
],
3568+
'wrapper_name' => 'data',
3569+
];
3570+
}
3571+
}
3572+
3573+
/**
3574+
* Apply Laravel Resource wrapper to collection schema
3575+
*/
3576+
private function applyResourceCollectionWrapper(string $resourceClass, array $itemProperties): array
3577+
{
3578+
try {
3579+
$reflection = new ReflectionClass($resourceClass);
3580+
3581+
// Get the wrapper name from the Resource class
3582+
$wrapperName = $this->getResourceWrapperName($reflection);
3583+
3584+
// Generate example item from properties
3585+
$itemExample = $this->generateExampleFromProperties($itemProperties);
3586+
3587+
// Apply wrapper to collection
3588+
if ($wrapperName) {
3589+
return [
3590+
'properties' => [
3591+
$wrapperName => [
3592+
'type' => 'array',
3593+
'items' => [
3594+
'type' => 'object',
3595+
'properties' => $itemProperties,
3596+
],
3597+
'description' => 'Collection of resources wrapped in ' . $wrapperName,
3598+
],
3599+
],
3600+
'example' => [
3601+
$wrapperName => [$itemExample],
3602+
],
3603+
'wrapper_name' => $wrapperName,
3604+
];
3605+
}
3606+
3607+
// No wrapper - return unwrapped collection
3608+
return [
3609+
'properties' => $itemProperties,
3610+
'example' => [$itemExample],
3611+
'wrapper_name' => null,
3612+
];
3613+
3614+
} catch (Throwable) {
3615+
// Fallback: assume default Laravel "data" wrapper for collections
3616+
$itemExample = $this->generateExampleFromProperties($itemProperties);
3617+
3618+
return [
3619+
'properties' => [
3620+
'data' => [
3621+
'type' => 'array',
3622+
'items' => [
3623+
'type' => 'object',
3624+
'properties' => $itemProperties,
3625+
],
3626+
'description' => 'Collection of resources wrapped in default Laravel data wrapper',
3627+
],
3628+
],
3629+
'example' => [
3630+
'data' => [$itemExample],
3631+
],
3632+
'wrapper_name' => 'data',
3633+
];
3634+
}
3635+
}
3636+
3637+
/**
3638+
* Get the wrapper name for a Laravel Resource class
3639+
*/
3640+
private function getResourceWrapperName(ReflectionClass $resourceReflection): ?string
3641+
{
3642+
// Check if the resource has a custom $wrap property
3643+
if ($resourceReflection->hasProperty('wrap')) {
3644+
$wrapProperty = $resourceReflection->getProperty('wrap');
3645+
3646+
// Check if the property is accessible (public or has getter)
3647+
if ($wrapProperty->isPublic() || $wrapProperty->isProtected()) {
3648+
$wrapProperty->setAccessible(true);
3649+
3650+
// Get default value from property
3651+
$defaultProps = $resourceReflection->getDefaultProperties();
3652+
if (isset($defaultProps['wrap'])) {
3653+
return $defaultProps['wrap'];
3654+
}
3655+
}
3656+
}
3657+
3658+
// Check parent classes for global wrapping configuration
3659+
$currentClass = $resourceReflection;
3660+
while ($currentClass = $currentClass->getParentClass()) {
3661+
// If we reach JsonResource class, check if wrapping is disabled globally
3662+
if ($currentClass->getName() === 'Illuminate\Http\Resources\Json\JsonResource') {
3663+
// TODO: Check if JsonResource::withoutWrapping() has been called globally
3664+
// For now, assume default Laravel behavior: use "data" wrapper
3665+
return 'data';
3666+
}
3667+
3668+
// Check parent class for $wrap property
3669+
if ($currentClass->hasProperty('wrap')) {
3670+
$wrapProperty = $currentClass->getProperty('wrap');
3671+
$wrapProperty->setAccessible(true);
3672+
3673+
$defaultProps = $currentClass->getDefaultProperties();
3674+
if (isset($defaultProps['wrap'])) {
3675+
return $defaultProps['wrap'];
3676+
}
3677+
}
3678+
}
3679+
3680+
// Default Laravel behavior: use "data" wrapper
3681+
return 'data';
3682+
}
35093683
}

0 commit comments

Comments
 (0)