Skip to content

feat(entity): Provide a new API for entities - #56199

Open
CarlSchwan wants to merge 10 commits into
masterfrom
carl/attributes-entity-2
Open

feat(entity): Provide a new API for entities#56199
CarlSchwan wants to merge 10 commits into
masterfrom
carl/attributes-entity-2

Conversation

@CarlSchwan

@CarlSchwan CarlSchwan commented Nov 4, 2025

Copy link
Copy Markdown
Member

Entity are now simple data object with attributes for the database mapping. They are manipulated via a Repository which reads the attributes to insert, update, delete and queries the PDO from the database.

Using attributes makes it easier to extends in the future with relations (ManyToMany, OneToMany, OneToOne, ...).

The design of the Repository is based on a mix between the Doctrine ORM repository while keeping some methods from the QBMapper for easier porting.

Example of a entity

#[Entity(name: 'twofactor_backupcodes')]
final class BackupCode {
	#[Id(generatorClass: IGenerator::class)]
	#[Column(name: 'id', type: Types::STRING, length: 64, nullable: false)]
	public ?string $id = null;

	#[Column(name: 'user_id', type: Types::STRING, length: 64, nullable: false)]
	public string $userId;

	#[Column(name: 'code', type: Types::STRING, length: 128, nullable: false)]
	public string $code;

	#[Column(name: 'used', type: Types::SMALLINT, nullable: false, default: 0)]
	public int $used = 0;
}

Supported

  • OneToOne relationship
  • ManyToOne relationship
  • Autoincremented ID
  • Snowflake ID
  • Basic queries
  • Same feature coverage as the old Entities so it's a drop-in replacement

Missing but for later

Checklist

@blizzz

blizzz commented Nov 5, 2025

Copy link
Copy Markdown
Member

Would have preferred the new API and the reference implementation in backup codes and Tagging to be two different commits. But cool.

What do you think of a CacheEnabled repository next to it (that wraps the Repository and just has a runtime cache)? Have a mapper with such functionality in tables, and saw it on at least another app as well, might be worth to have something like this implemented once properly?

@CarlSchwan

Copy link
Copy Markdown
Member Author

Would have preferred the new API and the reference implementation in backup codes and Tagging to be two different commits. But cool.

Currently it's more of a prototype, I'll split that in multiple commits.

What do you think of a CacheEnabled repository next to it (that wraps the Repository and just has a runtime cache)? Have a mapper with such functionality in tables, and saw it on at least another app as well, might be worth to have something like this implemented once properly?

Yes that could make sense. That I also saw in multiple apps is the relation stuff: https://github.com/nextcloud/deck/blob/main/lib/Db/RelationalEntity.php which could be quite handy

Comment thread lib/public/AppFramework/Db/Attribute/Column.php Outdated
Comment thread lib/public/AppFramework/Db/Repository.php Outdated
Comment thread lib/public/AppFramework/Db/Repository.php Outdated

@come-nc come-nc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks promising.

Comment thread apps/twofactor_backupcodes/lib/Db/BackupCode.php Outdated
Comment thread lib/private/Tags.php Outdated
Comment thread lib/public/AppFramework/Db/Repository.php Outdated
Comment thread lib/public/AppFramework/Db/Repository.php Outdated
@CarlSchwan
CarlSchwan force-pushed the carl/attributes-entity-2 branch from 27d1fd8 to 29a9903 Compare June 15, 2026 15:27
@CarlSchwan
CarlSchwan force-pushed the carl/attributes-entity-2 branch from 29a9903 to 0ac62ed Compare July 21, 2026 11:52
@CarlSchwan
CarlSchwan force-pushed the carl/attributes-entity-2 branch 2 times, most recently from f362314 to 68c1790 Compare July 21, 2026 14:12
@CarlSchwan CarlSchwan added 3. to review Waiting for reviews feature: database Database related DB labels Jul 21, 2026
@CarlSchwan
CarlSchwan marked this pull request as ready for review July 29, 2026 11:14
@CarlSchwan
CarlSchwan requested review from a team, ChristophWurst and miaulalala as code owners July 29, 2026 11:14
@CarlSchwan
CarlSchwan requested review from come-nc, leftybournes, nfebe and salmart-dev and removed request for a team July 29, 2026 11:14
Comment thread lib/public/AppFramework/ORM/Attribute/Entity.php Outdated
protected readonly IDBConnection $connection,
protected readonly string $entityClass,
) {
$this->entityManager = Server::get(EntityManager::class);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document why no DI (loop I suppose?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly because EntityManager is a private API (for now at least) and so it makes it possible to create a Repository without depending on it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, because child classes call the parent constructor so it needs public API only in its parameters.
If we put the entity class into a const that child classes override or something like that we may avoid the need for a constructor in most child classes, but I guess that won’t always work, some may want a constructor anyway. Not sure what the pretty solution is.

@CarlSchwan CarlSchwan Aug 6, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now use a const and made the constructor internal

@come-nc come-nc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is the modified columns detection replaced?
I think the old API was keeping track of which values were modified and I did not see how it is done here, as properties are public and set directly.

@CarlSchwan

Copy link
Copy Markdown
Member Author

How is the modified columns detection replaced? I think the old API was keeping track of which values were modified and I did not see how it is done here, as properties are public and set directly.

There is no detection, all properties are updated all the time. IHMO this is negligible in term of performance/network compared to the effort required to track changes via something similar to Doctrine Unit of Work as there isn't super frequent updates and for high performance code, it's best to anyway write queries manually requesting only what is needed and writing only what changed.

See https://www.doctrine-project.org/projects/doctrine-orm/en/3.6/reference/unitofwork.html At the moment, since the public API is minimal, we could add support for that later on.

CarlSchwan and others added 3 commits August 6, 2026 10:48
Entity are now simple data object with attributes for the database
mapping. They are manipulated via a Repository which reads the
attributes to insert, update, delete and queries the PDO from the
database.

Using attributes makes it easier to extends in the future with relations
(ManyToMany, OneToMany, OneToOne, ...).

The design of the Repository is based on a mix between the Doctrine ORM
repository while keeping some methods from the QBMapper for easier
porting.

Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
And do a lot of refactoring

Signed-off-by: Carl Schwan <carlschwan@kde.org>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Assisted-by: ClaudeCode:claude-opus-4-8
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
This is a left over from the maps prototype

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
@CarlSchwan
CarlSchwan force-pushed the carl/attributes-entity-2 branch 2 times, most recently from 1fe06c7 to 5e9186a Compare August 6, 2026 10:41
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
@CarlSchwan
CarlSchwan force-pushed the carl/attributes-entity-2 branch from 5e9186a to 3584712 Compare August 6, 2026 11:05
Let's keep this for a later refactor

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
@CarlSchwan
CarlSchwan force-pushed the carl/attributes-entity-2 branch from 77140cf to 476c196 Compare August 6, 2026 12:42

@ChristophWurst ChristophWurst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick review

Good stuff 👏

use OCP\Snowflake\ISnowflakeGenerator;

/**
* Attribute for marking a column as a primary id.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a rare case, but do we want to allow composite primary keys? Example would be an n:m mapping table. It could just use the two foreign keys as primary key. If we enforce a single primary key column then it needs a third column.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, doctrine does support this: https://www.doctrine-project.org/projects/doctrine-orm/en/3.6/tutorials/composite-primary-keys.html#primitive-types-only

So let's see if I can drop the requirement of only one Id attribute

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews AI assisted feature: database Database related DB

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants