-
-
Notifications
You must be signed in to change notification settings - Fork 0
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.
Collections in Laravel Arc are arrays of DTOs that behave like Laravel Collections but with additional DTO-specific functionality.
// Create collection of DTOs
$users = collect([
UserDto::from(['name' => 'John', 'email' => '[email protected]']),
UserDto::from(['name' => 'Jane', 'email' => '[email protected]']),
UserDto::from(['name' => 'Bob', 'email' => '[email protected]'])
]);
// Collection methods work seamlessly
$activeUsers = $users->filter(fn($user) => $user->status === 'active');
$emails = $users->pluck('email');
$totalAge = $users->sum('age');
// From array of arrays
$usersData = [
['name' => 'John', 'email' => '[email protected]', 'age' => 30],
['name' => 'Jane', 'email' => '[email protected]', 'age' => 25],
['name' => 'Bob', 'email' => '[email protected]', 'age' => 35]
];
$users = UserDto::collection($usersData);
// or
$users = collect($usersData)->map(fn($data) => UserDto::from($data));
// 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 JSON API response
$response = Http::get('https://api.example.com/users');
$users = collect($response->json('data'))
->map(fn($userData) => UserDto::from($userData));
Define collections directly in 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:
final readonly class UserCollectionDto
{
public function __construct(
public Collection $users, // Collection<UserDto>
public array $metadata,
) {}
public function getUsersCount(): int
{
return $this->users->count();
}
public function getActiveUsers(): Collection
{
return $this->users->filter(fn($user) => $user->status === 'active');
}
}
$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');
// 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));
// 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')
];
});
# 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:
$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<UserDto>
$hasMore = $paginatedUsers->pagination['has_more_pages'];
# 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:
$order = OrderDto::from([
'order_number' => 'ORD-123456',
'customer_email' => '[email protected]',
'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();
# 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:
// 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');
});
$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')
]
]
]);
// 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();
// 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()]);
}
}
For large datasets, use lazy collections:
// 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());
// Process collections in chunks
$users = UserDto::collection($largeUsersData);
$users->chunk(100)->each(function($chunk) {
// Process 100 users at a time
$this->emailService->sendBulkNotifications($chunk);
});
// 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();
}
});
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();
}
}
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()
];
}
}
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()
]);
}
}
Now that you understand collections:
- Nested DTOs - Complex hierarchical data structures
- Configuration - Advanced package configuration
- Example API Integration - Real-world API usage
- Complete Examples - 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. π
Laravel Arc - Generate Type-Safe DTOs from YAML Definitions
π Home | π Get Started | π Examples | βοΈ Config
From YAML to Type-Safe Code - Made with β€οΈ for the Laravel community
π Home
- π Understanding YAML Structure
- π·οΈ Field Types
- π Field Transformers
- π Behavioral Traits
YAML β DTO β Type-Safe Code
Laravel Arc transforms your YAML definitions into powerful PHP DTOs with automatic validation, field transformers, and behavioral traits.
- π Get Started - Create your first DTO in 5 minutes
- π All Examples - Copy-paste ready examples
- β‘ Commands - CLI reference