Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Deprecate extension via Doctrine Event Manager #5784

@morozov

Description

@morozov

Recently, I tried to enable checking exceptions in PHPStan and, besides a few other issues with the DBAL API, stumbled upon the one that shows what an awfully overcomplicated piece of work the Events API is:

  1. AbstractPlatform::getDropTableSQL() dispatches an onSchemaDropTable event which can override the SQL used to drop the table.
  2. The return type of SchemaDropTableEventArgs::getSql() is ?string. If it returns NULL, the platform will throw an UnexpectedValueException.
  3. There's no way to tell that a given method is an event handler unless it's documented as such or whoever reads the code knows that the methods with names starting with on are event handlers. Those methods are not invoked from anywhere in the code explicitly.

Here's a chain of questions that leads to the above conclusion:

  1. Why is SchemaDropTableEventArgs allowed to return NULL if it's not even a valid value?
  2. We could require that it returns a string but then what would it return if the handler of the event didn't call setSql()?
  3. Putting aside the event-handling scenario, what should the SchemaDropTableEventArgs class do if it was instantiated and then got its setSql() invoked? Maybe throw a logical exception?
  4. Then, should it throw this exception unconditionally or only if it got a call to preventDefault() first?

Problems with the API design and implementation

What this API does, most likely could be solved just by extending the platform class and overriding the corresponding methods. Besides this awfully implicitly stateful logic, a few more concerns about DDL events:

  1. It looks like it's an overengineered solution to what could be easily solved by extending the corresponding platform methods.
  2. Unlike extending the platform, events don't allow invoking the default implementation (the method of the parent class).
  3. Unlike class extension, events don't provide access to the original logic like a call to the parent method would.
  4. The DDL events API is incomplete (e.g. one can override DROP TABLE but cannot override DROP FOREIGN KEY).
  5. In event-driven systems and DDD, an event is an immutable object representing a fact that happened in the past. Which fact does an onSchemaDropTable event represent? Let's assume it's a fact that somebody intended to drop a table. Why is it mutable? Why does it carry any information about processing the event (prevent default) and even implementation of that processing (SQL)?
  6. For 800 lines of production code, there are three unit tests with mocks and not a single integration test which would demonstrate how this API can be used and if it can be used at all.
  7. The documentation shows only how to register handlers but not what they should to.

Problems with the EventManager API as such

This API is unsafe from the standpoint of the types:

  1. There's no way to enforce the fact that a given event (onSomething) is dispatched only with a specific type of arguments (SomethingArgs).
  2. There's no way to guarantee that the given listener supports handling the given type of event. The dispatcher calls the method corresponding to the event name dynamically:
    $listener->$eventName($eventArgs)

The mistakes in the code that implements the handling of events will lead to a type error at runtime.

Types of events

  1. Connection and transaction events:
    public const postConnect = 'postConnect';

    dbal/src/Events.php

    Lines 33 to 35 in a85d913

    public const onTransactionBegin = 'onTransactionBegin';
    public const onTransactionCommit = 'onTransactionCommit';
    public const onTransactionRollBack = 'onTransactionRollBack';
    The logic implemented on top of these events can be implemented using driver middlewares.
  2. Schema manipulation events:

    dbal/src/Events.php

    Lines 23 to 30 in a85d913

    public const onSchemaCreateTable = 'onSchemaCreateTable';
    public const onSchemaCreateTableColumn = 'onSchemaCreateTableColumn';
    public const onSchemaDropTable = 'onSchemaDropTable';
    public const onSchemaAlterTable = 'onSchemaAlterTable';
    public const onSchemaAlterTableAddColumn = 'onSchemaAlterTableAddColumn';
    public const onSchemaAlterTableRemoveColumn = 'onSchemaAlterTableRemoveColumn';
    public const onSchemaAlterTableChangeColumn = 'onSchemaAlterTableChangeColumn';
    public const onSchemaAlterTableRenameColumn = 'onSchemaAlterTableRenameColumn';
    Depending on the use case, it should be advised to either extend the corresponding platform method, or modify the arguments being passed to the method before calling it.
  3. Schema introspection events:

    dbal/src/Events.php

    Lines 31 to 32 in a85d913

    public const onSchemaColumnDefinition = 'onSchemaColumnDefinition';
    public const onSchemaIndexDefinition = 'onSchemaIndexDefinition';
    This is the hardest part: we allow users to hook into the very internals of the schema introspection, e.g. the API consumer is exposed to the SQL results representing raw introspected data. By definition, this API is very brittle since the raw results is an implementation detail that can and will change. The fact that we allow this extension complicates the refactoring of the Schema Manager and Platform API.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions