Weave 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

Api Platform Workflow Extension Laravel Package

wesnick/api-platform-workflow-extension

Integrates Symfony Workflow with API Platform by exposing workflow transitions as REST operations, letting clients move resources through workflow states via configurable endpoints with guard and access-control support.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle wires the Symfony Workflow component into API Platform so that workflow transitions can be exposed as API operations instead of hand-rolling a controller for every state change. It is archived, so treat this as a reference implementation to copy or fork rather than a long-term dependency. Before committing, open composer.json in the repo and confirm the constraint it pins (api-platform/core, symfony/workflow, PHP). If it targets API Platform 2.x, plan for the upgrade cost before you adopt it.

Minimal steps:

  1. Install and register.

    composer require wesnick/api-platform-workflow-extension
    

    Add the bundle to config/bundles.php (or AppKernel) if the package does not use Symfony Flex auto-discovery. If there is no tagged release, pin a commit hash — never dev-master on an archived repo.

  2. Look here first, in this order:

    • README.md for the intended annotation/attribute and request shape.
    • src/Resources/config/services.yaml (or services.xml) to see what is registered and under which tags — this tells you exactly which API Platform extension point is being used (metadata factory decorator, operation, or event subscriber).
    • The transition controller/action class under src/ — this is the code you will most likely need to patch for API Platform 3.x.
    • tests/ for the canonical request/response example.
  3. First use case — transition one entity through one workflow. Configure a normal Symfony workflow against a string property on a Doctrine entity:

    # config/packages/workflow.yaml
    framework:
      workflows:
        article:
          type: state_machine
          marking_store:
            type: method
            property: state
          supports:
            - App\Entity\Article
          initial_marking: draft
          places: [draft, review, published, archived]
          transitions:
            submit:  { from: draft,   to: review }
            publish: { from: review,  to: published }
            archive: { from: published, to: archived }
    

    Mark the entity as an API Platform resource, then declare the transition operation the bundle provides (check the README for the exact operation name — commonly a custom POST/PATCH operation pointing at the bundle's controller, e.g. /articles/{id}/transition). Send {"transition": "submit"} and confirm the response body reflects the new state.

If you only need the marking property updated and validated, this is the whole value proposition: one attribute plus workflow config replaces a custom controller and its validation logic.

Implementation Patterns

Explicit transition endpoint vs. automatic transitions. The bundle's main pattern is an explicit operation: the client names the transition, the bundle loads the workflow, calls $workflow->apply($entity, $transition), and persists. Use this when transitions are user-driven actions. For automatic transitions (e.g. submit immediately on create), keep that in a Doctrine listener or API Platform state processor and reserve the bundle's endpoint for manual moves — mixing both in one place makes guard behavior hard to reason about.

Keep the marking property out of mass assignment. The state property must be writable by the workflow, but not by the client. Add it to a read: false serialization group or a denormalization group that excludes it, otherwise a client can PATCH state directly and bypass the workflow entirely. This is the most common security mistake with any workflow-on-API setup.

Guards and security are two layers — use both.

  • Symfony workflow guards (guard on the transition or a GuardListener subscriber) enforce business rules: "only an editor can publish", "cannot publish without a reviewer".
  • API Platform security on the operation enforces authorization at the endpoint.

Do not rely on the operation's security alone to prevent invalid transitions; and do not rely on guards alone for access control. When a guard rejects a transition, WorkflowInterface::apply() throws Symfony\Component\Workflow\Exception\NotPermittedException. Wrap the transition call and map it to a 409 Conflict (or 422) with the list of currently enabled transitions — a bare 500 is the default failure mode.

Expose enabled transitions to the client. A robust pattern is a GET /articles/{id}/transitions sub-resource (or a computed field) returning $workflow->getEnabledTransitions($entity). Clients then render exactly the buttons that will succeed instead of guessing and eating 409s. If the bundle does not ship this, add it as a small custom operation — it is cheap and dramatically improves the API ergonomics.

Side effects belong in workflow events, not the controller. Use EventDispatcherInterface / EventSubscriberInterface on workflow.article.transition and workflow.article.completed to send notifications, write audit rows, or dispatch Messenger messages. This keeps the transition endpoint thin and makes side effects fire regardless of whether the transition came from HTTP or from a console command.

Transactions and persistence. The transition and the EntityManager::flush() should happen inside the same request transaction. If the bundle's controller flushes for you, verify that it does not double-flush after your subscriber already persisted. For async transitions via Messenger, remember the workflow state is only consistent if the message carries the entity id and re-loads it inside the handler.

Testing strategy.

  • Unit-test the workflow directly with WorkflowInterface — no HTTP needed to prove guard logic.
  • Functional-test the operation with KernelBrowser: assert 2xx on a valid transition, 409 on an invalid one, and that the state column actually changed in the DB.
  • Add a test that PATCHing the state property directly returns 400/ignored, to lock in the serialization group.

Gotchas and Tips

Archived means version-pinned or forked. No security patches and no compatibility work for API Platform 3.x. API Platform 3 changed resource metadata (ApiResource attributes, operations vs collectionOperations/itemOperations, StateProcessorInterface replacing some event hooks). Expect the bundle's metadata decorator or operation to be the breakage point. Fork it, or lift the ~100 lines of transition logic into your own custom operation — often cheaper than maintaining a fork.

The marking store is a leaky abstraction. marking_store.type: method requires a real getter/setter (getState/setState); type: property bypasses accessors and will not fire your entity's business logic. Verify which one the bundle assumes — many examples use method because the API Platform serializer and Doctrine both need consistent access.

Exceptions are not HTTP responses. NotPermittedException, LogicException for unknown transitions, and InvalidArgumentException for a transition not in the workflow all surface as 500s unless handled. Centralize a try/catch (or an API Platform exception listener) that translates them into 4xx responses with a stable error shape. Unknown transition names in particular should be a 400, not a crash.

Multiple workflows on one entity need explicit naming. If an entity supports article and article_review, WorkflowInterface cannot be autowired unambiguously for that entity. Resolve via $workflowRegistry->get($entity, 'article') or inject the specific service id (workflow.article). This bites when you add a second workflow later.

Concurrency and double transitions. Two concurrent publish requests can both pass the guard before either flushes. Add an optimistic-lock version column (#[ORM\Version]) and let the loser get a 409. For high-contention state machines, consider a SELECT ... FOR UPDATE inside the transition handler.

Config changes are cached. After editing workflow.yaml, places, or transitions, run bin/console cache:clear. In dev the profiler's Workflow panel shows the marking store and enabled transitions — the fastest way to confirm your config actually attached to the entity.

Debugging checklist when a transition does nothing:

bin/console debug:container workflow.article
bin/console debug:container --tag=api_platform.metadata.resource.metadata_collection_factory
bin/console debug:router            # is the transition route registered?
bin/console debug:config framework workflow

Also confirm the entity's supports entry exactly matches the FQCN (a typo silently produces a workflow that never applies), and check /api/docs to see whether the operation made it into the OpenAPI schema. If it is missing there, the metadata extension did not run — that is the archived-bundle-on-newer-API-Platform failure signature.

Serialization groups are the extension point you will actually use. Rather than fighting the bundle's operation definition, most teams keep the bundle for the transition call and add their own normalization_context / custom StateProvider to shape the response. Decorating the controller (Symfony service decoration) is the least invasive way to add validation or logging without touching vendor code.

Have an exit plan. Because the repo is archived, isolate the integration behind your own TransitionHandler interface and one adapter class. When you upgrade API Platform, you replace the adapter, not every call site. Given the 11-star footprint, budget for reading the source rather than finding Stack Overflow answers — the codebase is small enough that this is feasible in an afternoon.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment