DEV Community

Cover image for Small Entity Schema now supports Symfony and Doctrine 3
sebk69
sebk69

Posted on

Small Entity Schema now supports Symfony and Doctrine 3

Until now, Small Entity Schema was mainly designed around entities using Small Swoole Entity Manager.

That was useful for my own ecosystem, but it also meant that the schema editor was tightly coupled to one ORM representation.

The latest evolution changes that.

Small Entity Schema can now understand Doctrine 3 entities used in Symfony applications.

And more importantly, a project can contain Doctrine entities and Small Swoole Entity Manager entities at the same time.

What is Small Entity Schema?

Small Entity Schema is a desktop application for visually exploring and editing PHP entity models.

Instead of navigating through dozens of PHP classes to understand a data model, the application builds a graphical representation of the entities and their relations.

It can be used to:

  • visualize entities;
  • inspect their properties;
  • understand relationships;
  • reorganize the schema visually;
  • edit entity definitions;
  • regenerate the corresponding PHP source code.

The goal is not to replace an ORM.

It is to provide a visual layer above the ORM.


Doctrine 3 entity detection

The importer can now detect Doctrine entities such as:

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'product')]
class Product
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 255)]
    private string $name;
}
Enter fullscreen mode Exit fullscreen mode

Both the alias syntax:

use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
Enter fullscreen mode Exit fullscreen mode

and direct imports are interpreted.

The importer currently understands the main Doctrine mapping attributes used to describe an entity model:

Entity
Table
Id
Column

ManyToOne
OneToOne
OneToMany
ManyToMany

JoinColumn
Enter fullscreen mode Exit fullscreen mode

Doctrine DBAL type constants are also supported.

For example:

use Doctrine\DBAL\Types\Types;

#[ORM\Column(type: Types::STRING)]
private string $title;
Enter fullscreen mode Exit fullscreen mode

is interpreted as a string property by the schema editor.


Symfony Validator attributes

Doctrine support alone would not be enough for a Symfony application.

Entity properties very often contain Symfony Validator constraints too.

For example:

use Symfony\Component\Validator\Constraints as Assert;

#[Assert\NotBlank]
#[Assert\Length(min: 2, max: 100)]
#[ORM\Column(length: 100)]
private string $name;
Enter fullscreen mode Exit fullscreen mode

Small Entity Schema now imports these attributes as well.

Several common constraints are interpreted, including:

NotBlank
NotNull

Email
Url

Uuid
Ulid

Positive
PositiveOrZero

Negative
NegativeOrZero

GreaterThan
GreaterThanOrEqual
LessThan
LessThanOrEqual

Regex
Choice
Range
Length
Enter fullscreen mode Exit fullscreen mode

The original Symfony constraint is retained by the schema representation.

This means that when the entity is exported again as Doctrine, the Symfony validation metadata can also be regenerated.


Mixing Doctrine and Small Swoole entities

This was one of the most important architectural changes.

The schema model now stores the origin of each entity.

An entity can currently be identified as:

doctrine
Enter fullscreen mode Exit fullscreen mode

or:

swoole
Enter fullscreen mode Exit fullscreen mode

Consider a project containing:

src/Entity/User.php       -> Doctrine
src/Entity/Post.php       -> Doctrine
src/Entity/Product.php    -> Small Swoole Entity Manager
Enter fullscreen mode Exit fullscreen mode

Small Entity Schema can import all three into the same diagram.

The visual representation is therefore no longer tied to a specific ORM.

This is an important step toward treating the application as a more generic PHP entity schema editor.


Exporting back to the original ORM

By default, Small Entity Schema remembers the entity environment detected during import.

So:

Doctrine entity
    ↓
Small Entity Schema
    ↓
Doctrine entity
Enter fullscreen mode Exit fullscreen mode

and:

Small Swoole entity
    ↓
Small Entity Schema
    ↓
Small Swoole entity
Enter fullscreen mode Exit fullscreen mode

This behavior is called the automatic export mode.

It makes it possible to edit mixed projects without converting every entity to the same ORM.


Forcing the export format

Sometimes conversion is exactly what you want.

A new option in the project menu allows the export strategy to be changed.

Three modes are available:

Automatic
Force Small Swoole
Force Doctrine 3
Enter fullscreen mode Exit fullscreen mode

With:

Automatic
Enter fullscreen mode Exit fullscreen mode

each entity keeps its original environment.

With:

Force Doctrine 3
Enter fullscreen mode Exit fullscreen mode

the schema is generated using Doctrine attributes.

And with:

Force Small Swoole
Enter fullscreen mode Exit fullscreen mode

entities are generated using Small Swoole Entity Manager attributes.

This also opens an interesting possibility:

using Small Entity Schema as an ORM migration assistant.

It is not yet a full automatic ORM migration tool, but the underlying architecture is now there.


Tested against the Symfony Demo application

Synthetic unit tests are useful, but ORM compatibility needs to be tested against real code.

For that reason, I also tested the importer using the official Symfony Demo application.

The project contains real-world Doctrine patterns such as:

#[ORM\Entity]
#[ORM\Table(...)]
Enter fullscreen mode Exit fullscreen mode

Doctrine DBAL types:

Types::INTEGER
Types::STRING
Enter fullscreen mode Exit fullscreen mode

relations:

#[ORM\ManyToOne]
#[ORM\OneToMany]
#[ORM\ManyToMany]
Enter fullscreen mode Exit fullscreen mode

Symfony Validator constraints:

#[Assert\NotBlank]
#[Assert\Length(...)]
#[Assert\Email]
Enter fullscreen mode Exit fullscreen mode

