Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Restify Bundle Laravel Package

antoi/restify-bundle

Reusable Symfony bundle (6.4–8.x, PHP 8.2+) that provides a full REST CRUD stack: abstract repository/service/controller, automatic entity hydration, query filtering, eager-loading, and pick-based response enrichment. Six endpoints per resource, quickly wired.

View on GitHub
Deep Wiki
Context7

RestifyBundle

A reusable Symfony bundle providing a complete REST CRUD stack: abstract service, controller, repository, automatic entity hydration, and pick-based response enrichment.

Compatible with Symfony 6.4, 7.x, and 8.x — requires PHP 8.2+.


Installation

composer require antoi/restify-bundle

If you are not using Symfony Flex, register the bundle manually:

// config/bundles.php
return [
    Antoi\RestifyBundle\RestifyBundle::class => ['all' => true],
];

Full wiring example (User resource)

1. Repository

use Antoi\RestifyBundle\Repository\AbstractRestRepository;

class UserRepository extends AbstractRestRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, User::class);
    }

    // Only these fields may be used as query-param filters
    protected function getFilterableFields(): array
    {
        return ['name', 'email', 'status', 'createdAt'];
    }

    // Eager-load these relations on every list query
    protected function getDefaultJoins(): array
    {
        return ['role'];
    }
}

2. Service

use Antoi\RestifyBundle\Service\AbstractRestService;

class UserService extends AbstractRestService
{
    protected function getEntityClass(): string { return User::class; }

    protected function getWritableFields(): array
    {
        return ['name', 'email', 'role', 'status'];
    }
}

Wire in config/services.yaml (needed because AbstractRestService takes a typed AbstractRestRepository):

App\Service\UserService:
  arguments:
    $repository: '@App\Repository\UserRepository'

3. Controller

use Antoi\RestifyBundle\Controller\AbstractRestController;

#[Route('/api/users')]
class UserController extends AbstractRestController
{
    public function __construct(
        SerializerInterface $serializer,
        ValidatorInterface  $validator,
        PickResolver        $pickResolver,
        UserService         $service,
    ) {
        parent::__construct($serializer, $validator, $pickResolver, $service);
    }

    protected function getReadGroups(): array  { return ['user:read']; }
    protected function getListGroups(): array  { return ['user:list']; }
}

That's it. Six endpoints are registered automatically.


Endpoints

Method Path Action Description
GET / list() Paginated, filterable list
GET /{id} show() Single resource
POST / create() Create + validate + persist
PUT /{id} update() Full replace + validate + persist
PATCH /{id} patch() Partial update + validate + save
DELETE /{id} delete() Remove

Query parameters

Pagination & sorting

GET /api/users?page=2&limit=10&sort=name,-createdAt
  • sort=nameORDER BY name ASC
  • sort=-createdAtORDER BY createdAt DESC
  • Multiple fields: sort=name,-createdAt

Filtering

Any query param not in [page, limit, sort, pick] is forwarded to the repository as a filter.

GET /api/users?status=active&createdAt_gte=2024-01-01&name_like=john

Supported operators (append as suffix):

Suffix SQL equivalent
(none) = :value
_gte >= :value
_gt > :value
_lte <= :value
_lt < :value
_neq != :value
_like LIKE %value%
_in IN (a, b, c)
=null IS NULL

Pick — extra properties outside serialization groups

GET /api/users/42?pick=ip,lastLogin.ip,roles.name

Enriches the serialized response with additional properties resolved from the entity:

  • ip$user->getIp()
  • lastLogin.ip$user->getLastLogin()->getIp() (deep traversal)
  • roles.name[$role->getName(), ...] for each role in the collection

Response shapes

Single resource

{
  "success": true,
  "data": { "id": 1, "name": "John" }
}

Paginated list

{
  "success": true,
  "data": [ ... ],
  "meta": { "total": 42, "page": 1, "limit": 20, "pages": 3 }
}

Validation error (422)

{
  "success": false,
  "message": "Validation failed.",
  "errors": {
    "email": ["This value is not a valid email address."]
  }
}

Not found (404)

{ "success": false, "message": "User with ID \"99\" was not found." }

Wire InvalidPayloadException (400) and ResourceNotFoundException (404) to your API error listener to produce these shapes automatically.


Components reference

Class Namespace Description
AbstractRestRepository …\Repository Paginator, dynamic filters, operator suffixes
AbstractRestService …\Service Decode → filter fields → hydrate → persist
AbstractRestController …\Controller Full CRUD actions, serialization, pick merging
EntityHydrator …\Service Scalar + datetime + ManyToOne + ManyToMany
PickResolver …\Service Dot-notation deep property resolver
ApiResponse …\DTO { success, data, message } envelope
PaginatedResponse …\DTO { success, data, meta } envelope
InvalidPayloadException …\Exception 400 — bad JSON or wrong field type
ResourceNotFoundException …\Exception 404 — entity not found
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony
spatie/flare-daemon-runtime