# 📊 Collections Laravel Arc provides powerful collection support for working with arrays of DTOs. Learn how to handle lists, manage bulk operations, and leverage Laravel's Collection methods. ## Collection Basics Collections in Laravel Arc are arrays of DTOs that behave like Laravel Collections but with additional DTO-specific functionality. ```php // Create collection of DTOs $users = collect([ UserDto::from(['name' => 'John', 'email' => 'john@example.com']), UserDto::from(['name' => 'Jane', 'email' => 'jane@example.com']), UserDto::from(['name' => 'Bob', 'email' => 'bob@example.com']) ]); // Collection methods work seamlessly $activeUsers = $users->filter(fn($user) => $user->status === 'active'); $emails = $users->pluck('email'); $totalAge = $users->sum('age'); ``` ## Creating Collections ### From Arrays ```php // From array of arrays $usersData = [ ['name' => 'John', 'email' => 'john@example.com', 'age' => 30], ['name' => 'Jane', 'email' => 'jane@example.com', 'age' => 25], ['name' => 'Bob', 'email' => 'bob@example.com', 'age' => 35] ]; $users = UserDto::collection($usersData); // or $users = collect($usersData)->map(fn($data) => UserDto::from($data)); ``` ### From Database Results ```php // From Eloquent collection $users = User::all()->map(fn($user) => UserDto::from($user->toArray())); // From query results $users = DB::table('users') ->get() ->map(fn($user) => UserDto::from((array) $user)); ``` ### From API Responses ```php // From JSON API response $response = Http::get('https://api.example.com/users'); $users = collect($response->json('data')) ->map(fn($userData) => UserDto::from($userData)); ``` ## YAML Collection Definitions Define collections directly in YAML: ```yaml header: class: UserCollectionDto namespace: App\DTOs\Collections fields: users: type: array items: type: object class: UserDto min_items: 1 max_items: 100 metadata: type: object properties: total: { type: integer } page: { type: integer } per_page: { type: integer } ``` Generated collection DTO: ```php final readonly class UserCollectionDto { public function __construct( public Collection $users, // Collection public array $metadata, ) {} public function getUsersCount(): int { return $this->users->count(); } public function getActiveUsers(): Collection { return $this->users->filter(fn($user) => $user->status === 'active'); } } ``` ## Collection Operations ### Filtering and Searching ```php $users = UserDto::collection($usersData); // Filter by property $adults = $users->filter(fn($user) => $user->age >= 18); // Filter by multiple conditions $activeAdults = $users->filter(function($user) { return $user->age >= 18 && $user->status === 'active'; }); // Search by email domain $companyUsers = $users->filter(fn($user) => str_ends_with($user->email, '@company.com') ); // Find specific user $john = $users->firstWhere('name', 'John'); $adminUsers = $users->where('role', 'admin'); ``` ### Transformations ```php // Map to different format $userSummaries = $users->map(fn($user) => [ 'id' => $user->id, 'display_name' => $user->getDisplayName(), 'is_admin' => $user->hasRole('admin') ]); // Transform to another DTO $profiles = $users->map(fn($user) => UserProfileDto::from([ 'user_id' => $user->id, 'name' => $user->name, 'email' => $user->email ])); // Flatten nested collections $allAddresses = $users ->pluck('addresses') ->flatten() ->map(fn($addr) => AddressDto::from($addr)); ``` ### Aggregations ```php // Basic aggregations $totalUsers = $users->count(); $averageAge = $users->avg('age'); $oldestUser = $users->max('age'); $youngestUser = $users->min('age'); // Group by property $usersByRole = $users->groupBy('role'); $usersByDepartment = $users->groupBy(fn($user) => $user->department); // Count by group $roleCounts = $users->countBy('role'); // Result: ['admin' => 2, 'user' => 15, 'moderator' => 3] // Custom aggregations $totalSalary = $users->sum(fn($user) => $user->salary); $departmentStats = $users->groupBy('department')->map(function($deptUsers) { return [ 'count' => $deptUsers->count(), 'avg_age' => $deptUsers->avg('age'), 'total_salary' => $deptUsers->sum('salary') ]; }); ``` ## Advanced Collection Features ### Pagination Support ```yaml # Define paginated collection header: class: PaginatedUserCollectionDto namespace: App\DTOs\Collections fields: data: type: array items: { type: object, class: UserDto } pagination: type: object properties: current_page: { type: integer } per_page: { type: integer } total: { type: integer } last_page: { type: integer } has_more_pages: { type: boolean } ``` Usage: ```php $paginatedUsers = PaginatedUserCollectionDto::from([ 'data' => $usersData, 'pagination' => [ 'current_page' => 1, 'per_page' => 10, 'total' => 50, 'last_page' => 5, 'has_more_pages' => true ] ]); // Access paginated data $users = $paginatedUsers->data; // Collection $hasMore = $paginatedUsers->pagination['has_more_pages']; ``` ### Nested Collections ```yaml # Order with items collection header: class: OrderDto namespace: App\DTOs\Orders fields: order_number: { type: string, required: true } customer_email: { type: email, required: true } items: type: array items: { type: object, class: OrderItemDto } min_items: 1 shipping_addresses: type: array items: { type: object, class: AddressDto } max_items: 3 ``` Usage: ```php $order = OrderDto::from([ 'order_number' => 'ORD-123456', 'customer_email' => 'customer@example.com', 'items' => [ ['product_id' => 1, 'quantity' => 2, 'price' => 29.99], ['product_id' => 2, 'quantity' => 1, 'price' => 15.50] ], 'shipping_addresses' => [ ['street' => '123 Main St', 'city' => 'Boston', 'zip' => '02101'] ] ]); // Work with nested collections $totalValue = $order->items->sum(fn($item) => $item->quantity * $item->price); $productIds = $order->items->pluck('product_id'); $primaryAddress = $order->shipping_addresses->first(); ``` ### Collection Validation ```yaml # Validated collection with constraints fields: team_members: type: array items: { type: object, class: TeamMemberDto } min_items: 2 # Team must have at least 2 members max_items: 10 # Team cannot exceed 10 members validation: - "unique_emails" # Custom validation rule - "at_least_one_admin" ``` Custom collection validation: ```php // In a service provider Validator::extend('unique_emails', function ($attribute, $value, $parameters, $validator) { $emails = collect($value)->pluck('email'); return $emails->count() === $emails->unique()->count(); }); Validator::extend('at_least_one_admin', function ($attribute, $value, $parameters, $validator) { return collect($value)->contains('role', 'admin'); }); ``` ## Collection Export and Import ### Bulk Export ```php $users = UserDto::collection($usersData); // Export entire collection $json = $users->toJson(); $csv = $users->toCsv(); $xml = $users->toXml(); // Export with custom format $excel = $users->toExcel([ 'filename' => 'users_export.xlsx', 'sheets' => [ 'Users' => $users->toArray(), 'Summary' => [ 'total_users' => $users->count(), 'active_users' => $users->where('status', 'active')->count(), 'average_age' => $users->avg('age') ] ] ]); ``` ### Selective Export ```php // Export specific fields only $userSummary = $users->map->only(['name', 'email', 'status'])->toArray(); // Export with computed fields $usersWithAge = $users->map(function($user) { return $user->toArray() + [ 'age_group' => $user->getAgeGroup(), 'is_senior' => $user->age >= 65 ]; })->toJson(); ``` ### Bulk Import ```php // Import from CSV $csvData = File::get('users.csv'); $users = collect(str_getcsv($csvData, "\n")) ->map(fn($row) => str_getcsv($row)) ->map(fn($row) => UserDto::from([ 'name' => $row[0], 'email' => $row[1], 'age' => (int) $row[2] ])); // Import with validation $validUsers = collect(); $errors = collect(); foreach ($usersData as $userData) { try { $user = UserDto::fromValidated($userData); $validUsers->push($user); } catch (ValidationException $e) { $errors->push(['data' => $userData, 'errors' => $e->errors()]); } } ``` ## Performance Considerations ### Lazy Collections For large datasets, use lazy collections: ```php // Process large dataset without memory issues $users = LazyCollection::make(function() { $page = 1; do { $results = DB::table('users') ->skip(($page - 1) * 1000) ->take(1000) ->get(); foreach ($results as $user) { yield UserDto::from((array) $user); } $page++; } while ($results->count() === 1000); }); // Process one at a time $users->filter(fn($user) => $user->isActive()) ->each(fn($user) => $user->sendWelcomeEmail()); ``` ### Chunk Processing ```php // Process collections in chunks $users = UserDto::collection($largeUsersData); $users->chunk(100)->each(function($chunk) { // Process 100 users at a time $this->emailService->sendBulkNotifications($chunk); }); ``` ### Memory Optimization ```php // Clear processed items from memory $processedCount = 0; $users->each(function($user) use (&$processedCount) { $this->processUser($user); $processedCount++; // Clear every 1000 items if ($processedCount % 1000 === 0) { gc_collect_cycles(); } }); ``` ## Real-World Examples ### API Response Collection ```php class UserApiController extends Controller { public function index(Request $request) { $users = User::query() ->when($request->search, fn($q) => $q->where('name', 'like', "%{$request->search}%")) ->when($request->role, fn($q) => $q->where('role', $request->role)) ->paginate($request->per_page ?? 15); $userDtos = $users->getCollection() ->map(fn($user) => UserResponseDto::from($user->toArray())); return PaginatedUserCollectionDto::from([ 'data' => $userDtos, 'pagination' => [ 'current_page' => $users->currentPage(), 'per_page' => $users->perPage(), 'total' => $users->total(), 'last_page' => $users->lastPage(), 'has_more_pages' => $users->hasMorePages() ] ])->toArray(); } } ``` ### Bulk Data Processing ```php class UserImportService { public function importFromCsv(string $csvPath): array { $csvData = $this->parseCsv($csvPath); $results = [ 'successful' => collect(), 'failed' => collect(), 'duplicates' => collect() ]; collect($csvData)->each(function($row) use (&$results) { try { // Check for duplicates if (User::where('email', $row['email'])->exists()) { $results['duplicates']->push($row); return; } // Validate and create DTO $userDto = UserDto::fromValidated($row); // Create user User::create($userDto->toArray()); $results['successful']->push($userDto); } catch (ValidationException $e) { $results['failed']->push([ 'data' => $row, 'errors' => $e->errors() ]); } }); return [ 'imported' => $results['successful']->count(), 'failed' => $results['failed']->count(), 'duplicates' => $results['duplicates']->count(), 'errors' => $results['failed']->toArray() ]; } } ``` ### Report Generation ```php class UserReportService { public function generateMonthlyReport(): UserReportDto { $users = User::whereMonth('created_at', now()->month) ->get() ->map(fn($user) => UserDto::from($user->toArray())); return UserReportDto::from([ 'period' => now()->format('F Y'), 'total_users' => $users->count(), 'new_users' => $users->where('created_at', '>=', now()->startOfMonth())->count(), 'active_users' => $users->where('status', 'active')->count(), 'users_by_role' => $users->countBy('role'), 'average_age' => $users->avg('age'), 'top_domains' => $users ->pluck('email') ->map(fn($email) => Str::after($email, '@')) ->countBy() ->sortDesc() ->take(10) ->toArray() ]); } } ``` ## What's Next? Now that you understand collections: - **[Nested DTOs](Nested-DTOs.md)** - Complex hierarchical data structures - **[Configuration](Configuration.md)** - Advanced package configuration - **[Example API Integration](Example-API-Integration.md)** - Real-world API usage - **[Complete Examples](Complete-Examples.md)** - Working with Eloquent and more --- *Collections make working with multiple DTOs as elegant as working with single objects. Laravel Arc's collection support brings the power of Laravel Collections to your type-safe DTOs.* 📊