Installation:
composer require 21torr/rad
Ensure your config/bundles.php includes RadBundle.
First Use Case:
JsonResponse with ApiResponse in controllers:
use Rad\ApiResponse;
public function show(): ApiResponse
{
return ApiResponse::ok(['data' => $this->getData()]);
}
EntityModel for CRUD operations:
use Rad\EntityModel;
$model = new EntityModel($entityManager, User::class);
$model->persist($request->toArray());
Key Entry Points:
src/Rad/ namespace (core classes)config/packages/rad.yaml (optional bundle config)Response Normalization:
// Controller
public function create(Request $request): ApiResponse
{
$data = $this->handleRequest($request);
return ApiResponse::created($data, ['Location' => $this->generateUrl('show', ['id' => $data['id']])]);
}
ApiResponseNormalizer for custom serialization:
$normalizer = $this->container->get(ApiResponseNormalizer::class);
$response = $normalizer->createResponse($data, 200, ['X-Custom' => 'Header']);
Error Handling:
try {
$result = $this->service->execute();
} catch (ValidationException $e) {
return ApiResponse::validationError($e->getErrors());
}
CRUD with EntityModel:
$model = new EntityModel($entityManager, Post::class);
$model->persist($request->validate()->all()); // Handles create/update
$model->refresh(); // Force refresh from DB
$model->update($data, skipModified: true);
Change Tracking:
$changeChecker = new DoctrineChangeChecker($entityManager);
$changes = $changeChecker->getEntityChanges($entity); // Returns array of changed fields
use Rad\ImportData;
$import = new ImportData($request->json());
$import->normalizeEmptyStringsToNull(); // Auto-normalize
$cleanData = $import->getData();
// In your entity
#[Attribute('CAN_EDIT')]
public function canEdit(): bool { return $this->isAdmin; }
// In voter
public function vote(AuthorizationContext $context): bool
{
return $context->getAttribute('CAN_EDIT', $entity);
}
$translation = $this->translationHelper->translate('key', ['%name%' => $user->name]);
$args = new ArgumentBag(['key' => 'value']);
$args['nested']['key'] = 'value'; // ArrayAccess support
Rad\BaseController for shared logic:
class PostController extends BaseController
{
public function __construct(private EntityManagerInterface $em) {}
protected function getEntityManager(): EntityManagerInterface
{
return $this->em;
}
}
TranslationHelper, ApiResponseNormalizer, etc., directly.EntityModel with FormFactory for seamless integration:
$form = $this->formFactory->create(PostType::class, $model->getEntity());
PHP Version:
composer.json for your project’s constraints.php -v and update if needed.DoctrineChangeChecker:
DoctrineChangeChecker::getEntityChanges() sparingly—it modifies the UnitOfWork.$clone = clone $entity;
$changes = $changeChecker->getEntityChanges($clone);
ApiResponse:
bool (deprecated in v3.1.0). Always pass HTTP codes:
// ❌ Avoid
ApiResponse::ok(true); // Deprecated
// ✅ Use
ApiResponse::ok(['data' => $data], 200);
NotNull violations.EntityModel:
persist() handles both create/update. Use update() only for partial updates.refresh() detaches and re-attaches the entity. Use cautiously in transactions.ImportData:
null by default (v3.4.1). Disable with:
$import = new ImportData($data, normalizeEmptyStrings: false);
ArrayAccess in ArgumentBag:
UndefinedArrayKeyException if keys don’t exist. Initialize with defaults:
$args = new ArgumentBag(['nested' => ['key' => null]]);
JSON Request Logging:
# config/packages/dev/rad.yaml
rad:
debug: true
Change Tracking:
$changes = $changeChecker->getEntityChanges($entity);
$this->logger->debug('Entity changes', ['changes' => $changes]);
Autocompletion:
@method annotations for IDE support (improved in v3.4.3). Example:
/**
* @method static self create(array $data)
*/
class EntityModel {}
Custom ApiResponse Normalizers:
Rad\Normalizer\ApiResponseNormalizerInterface:
class CustomNormalizer implements ApiResponseNormalizerInterface
{
public function normalize($data, int $statusCode, array $headers = []): ApiResponse
{
// Custom logic
return ApiResponse::create($data, $statusCode, $headers);
}
}
services:
Rad\Normalizer\ApiResponseNormalizerInterface: '@App\Normalizer\CustomNormalizer'
Translation Helpers:
Rad\TranslationHelper for custom domains or fallbacks:
class AppTranslationHelper extends TranslationHelper
{
protected function getDefaultDomain(): string
{
return 'app';
}
}
EntityModel Events:
entity_model.persist or entity_model.update via Symfony events:
// config/services.yaml
Rad\EntityModel:
tags: ['rad.entity_model']
Doctrine Events:
onFlush for pre-persist logic:
$eventManager->addEventListener(
ORMEvents::ON_FLUSH,
fn(OnFlushEventArgs $args) => $this->prePersistLogic($args)
);
Optional Dependencies:
symfony/translator is optional. If missing, TranslationHelper throws LogicException.composer require symfony/translator
$helper = new TranslationHelper(new NullTranslator());
Cache Invalidation:
InMemoryCache is not persistent. Clear manually or use Symfony’s cache system:
$cache = new InMemoryCache();
$cache->clear(); // Manual invalidation
refresh() in loops—batch operations instead.$clone = clone $
How can I help you explore Laravel packages today?