Installation
composer require swag-industries/melodiia
This auto-generates melodiia.yaml in config/.
First Use Case: Basic CRUD Endpoint
PostType):
// src/Form/PostType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class PostType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('title', TextType::class)
->add('content', TextareaType::class);
}
}
#[Melodiia\Resource]:
// src/Entity/Post.php
use Melodiia\Resource;
#[Resource(formType: PostType::class)]
class Post {}
/api/posts).Test the API
/api/docs for Swagger UI.POST to /api/posts with JSON payload:
{"title": "Hello", "content": "World"}
// src/Form/UserType.php
class UserType extends AbstractType {
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'csrf_protection' => false, // Disable for APIs
'validation_groups' => ['Default', 'api'],
]);
}
}
validation_groups to separate API-specific rules (e.g., api).// src/EventSubscriber/CustomResponseSubscriber.php
use Melodiia\Event\ResourceEvent;
class CustomResponseSubscriber implements EventSubscriberInterface {
public static function getSubscribedEvents() {
return [
ResourceEvent::PRE_SERIALIZE => 'onPreSerialize',
];
}
public function onPreSerialize(ResourceEvent $event) {
$event->getData()->setAttribute('custom_field', 'value');
}
}
services.yaml:
services:
App\EventSubscriber\CustomResponseSubscriber:
tags: ['melodiia.event_subscriber']
#[Resource]:
#[Resource(
formType: PostType::class,
collectionOperations: ['get', 'post'],
itemOperations: ['get', 'put', 'delete'],
subresources: ['comments'] // Nested route: /api/posts/{id}/comments
)]
class Post {}
CommentType form and annotate Comment similarly.# melodiia.yaml
security:
enabled: true
provider: app_user_provider
roles:
POST: ROLE_USER
PUT: ROLE_ADMIN
melodiia.security.firewall.Melodiia\Test\ApiTestCase:
use Melodiia\Test\ApiTestCase;
class PostTest extends ApiTestCase {
public function testCreatePost() {
$response = $this->postJson('/api/posts', [
'title' => 'Test',
'content' => 'Content',
]);
$this->assertResponseSuccess();
$this->assertJsonApiResponse($response);
}
}
Form Validation Mismatch
Default group but API expects api.validation_groups in formType options (see Implementation Patterns).CSRF Protection
formType options:
$builder->setCsrfProtection(false);
JSON:API Compliance
setAttribute() for top-level data or nest under data.attributes.Route Overrides
melodiia.yaml:
routing:
auto_generate: false
Then define routes manually in config/routes.yaml.Pagination
melodiia.yaml:
pagination:
enabled: true
items_per_page: 20
max_items_per_page: 100
Enable Verbose Logging
# melodiia.yaml
debug: true
Logs events to var/log/melodiia.log.
Check Event Dispatching
Melodiia\Event\ResourceEvent listeners to inspect data before serialization.Validate JSON:API Responses
data, errors, meta.links.related or data arrays.Custom Serializers
Melodiia\Serializer\SerializerInterface and bind in services.yaml:
services:
App\Serializer\CustomSerializer:
tags: ['melodiia.serializer']
Dynamic Forms
FormBuilderInterface callbacks to conditionally add fields:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$data = $event->getData();
if ($data->isAdmin()) {
$event->getForm()->add('admin_field');
}
});
Webhook Integration
Melodiia\Event\ResourceEvent to trigger webhooks on POST/PUT:
public function onPostCreate(ResourceEvent $event) {
if ($event->getOperation() === 'post') {
$this->webhookClient->send($event->getData());
}
}
GraphQL-like Queries
melodiia.yaml to enable partial responses:
serialization:
include_nulls: false
groups: ['api']
?fields[posts]=title,content.How can I help you explore Laravel packages today?