mustafafares / laravel-selective-response
Automatic API resource filtering based on model select() queries with optional Scramble documentation support
Installs: 12
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/mustafafares/laravel-selective-response
Requires
- php: ^8.1
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.0
- phpunit/phpunit: ^10.0|^11.0
Suggests
- dedoc/scramble: ^1.0
- nikic/php-parser: ^5.0
README
Automatic API resource filtering based on model select() queries with optional Scramble documentation support.
Features
- Automatic Filtering: Automatically filters API resource responses based on
select()queries - Zero Breaking Changes: Just change your resource parent class - no code changes needed
- Scramble Integration: Optional extension that updates API documentation to show only selected fields
- Flexible Configuration: Enable/disable globally or per-resource
- Always-Include Fields: Support for fields that should always be included
- Relationship Support: Works seamlessly with
whenLoaded()pattern
Installation
composer require mustafafares/laravel-selective-response
Publish the configuration file:
php artisan vendor:publish --tag=selective-response-config
Quick Start
1. Update Your Resources
Change your resources to extend BaseApiResource instead of JsonResource:
<?php namespace App\Http\Resources; use MustafaFares\SelectiveResponse\Http\Resources\BaseApiResource; class UserResource extends BaseApiResource { public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'phone' => $this->phone, 'address' => $this->address, ]; } }
2. Use in Controllers
<?php namespace App\Http\Controllers\Api; use App\Http\Resources\UserResource; use App\Models\User; class UserController extends Controller { // Full response - returns all fields public function show($id) { $user = User::findOrFail($id); return new UserResource($user); } // Selective response - returns only selected fields public function summary($id) { $user = User::select('id', 'name', 'email')->findOrFail($id); return new UserResource($user); // Returns: {id, name, email} ✨ } }
That's it! The filtering happens automatically.
Usage Examples
Basic Usage
// Resource (no changes needed!) class UserResource extends BaseApiResource { public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'phone' => $this->phone, ]; } } // Controller - Full response $user = User::find($id); return new UserResource($user); // Returns: {id, name, email, phone} // Controller - Selective response $user = User::select('id', 'name')->find($id); return new UserResource($user); // Returns: {id, name} ✨ Automatic!
Always Include Fields
class UserResource extends BaseApiResource { protected $alwaysInclude = ['id']; public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, ]; } } // Even with select('name'), 'id' is always included User::select('name')->find($id); // Returns: {id, name}
Disable Filtering
// Option 1: In resource class class AdminResource extends BaseApiResource { protected $useSelectiveResponse = false; } // Option 2: In controller return (new UserResource($user))->withoutSelectiveFiltering();
With Relationships
class UserResource extends BaseApiResource { public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'posts' => PostResource::collection($this->whenLoaded('posts')), ]; } } // Load relationship User::select('id', 'name')->with('posts')->find($id); // Returns: {id, name, posts: [...]}
Dynamic Always-Include
public function summary($id) { $user = User::select('name')->findOrFail($id); return (new UserResource($user)) ->alwaysInclude(['id', 'created_at']); }
Scramble Extension
The package includes an optional extension for Scramble that automatically detects select() calls in your controllers and updates the API documentation to show only selected fields.
Setup
- Install Scramble (if not already installed):
composer require dedoc/scramble
- Install PHP Parser (required for the extension):
composer require nikic/php-parser
- Publish Scramble config (if not already done):
php artisan vendor:publish --tag=scramble-config
- Register the extension in
config/scramble.php:
<?php return [ // ... other config ... 'extensions' => [ \MustafaFares\SelectiveResponse\Extensions\SelectiveResponseExtension::class, ], ];
How It Works
The extension uses PHP Parser to analyze your controller methods and find select() calls. It then filters the OpenAPI schema to show only the selected fields in the documentation.
Before Extension:
// Controller $user = User::select('id', 'name', 'email')->findOrFail($id); return new UserResource($user); // Scramble docs show: {id, name, email, phone, address, ...} ❌
After Extension:
// Same controller code $user = User::select('id', 'name', 'email')->findOrFail($id); return new UserResource($user); // Scramble docs show: {id, name, email} ✅
Disable Scramble Extension
You can disable the Scramble extension in the config:
// config/selective-response.php 'scramble' => [ 'enabled' => false, ],
Configuration
Publish the configuration file:
php artisan vendor:publish --tag=selective-response-config
Available Options
<?php return [ // Enable/disable selective filtering globally 'enabled' => env('SELECTIVE_RESPONSE_ENABLED', true), // Fields that should always be included globally 'always_include' => [ // 'id', ], // Scramble extension configuration 'scramble' => [ 'enabled' => env('SELECTIVE_RESPONSE_SCRAMBLE_ENABLED', true), 'always_include_in_docs' => [ // 'id', ], ], ];
Common Patterns
List Endpoint (Efficient)
public function index() { $users = User::select('id', 'name', 'email') ->paginate(20); return UserResource::collection($users); }
Detail Endpoint (Full Data)
public function show($id) { $user = User::with('posts', 'role')->findOrFail($id); return new UserResource($user); }
Search with Dynamic Fields
public function search(Request $request) { $fields = explode(',', $request->input('fields', 'id,name,email')); $users = User::select($fields) ->where('name', 'like', "%{$request->q}%") ->get(); return UserResource::collection($users); }
Force Full Response
public function adminView($id) { $user = User::select('id')->findOrFail($id); return (new UserResource($user))->withoutSelectiveFiltering(); }
Advanced Features
Custom Always-Include Logic
class UserResource extends BaseApiResource { protected function shouldIncludeKey(string $key, $value): bool { // Always include timestamps if (in_array($key, ['created_at', 'updated_at'])) { return true; } return parent::shouldIncludeKey($key, $value); } }
Conditional Computed Fields
public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'full_name' => $this->whenAttributeLoaded('name', $this->first_name . ' ' . $this->last_name ), ]; }
Testing
use MustafaFares\SelectiveResponse\Http\Resources\BaseApiResource; use Tests\TestCase; use App\Models\User; class SelectiveResponseTest extends TestCase { public function test_selective_response() { $user = User::factory()->create(); $selected = User::select('id', 'name')->find($user->id); $resource = new UserResource($selected); $data = $resource->toArray(request()); $this->assertArrayHasKey('id', $data); $this->assertArrayHasKey('name', $data); $this->assertArrayNotHasKey('email', $data); } }
Troubleshooting
Issue: All fields still returned
Solution: Verify you're extending BaseApiResource, not JsonResource
Issue: Relationships not working
Solution: Use $this->whenLoaded() for relationships
Issue: Computed fields missing
Solution: Use $this->whenAttributeLoaded() for computed fields
Issue: Need specific field always
Solution: Add to protected $alwaysInclude = ['field']; in your resource
Issue: Scramble extension not working
Solutions:
- Clear config cache:
php artisan config:clear - Verify extension is in
config/scramble.php - Check PHP Parser is installed:
composer show nikic/php-parser - Ensure Scramble is installed:
composer show dedoc/scramble
Performance Benefits
| Endpoint | Without Select | With Select | Improvement |
|---|---|---|---|
| User list | 50 KB | 10 KB | 80% smaller |
| Search | 100 KB | 20 KB | 80% smaller |
| Summary | 2 KB | 0.5 KB | 75% smaller |
Result: Faster APIs, lower bandwidth costs, better UX!
Migration Guide
From JsonResource to BaseApiResource
- Change the parent class:
// Before class UserResource extends JsonResource // After class UserResource extends BaseApiResource
- Add the import:
use MustafaFares\SelectiveResponse\Http\Resources\BaseApiResource;
- That's it! Your
toArray()method works exactly the same.
Requirements
- PHP 8.1+
- Laravel 10.0+, 11.0+, or 12.0+
- (Optional) Scramble 1.0+ for documentation extension
- (Optional) PHP Parser 5.0+ for Scramble extension
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For issues and questions, please open an issue on GitHub.