and modern PHP patterns such as:

private readonly string $name;
Enter fullscreen mode Exit fullscreen mode

Small Entity Schema successfully imports the Symfony Demo entities into its visual model.

This test was particularly useful because it revealed several edge cases that simple test entities did not expose.

For example:

#[ORM\JoinColumn(nullable: false)]
Enter fullscreen mode Exit fullscreen mode

must obviously not be interpreted as if false were the column name.

The same testing also exposed cases involving:

\DateTimeImmutable
Enter fullscreen mode Exit fullscreen mode

and readonly properties during source regeneration.


Desktop support matters too

One particularly interesting bug appeared during the Symfony Demo test.

The Doctrine importer itself worked correctly.

But the desktop application displayed:

0 entities
Enter fullscreen mode Exit fullscreen mode

The reason was architectural.

The Electron bridge was still calling the old entity importer directly instead of the new compatibility layer.

So the backend could understand Doctrine, while the desktop application could not actually access that functionality.

The desktop bridge now uses the same compatibility layer for both import and export.

This is exactly the kind of issue that only appears when testing the complete application instead of isolated services.


Current test coverage

The compatibility work is covered by backend and frontend regression tests.

The current test suite includes checks for:

  • mixed Doctrine/Swoole projects;
  • Doctrine entity detection;
  • Symfony Validator attributes;
  • Doctrine column type inference;
  • Doctrine relations;
  • DateTimeImmutable;
  • readonly PHP properties;
  • desktop bridge import/export;
  • automatic export mode;
  • forced Doctrine export;
  • forced Small Swoole export.

The implementation is also checked with PHPStan.


A note about Doctrine round-tripping

There is an important distinction between:

supporting a Doctrine entity model

and:

reproducing every Doctrine attribute byte-for-byte.

The first level is now implemented.

The second is still evolving.

Doctrine supports a very large amount of mapping metadata:

GeneratedValue
indexes
unique constraints
precision / scale
custom column options
inheritance mapping
discriminator maps
association options
cascade configuration
orphanRemoval
ordering
fetch strategies
...
Enter fullscreen mode Exit fullscreen mode

Some of these attributes are not yet represented explicitly by Small Entity Schema.

The next step is therefore to retain more Doctrine-specific metadata while keeping the internal schema independent from Doctrine itself.

The objective is not to turn the internal model into a copy of Doctrine metadata.

The challenge is to find the right abstraction.


Toward an ORM-independent schema model

Doctrine compatibility required a useful change in the architecture.

Previously, the conceptual model was roughly:

Small Swoole Entity
        ↓
Schema
        ↓
Small Swoole Entity
Enter fullscreen mode Exit fullscreen mode

It is now closer to:

              ┌─ Doctrine
              │
PHP Entity ───┼─ Small Swoole
              │
              └─ future adapters
                   ↓
              Schema Model
                   ↓
              ORM Writer
Enter fullscreen mode Exit fullscreen mode

That separation is much more interesting.

Doctrine is the first major external ORM supported by this architecture, but it does not have to be the last one.

In the future, adapters could potentially target other persistence models without changing the visual editor itself.


What's next?

The next Doctrine-related improvements will focus on richer metadata preservation, especially:

  • exact relation types such as OneToOne versus ManyToOne;
  • ManyToMany metadata;
  • GeneratedValue;
  • column length, precision and scale;
  • unique constraints and indexes;
  • nullability;
  • cascade configuration;
  • additional Symfony attributes.

The long-term objective is straightforward:

Import an existing PHP domain model, visually edit it, and safely regenerate it without caring which supported ORM originally produced it.

Doctrine 3 compatibility is a significant step toward that goal.


Small Entity Schema is open source:

https://git.small-project.dev/lib/small-entity-schema

Top comments (2)

Collapse
 
topstar_ai profile image
Luis Cruz

It's impressive to see how Small Entity Schema can now handle both Doctrine and Small Swoole entities seamlessly, allowing developers to visualize and manage mixed environments more effectively. The incorporation of Symfony Validator attributes adds significant value, as it ensures that validation logic is preserved during export, which is crucial for maintaining data integrity. One area for potential improvement could be providing more customization options for the visual representation, enabling teams to tailor the editor to their specific workflows. If you're looking for additional development support in enhancing the schema editor or its integration features, I'd be glad to discuss a paid collaboration. What challenges have you encountered while ensuring compatibility between the different ORM systems?

Collapse
 
sebk69 profile image
sebk69

Thanks for your feedback.

One of the main goals behind Small Entity Schema is precisely to improve interoperability between different ecosystems rather than enforcing a single model.

Doctrine / Small Swoole Entity Manager compatibility follows that approach: the schema acts as an intermediate representation between multiple ORMs while preserving as much of their specific metadata as possible.

A significant part of this work also relies on Small Class Manipulator, which makes it relatively easy to interact with PHP classes both as structured representations and directly at code level. This greatly simplifies entity analysis, transformation, and generation without having to build a completely separate implementation for each framework or ORM.

The main challenge is therefore not simply mapping one ORM property to another, but defining a sufficiently neutral representation that allows conversions without losing ORM-specific information. Symfony Validator attributes are a good example: they are not strictly part of Doctrine mapping, but they are part of the functional definition of an entity, so they need to survive an import/export cycle.

Regarding paid collaboration, my open source projects are intentionally non-profit. I mainly build them to experiment, share reusable components across my own projects, and make those components available to the community. As a result, they do not generate a budget that I can redistribute through paid contributions.

Technical contributions, architectural feedback, real-world use cases, and pull requests are of course very welcome.