Projectsphp-dataPredefined

PHP Data

Package

Lightweight, framework-agnostic Data Transfer Object (DTO) library with first-class Laravel integration.

Predefined DTOs

The package ships a few ready-made Data objects for common Laravel scenarios. These live under BradieTilley\Data\Predefined and require Laravel.

ApiResponse

A consistent JSON envelope for API responses. It extends Illuminate\Http\JsonResponse, so you can return it directly from a controller.

The envelope shape is:

{
    "status": 200,
    "success": true,
    "message": "...",
    "data": { },
    "errors": [ ]
}

Success responses

use BradieTilley\Data\Predefined\Api\ApiResponse;

return ApiResponse::success('Product loaded', ProductData::from($product));

return ApiResponse::created('Product created', ProductData::from($product)); // 201

->data(...) attaches or replaces the payload fluently:

return ApiResponse::success('Loaded')->data(ProductData::from($product));

Error responses

return ApiResponse::error('Something went wrong');                 // 400
return ApiResponse::unauthorized('Not logged in');                 // 401
return ApiResponse::forbidden('Not allowed');                      // 403
return ApiResponse::notFound('Product not found');                 // 404
return ApiResponse::invalid('Validation failed', $errors);         // 422
return ApiResponse::conflict('Resource changed');                  // 409

Errors accept a field-keyed array; each entry becomes a structured error object:

ApiResponse::invalid('Failed to create product', [
    'title' => ['Must be at least 3 characters long'],
]);
// errors => [ ['message' => 'Must be at least 3 characters long', 'field' => 'title'] ]

Pass a Throwable to derive the message (and attach the exception to the response) automatically:

ApiResponse::error($exception);

From a validator

public function store(Request $request)
{
    $validator = Validator::make($request->all(), $rules);

    if ($validator->fails()) {
        return ApiResponse::validator($validator); // 422 with the error bag
    }

    // ...
}

Paginator DTOs

LengthAwarePaginatorData and CursorPaginatorData wrap Laravel's paginators into Data objects. Use Data::paginate() to build one from a paginator instance — items are hydrated to the calling Data class:

$page = ProductData::paginate($products); // $products is a Laravel paginator

LengthAwarePaginatorData

Maps a LengthAwarePaginator. Serialized shape:

PropertySource
total$paginator->total()
lastPage$paginator->lastPage()
perPage$paginator->perPage()
currentPage$paginator->currentPage()
items$paginator->items(), each cast via ProductData::from() when using paginate()
use BradieTilley\Data\Predefined\LengthAwarePaginatorData;

/** @var LengthAwarePaginatorData $page */
$page = ProductData::paginate(Product::paginate(15));

$page->toArray();
// [
//   'total' => 42,
//   'lastPage' => 3,
//   'perPage' => 15,
//   'currentPage' => 1,
//   'items' => [ /* ProductData::toArray() for each item */ ],
// ]

The class is marked #[Template('T')] for tooling that understands generic item types.

CursorPaginatorData

Maps a CursorPaginator. Serialized shape:

PropertySource
hasMore$paginator->hasMorePages()
perPage$paginator->perPage()
cursor$paginator->cursor()?->toArray() ?? []
items$paginator->items(), each cast when using paginate()
$page = ProductData::paginate(Product::cursorPaginate(15));

$page->toArray();
// [
//   'hasMore' => true,
//   'perPage' => 15,
//   'cursor' => [ /* cursor payload */ ],
//   'items' => [ /* ProductData::toArray() for each item */ ],
// ]

You can also construct the wrappers directly with an optional item type:

use BradieTilley\Data\Predefined\LengthAwarePaginatorData;

new LengthAwarePaginatorData($paginator, ProductData::class);