{"id":14983735,"url":"https://github.com/w3c/w3clifecycleeventsbundle","last_synced_at":"2025-04-13T06:37:20.870Z","repository":{"id":49506778,"uuid":"83440118","full_name":"w3c/W3CLifecycleEventsBundle","owner":"w3c","description":"A Symfony bundle to dispatch usable entity lifecycle events (create, update, delete)","archived":false,"fork":false,"pushed_at":"2025-01-10T15:10:17.000Z","size":195,"stargazers_count":18,"open_issues_count":0,"forks_count":9,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-04-11T18:00:06.830Z","etag":null,"topics":["doctrine","doctrine-orm","events","lifecycle","lifecycle-events","symfony-bundle","symfony3"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/w3c.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-02-28T14:12:23.000Z","updated_at":"2025-01-25T15:44:44.000Z","dependencies_parsed_at":"2024-11-15T21:37:53.490Z","dependency_job_id":"05ae3df6-e427-461b-9482-34b7e1640935","html_url":"https://github.com/w3c/W3CLifecycleEventsBundle","commit_stats":{"total_commits":111,"total_committers":3,"mean_commits":37.0,"dds":0.05405405405405406,"last_synced_commit":"d359cf2e5001c6efe67c766362b1a04ba7bd47ea"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/w3c%2FW3CLifecycleEventsBundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/w3c%2FW3CLifecycleEventsBundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/w3c%2FW3CLifecycleEventsBundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/w3c%2FW3CLifecycleEventsBundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/w3c","download_url":"https://codeload.github.com/w3c/W3CLifecycleEventsBundle/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248675347,"owners_count":21143763,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["doctrine","doctrine-orm","events","lifecycle","lifecycle-events","symfony-bundle","symfony3"],"created_at":"2024-09-24T14:07:51.109Z","updated_at":"2025-04-13T06:37:20.837Z","avatar_url":"https://github.com/w3c.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://github.com/w3c/W3CLifecycleEventsBundle/actions/workflows/symfony.yml/badge.svg)](https://github.com/w3c/W3CLifecycleEventsBundle/actions/workflows/symfony.yml)\n[![Coverage Status](https://coveralls.io/repos/github/w3c/W3CLifecycleEventsBundle/badge.svg?branch=master)](https://coveralls.io/github/w3c/W3CLifecycleEventsBundle?branch=master)\n[![SensioLabsInsight](https://insight.symfony.com/projects/b0d1493c-6de8-4c18-87ad-a12d2487fd59/mini.svg)](https://insight.symfony.com/account/widget?project=b0d1493c-6de8-4c18-87ad-a12d2487fd59)\n\nlifecycle-events-bundle\n=======================\n\nThis Symfony bundle is meant to capture and dispatch events that happen throughout the lifecycle of entities:\n- creation\n- deletion\n- updates\n\nDoctrine already provides such events, but using them directly has a few shortcomings:\n- You don't decide at which point in a action you want to dispatch events. Events are fired during a flush.\n- When Doctrine events are fired, you are not assured that the entities have actually been saved in the database. \nThis is obvious for preUpdate (sent before persisting the changes), but postPersist and preRemove have the same issue: \nif you persist two new entities in a single transaction, the first insert could work (thus an event would be sent) but \nnot the second, resulting in no entities being saved at all\n\nThis bundle aims at circumventing these issues by providing means to fire entity creation, deletion and update events \nafter a successful flush or whenever needed.\n\nIt also provides a set of attributes to configure what events should be sent and when.\n\nThis bundle was partially inspired by @kriswallsmith's talk\n[\"Matters of State\"](https://www.youtube.com/watch?v=lEiwP4w6mf4).\n\nInstallation\n------------\n\nSimply run assuming you have installed composer.phar or composer binary:\n\n``` bash\n$ php composer.phar require w3c/lifecycle-events-bundle 1.0.*\n```\n\nFinally, enable the bundle in the kernel:\n\n``` php\n\u003c?php\n// app/AppKernel.php\n\npublic function registerBundles()\n{\n    $bundles = array(\n        // ...\n        new W3C\\LifecycleEventsBundle\\W3CLifecycleEventsBundle(),\n    );\n}\n```\nThat's it!\n\nUsage\n-----\n\n### Annotations\n\nFor this bundle to do anything interesting, it is necessary to annotate entities you want to monitor.\nThere are five attributes. Three of them apply to classes (`#[On\\Create]`, `#[On\\Delete]` and `#[On\\Update]`) and the remaining two to\nproperties (`#[On\\Change]`, `#[On\\IgnoreClassUpdates]`).\n\nAll attributes live in the namespace `W3C\\LifecycleEventsBundle\\Attribute`, so it is recommended to import it:\n``` php\n\u003c?php\nuse W3C\\LifecycleEventsBundle\\Attribute as On;\n```\n\n#### `#[On\\Create]` \n\nMonitors the creation of new entities. It accepts the following parameters:\n- `event`: the event being sent every time an entity is created (`w3c.lifecycle.created` by default)\n- `class`: the class of this event (`W3C\\LifecycleEventsBundle\\Event\\LifecycleEvent` by default). This class\nmust have a constructor with the following signature:\n\n``` php\n\u003c?php\n/**\n * @param mixed $entity the entity being created\n */\npublic function __construct($entity)\n```\n\n#### `#[On\\Delete]`\n\nMonitors the deletion (or soft deletion, if you use Doctrine Extensions) of entities. It accepts the following parameters:\n- `event`: the event being sent every time an entity is deleted (`w3c.lifecycle.deleted` by default)\n- `class`: the class of this event (`W3C\\LifecycleEventsBundle\\Event\\LifecycleEvent` by default). This class\nmust have a constructor with the following signature:\n\n``` php\n\u003c?php\n/**\n * @param mixed $entity the entity being deleted\n * @param array $identifier identifier of the entity being deleted\n */\npublic function __construct($entity, $identifier)\n```\n\n#### `#[On\\Update]`\n\nMonitors updates to entities. It accepts the following parameters:\n- `event`: the event being sent (`w3c.lifecycle.updated` by default) every time an entity is updated\n- `class`: the class of this event (`W3C\\LifecycleEventsBundle\\Event\\LifecycleUpdateEvent` by default). This class\nmust have a constructor with the following signature: \n\n``` php\n\u003c?php\n/**\n * @param object $entity the entity being modified\n * @param array $propertiesChangeSet list of changes to properties\n * @param array $collectionsChangeSet list of changes to collections\n */\npublic function __construct($entity, array $propertiesChangeSet = null, array $collectionsChangeSet = null)\n```\n- `monitor_collections`: whether the attribute should monitor changes to collection fields. Defaults to true\n- `monitor_owning`: whether owning side relationship changes should be also monitored as inverse side changes. Defaults to false\n\n#### `#[On\\Change]`\n\nMonitors whenever an entity field (property or collection) changes. It accepts the following parameters:\n- `event`: the event being sent (`w3c.lifecycle.property_changed` or `w3c.lifecycle.collection_changed` by default) \nevery time an entity is updated\n- `class`: the class of this event (`W3C\\LifecycleEventsBundle\\Event\\LifecyclePropertyChangedEvent` by default if put on\na regular property, `W3C\\LifecycleEventsBundle\\Event\\LifecycleCollectionChangedEvent` when put on a collection).\nThis class must have a constructor with the following signature for regular properties: \n\n``` php\n\u003c?php\n/**\n * @param object $entity entity being modified\n * @param string $property property being modified\n * @param array $oldValue property's old value\n * @param array $newValue property's new value\n */\npublic function __construct($entity, $property, $oldValue = null, $newValue = null)\n```\nand for collections:\n\n``` php\n\u003c?php\n/**\n * @param object $entity entity being modified\n * @param string $property collection being modified\n * @param array $deletedElements elements being deleted from the collection\n * @param array $insertedElements elements being inserted to the collection\n */\npublic function __construct($entity, $property, $deletedElements = null, $insertedElements = null)\n```\n- `monitor_owning`: whether to record changes to this field when owning sides change (defaults to `false`). Using\n`#[On\\Change]` on inverse side of relationships won't trigger any events unless this paramter is set to true. This\nparameter is likely to be removed in the next major version and act as if it was set to `true` since when the\nattribute is added to the inverse side of relationship, it is obvious it means that you want changes to owning side to\nbe monitored here\n\n#### `#[On\\IgnoreClassUpdates]`\n\nThis attribute is a bit different. When placed on a field (property or collection), it prevents `#[On\\Update]` from \nfiring events related to this field. `#[On\\Change]` ones will still work. This attribute does not allow any parameters.\n\n#### Example class\n\n``` php\n\u003c?php\n/**\n * Person\n *\n * @ORM\\Table(name=\"person\")\n * @ORM\\Entity(repositoryClass=\"AppBundle\\Repository\\PersonRepository\")\n */\n #[On\\Create(event: PersonEvents::CREATED, class: PersonEvent::class)]\n #[On\\Delete(event: PersonEvents::DELETED, class: PersonEvent::class)]\n #[On\\Update(event: PersonEvents::UPDATED)]\nclass Person\n{\n    /**\n     * @ORM\\Column(name=\"id\", type=\"integer\")\n     * @ORM\\Id\n     * @ORM\\GeneratedValue(strategy=\"AUTO\")\n     */\n    private ?int $id = null;\n\n    /**\n     * @ORM\\Column(name=\"name\", type=\"string\", length=255, unique=true)\n     */\n    #[On\\Change(event: PersonEvents::PROPERTY_CHANGED)]\n    private ?string $name = null;\n\n    /**\n     * @var Person[]|Collection\n     *\n     * @ORM\\ManyToMany(targetEntity=\"Person\")\n     * @ORM\\JoinTable(name=\"friendships\",\n     *      joinColumns={@ORM\\JoinColumn(name=\"person_id\", referencedColumnName=\"id\")},\n     *      inverseJoinColumns={@ORM\\JoinColumn(name=\"friend_id\", referencedColumnName=\"id\")}\n     *      )\n     */\n    #[On\\Change]\n    #[On\\IgnoreClassUpdates]\n    private Collection $friends;\n\n    [...]   \n}\n```\n\nWith such a class, the following events will be fired:\n- `PersonEvents::CREATED` when a Person is created, with the class `PersonEvent`\n- `PersonEvents::DELETED` when a Person is deleted, with the class `PersonEvent`\n- `PersonEvents::UPDATED` when a Person is updated, with the  class `LifecycleUpdatedEvent`, but will not record changes\nto `Person::$friends`\n- `PersonEvents::PROPERTY_CHANGED` when Person::$name changes, with the class `LifecyclePropertyChangedEvent`\n- `w3c.lifecycle.collection_changed` when Person::$friends changes, with the class `LifecycleCollectionChangedEvent`\n\nDisabling automatic dispatching of events\n-----------------------------------------\n\nLifecycle events are dispatched by default after a successful flush. If needed, this can be disabled:\n- globally in config.yml\n``` yaml\nw3_c_lifecycle_events:\n    auto_dispatch:        false\n```\n\n- temporarily in a container (since Symfony 4):\n\n``` php\n\u003c?php\nuse W3C\\LifecycleEventsBundle\\Services\\LifecycleEventsDispatcher;\npublic function testAction(LifecycleEventsDispatcher $dispatcher)\n{\n    [...]\n    $dispatcher-\u003esetAutoDispatch(false);\n    [...]\n}\n```\n\nEvents can then be dispatched manually using the following:\n``` php\n\u003c?php\n$dispatcher-\u003edispatchEvents(); // manually dispatch all events\n```\n\nSpecial case: inheritance\n-------------------------\n\nIf you use inheritance in your entities, make sure to set fields of the parent class(es) protected (or public) so that\nchanges to those can be monitored as belonging to subclasses.\n\nFailing to do so may lead to `\\ReflectionException` exceptions such as:\n```\nProperty W3CGroup::$updated does not exist. Could this be a private field of a parent class?\n```\nEven if those fields are not monitored!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fw3c%2Fw3clifecycleeventsbundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fw3c%2Fw3clifecycleeventsbundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fw3c%2Fw3clifecycleeventsbundle/lists"}