Skip to content

Conversation

@lohanidamodar
Copy link
Contributor

@lohanidamodar lohanidamodar commented Nov 3, 2025

  • Support typed documents using setDocumentType
  • The custom types must extend Document

Summary by CodeRabbit

  • New Features

    • Added support for mapping custom document classes to specific collections, enabling type-safe domain-driven operations across create, retrieve, and find workflows.
  • Documentation

    • Added "Custom Document Types" section to README with configuration examples, usage patterns, and benefits overview including backwards compatibility information.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Walkthrough

This pull request introduces a custom document type mapping feature to the database layer. It allows developers to register custom Document subclasses for specific collections, enabling operations like create, get, and find to return instances of the mapped class instead of the base Document class. The feature includes public API methods for managing mappings and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added "Custom Document Types" section describing the mapping mechanism, benefits (domain-driven design, type safety, code organization), and usage examples.
Core Implementation
src/Database/Database.php
Added protected $documentTypes property for storing collection-to-class mappings; four public API methods (setDocumentType, getDocumentType, clearDocumentType, clearAllDocumentTypes) for managing mappings; protected createDocumentInstance helper to instantiate mapped document classes; integrated mapping resolution across create, retrieve, find, and update operations.
Test Infrastructure
tests/e2e/Adapter/Base.php
Added CustomDocumentTypeTests trait import and use statement to enable new test scope in Base test class.
Test Coverage
tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php
Added new test file with TestUser and TestPost document subclasses and comprehensive CustomDocumentTypeTests trait covering mapping registration, validation, chaining, instance type preservation across CRUD operations, error handling, and unmapped collection fallback behavior.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Database
    participant Mapping as Document Type<br/>Mapping
    participant Doc as Document/<br/>Custom Class

    User->>Database: setDocumentType('users', TestUser::class)
    Database->>Mapping: Store mapping
    
    User->>Database: createDocument('users', {email: 'test@example.com'})
    Database->>Mapping: Check if 'users' mapped
    Mapping-->>Database: Return TestUser::class
    Database->>Doc: Instantiate TestUser
    Doc-->>Database: TestUser instance
    Database-->>User: Return TestUser instance
    
    User->>Database: getDocument('users', docId)
    Database->>Mapping: Check if 'users' mapped
    Mapping-->>Database: Return TestUser::class
    Database->>Doc: Instantiate TestUser from data
    Doc-->>Database: TestUser instance
    Database-->>User: Return TestUser instance
    
    Note over User,Database: Unmapped collection<br/>falls back to Document
    User->>Database: createDocument('posts', {title: 'Test'})
    Database->>Mapping: Check if 'posts' mapped
    Mapping-->>Database: No mapping found
    Database->>Doc: Instantiate Document (default)
    Doc-->>Database: Document instance
    Database-->>User: Return Document instance
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Areas requiring attention:
    • Integration points in Database.php where createDocumentInstance is called across multiple CRUD operations (create, get, find, update paths) to verify all document instantiation paths respect the custom type mapping
    • Test coverage completeness in CustomDocumentTypeTests.php, particularly error handling for invalid/non-Document classes and chaining behavior validation
    • Backwards compatibility verification that unmapped collections continue to use the default Document class

Possibly related PRs

Suggested reviewers

  • abnegate
  • fogelito

Poem

A rabbit hops through types with glee, 🐰
Custom documents roam so free,
From TestUser to TestPost they leap,
Each collection's secrets to keep,
Type-safe treasures, organized neat! ✨

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat: Custom document types' directly and clearly summarizes the main feature addition - support for custom document types across the database layer.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-external-type

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (5)
src/Database/Database.php (4)

1195-1216: Harden registration: verify constructor compatibility of custom class

Guard against subclasses that don’t accept the standard Document constructor. Add a reflection check (and keep throwing DatabaseException on failure).

     public function setDocumentType(string $collection, string $className): static
     {
         if (!\class_exists($className)) {
             throw new DatabaseException("Class {$className} does not exist");
         }
 
         if (!\is_subclass_of($className, Document::class)) {
             throw new DatabaseException("Class {$className} must extend " . Document::class);
         }
 
+        // Ensure constructor is compatible with Document::__construct(array $input = [])
+        try {
+            $rc = new \ReflectionClass($className);
+            $ctor = $rc->getConstructor();
+            if ($ctor && $ctor->getNumberOfRequiredParameters() > 1) {
+                throw new DatabaseException("Class {$className} must have a compatible constructor (array \$input = []).");
+            }
+            if ($ctor && $ctor->getNumberOfRequiredParameters() === 1) {
+                $p = $ctor->getParameters()[0];
+                $hasArrayType = !$p->hasType() || (string)$p->getType() === 'array';
+                if (!$hasArrayType) {
+                    throw new DatabaseException("Class {$className} first constructor parameter must be of type array.");
+                }
+            }
+        } catch (\ReflectionException $e) {
+            throw new DatabaseException("Failed validating {$className} constructor: {$e->getMessage()}", 0, $e);
+        }
+
         $this->documentTypes[$collection] = $className;
 
         return $this;
     }

1254-1266: Wrap instantiation errors with context

If a custom class throws in its constructor, wrapping helps diagnostics.

-        return new $className($data);
+        try {
+            return new $className($data);
+        } catch (\Throwable $e) {
+            throw new DatabaseException("Failed to instantiate {$className} for collection '{$collection}': {$e->getMessage()}", 0, $e);
+        }

3774-3782: Type mapping usage is correct; consider a tiny helper to DRY

Current conversions are right (post-casting, pre-decode checks included). To avoid duplication, consider a small helper that maps if configured.

// Example:
protected function mapIfConfigured(Document $collection, Document $doc): Document {
    return isset($this->documentTypes[$collection->getId()])
        ? $this->createDocumentInstance($collection->getId(), $doc->getArrayCopy())
        : $doc;
}

Then replace inline blocks with $document = $this->mapIfConfigured($collection, $document);

Also applies to: 3797-3807, 3815-3816


4550-4552: Ensure mapped types are returned from batch & upsert paths too

createDocuments() and upsertDocumentsWithIncrease() currently emit base Document instances to callbacks; upsertDocument() may also return base instances when changes occur. Apply the same mapping before invoking callbacks/returning to keep behavior consistent across APIs.

*** In createDocuments(), inside: foreach ($batch as $document) { ... } before onNext ***
-                try {
-                    $onNext && $onNext($document);
+                // Convert to custom document type if mapped
+                if (isset($this->documentTypes[$collection->getId()])) {
+                    $document = $this->createDocumentInstance($collection->getId(), $document->getArrayCopy());
+                }
+                try {
+                    $onNext && $onNext($document);
                 } catch (\Throwable $e) {

*** In upsertDocumentsWithIncrease(), inside: foreach ($batch as $index => $doc) { ... } before onNext ***
-                $doc = $this->decode($collection, $doc);
+                $doc = $this->decode($collection, $doc);
+                if (isset($this->documentTypes[$collection->getId()])) {
+                    $doc = $this->createDocumentInstance($collection->getId(), $doc->getArrayCopy());
+                }

*** Optionally, in upsertDocument(), after computing $result ***
-        return $result;
+        if (isset($this->documentTypes[$collection])) {
+            $result = $this->createDocumentInstance($collection, $result->getArrayCopy());
+        }
+        return $result;

Also consider mapping in increaseDocumentAttribute()/decreaseDocumentAttribute() before returning for full parity.

Also applies to: 5238-5242, 7254-7258

tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php (1)

200-212: Drop unused locals in setup

$post1 and $post2 are never referenced after creation, so PHPMD flags them. You can safely remove the assignments (or assert on them) to keep the test lean and static-analysis clean.

-        $post1 = $database->createDocument('customPosts', new Document([
+        $database->createDocument('customPosts', new Document([
             '$id' => ID::unique(),
             'title' => 'First Post',
             'content' => 'This is the first post',
             '$permissions' => [Permission::read(Role::any())],
         ]));
 
-        $post2 = $database->createDocument('customPosts', new Document([
+        $database->createDocument('customPosts', new Document([
             '$id' => ID::unique(),
             'title' => 'Second Post',
             'content' => 'This is the second post',
             '$permissions' => [Permission::read(Role::any())],
         ]));
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b6541a9 and 36fab3a.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • src/Database/Database.php (11 hunks)
  • tests/e2e/Adapter/Base.php (2 hunks)
  • tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-25T10:26:49.291Z
Learnt from: ArnabChatterjee20k
Repo: utopia-php/database PR: 715
File: tests/e2e/Adapter/Scopes/DocumentTests.php:6038-6066
Timestamp: 2025-09-25T10:26:49.291Z
Learning: Database::addFilter() is a static method that stores filters in a global static map (self::$filters). This means filters persist across all Database instances and test runs, potentially causing test interdependence issues when the same filter name is used by multiple tests.

Applied to files:

  • README.md
📚 Learning: 2025-10-03T02:04:17.803Z
Learnt from: abnegate
Repo: utopia-php/database PR: 721
File: tests/e2e/Adapter/Scopes/DocumentTests.php:6418-6439
Timestamp: 2025-10-03T02:04:17.803Z
Learning: In tests/e2e/Adapter/Scopes/DocumentTests::testSchemalessDocumentInvalidInteralAttributeValidation (PHP), when the adapter reports getSupportForAttributes() === false (schemaless), the test should not expect exceptions from createDocuments for “invalid” internal attributes; remove try/catch and ensure the test passes without exceptions, keeping at least one assertion.

Applied to files:

  • tests/e2e/Adapter/Base.php
  • tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php
🧬 Code graph analysis (2)
tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php (1)
src/Database/Database.php (10)
  • Database (37-8604)
  • setDocumentType (1203-1216)
  • getDocumentType (1224-1227)
  • clearDocumentType (1235-1240)
  • clearAllDocumentTypes (1247-1252)
  • create (1408-1427)
  • createDocument (4455-4557)
  • getDocument (3717-3846)
  • find (7124-7269)
  • updateDocument (5013-5246)
src/Database/Database.php (4)
src/Database/Document.php (5)
  • Document (12-470)
  • getId (63-66)
  • getRead (101-104)
  • getArrayCopy (423-458)
  • setAttribute (244-261)
src/Database/Validator/Structure.php (1)
  • isValid (211-245)
src/Database/Adapter/Mongo.php (1)
  • castingAfter (1194-1269)
src/Database/Adapter.php (1)
  • castingAfter (1376-1376)
🪛 GitHub Actions: CodeQL
tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php

[error] 76-76: Parameter #2 $className of method Utopia\Database\Database::setDocumentType() expects class-string<Utopia\Database\Document>, string given.


[error] 87-87: Parameter #2 $className of method Utopia\Database\Database::setDocumentType() expects class-string<Utopia\Database\Document>, string given.

src/Database/Database.php

[error] 3717-3717: Template type T of method Utopia\Database\Database::getDocument() is not referenced in a parameter.


[error] 4455-4455: Template type T of method Utopia\Database\Database::createDocument() is not referenced in a parameter.


[error] 5013-5013: Template type T of method Utopia\Database\Database::updateDocument() is not referenced in a parameter.


[error] 7124-7124: Template type T of method Utopia\Database\Database::find() is not referenced in a parameter.

🪛 PHPMD (2.15.0)
tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php

200-200: Avoid unused local variables such as '$post1'. (undefined)

(UnusedLocalVariable)


207-207: Avoid unused local variables such as '$post2'. (undefined)

(UnusedLocalVariable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Setup & Build Docker Image
🔇 Additional comments (8)
tests/e2e/Adapter/Base.php (1)

8-8: LGTM! Test coverage extended correctly.

The import and trait usage follow the established pattern in the file, correctly adding test coverage for the custom document types feature.

Also applies to: 26-26

README.md (3)

35-37: Clear and well-structured documentation.

The section introduces the custom document types feature effectively, positioning it within a domain-driven design context.


39-63: Excellent code example demonstrating the feature.

The example clearly illustrates the complete workflow: defining a custom document class, registering it, and using domain-specific methods. The code is syntactically correct and demonstrates proper usage patterns.


65-69: Well-articulated benefits.

The benefits section effectively communicates the value proposition of custom document types, covering developer experience, code quality, and compatibility.

src/Database/Database.php (4)

417-422: LGTM: internal map for custom document types

Property definition and typing look correct.


1218-1227: LGTM: accessor for mapped class

Simple and correct.


1229-1240: LGTM: clear mapping for a collection


1242-1252: LGTM: clear all mappings

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php (1)

73-82: Fix CodeQL type violation with dynamic dispatch.

The string literal 'NonExistentClass' violates the class-string<Document> parameter type in setDocumentType, causing a CodeQL pipeline failure. Use dynamic dispatch to test the runtime exception path while satisfying the static type checker.

Apply this diff:

     public function testSetDocumentTypeWithInvalidClass(): void
     {
         /** @var Database $database */
         $database = static::getDatabase();
 
         $this->expectException(DatabaseException::class);
         $this->expectExceptionMessage('does not exist');
 
-        // @phpstan-ignore-next-line - Testing with invalid class name
-        $database->setDocumentType('users', 'NonExistentClass');
+        /** @var mixed $invalidClass */
+        $invalidClass = 'NonExistentClass';
+        \call_user_func([$database, 'setDocumentType'], 'users', $invalidClass);
     }
🧹 Nitpick comments (8)
src/Database/Database.php (6)

1227-1249: Guard against incompatible constructors in custom Document subclasses

If a mapped subclass defines a non-compatible constructor, new $className($data) will fatal. Add a reflection check to fail fast with a DatabaseException.

     public function setDocumentType(string $collection, string $className): static
     {
         if (!\class_exists($className)) {
             throw new DatabaseException("Class {$className} does not exist");
         }

         if (!\is_subclass_of($className, Document::class)) {
             throw new DatabaseException("Class {$className} must extend " . Document::class);
         }
+        // Validate constructor compatibility: must accept array (or mixed) first param, or no required params
+        try {
+            $rc = new \ReflectionClass($className);
+            $ctor = $rc->getConstructor();
+            if ($ctor && $ctor->getNumberOfRequiredParameters() > 0) {
+                $p = $ctor->getParameters()[0];
+                $t = $p->getType();
+                $acceptsArray = true;
+                if ($t instanceof \ReflectionNamedType) {
+                    $acceptsArray = \in_array($t->getName(), ['array', 'mixed'], true);
+                } elseif ($t instanceof \ReflectionUnionType) {
+                    $acceptsArray = \count(\array_filter($t->getTypes(), fn($n) => $n->getName()==='array'||$n->getName()==='mixed')) > 0;
+                }
+                if (!$acceptsArray) {
+                    throw new DatabaseException("Custom Document constructor must accept an array (or mixed) as first parameter.");
+                }
+            }
+        } catch (\ReflectionException $e) {
+            throw new DatabaseException($e->getMessage(), $e->getCode(), $e);
+        }

         $this->documentTypes[$collection] = $className;

         return $this;
     }

Optional: consider rejecting mapping for _metadata to avoid surprising typing there.


1293-1299: Harden factory with error wrapping

Wrap instantiation to surface a DatabaseException instead of a fatal if subclass construction fails.

     protected function createDocumentInstance(string $collection, array $data): Document
     {
         $className = $this->documentTypes[$collection] ?? Document::class;

-        return new $className($data);
+        try {
+            return new $className($data);
+        } catch (\Throwable $e) {
+            throw new DatabaseException('Failed to instantiate custom document: ' . $e->getMessage(), (int)$e->getCode(), $e);
+        }
     }

4688-4699: Surface mapped type to createDocuments onNext callback

onNext currently receives base Document. Convert to mapped type for consistency with single create/get.

             foreach ($batch as $document) {
                 $document = $this->adapter->castingAfter($collection, $document);
                 $document = $this->casting($collection, $document);
                 $document = $this->decode($collection, $document);
+                if (isset($this->documentTypes[$collection->getId()])) {
+                    $document = $this->createDocumentInstance($collection->getId(), $document->getArrayCopy());
+                }

                 try {
                     $onNext && $onNext($document);
                 } catch (\Throwable $e) {

5492-5501: Also map types for updateDocuments callbacks (new and old)

Ensure both $doc and $old[$index] passed to onNext are mapped when a type is registered.

             foreach ($batch as $index => $doc) {
                 $doc = $this->adapter->castingAfter($collection, $doc);
                 $doc->removeAttribute('$skipPermissionsUpdate');
                 $this->purgeCachedDocument($collection->getId(), $doc->getId());
                 $doc = $this->decode($collection, $doc);
+                if (isset($this->documentTypes[$collection->getId()])) {
+                    $doc = $this->createDocumentInstance($collection->getId(), $doc->getArrayCopy());
+                }
                 try {
-                    $onNext && $onNext($doc, $old[$index]);
+                    $oldDoc = $old[$index];
+                    if (!$oldDoc->isEmpty()) {
+                        $oldDoc = $this->adapter->castingAfter($collection, $oldDoc);
+                        $oldDoc = $this->decode($collection, $oldDoc);
+                        if (isset($this->documentTypes[$collection->getId()])) {
+                            $oldDoc = $this->createDocumentInstance($collection->getId(), $oldDoc->getArrayCopy());
+                        }
+                    }
+                    $onNext && $onNext($doc, $oldDoc);
                 } catch (Throwable $th) {

6235-6261: Map types for upsert onNext callback

Return mapped instances to callbacks for parity with CRUD.

             foreach ($batch as $index => $doc) {
                 $doc = $this->adapter->castingAfter($collection, $doc);
                 if (!$hasOperators) {
                     $doc = $this->decode($collection, $doc);
                 }
+                if (isset($this->documentTypes[$collection->getId()])) {
+                    $doc = $this->createDocumentInstance($collection->getId(), $doc->getArrayCopy());
+                }
                 if ($this->getSharedTables() && $this->getTenantPerDocument()) {
                     $this->withTenant($doc->getTenant(), function () use ($collection, $doc) {
                         $this->purgeCachedDocument($collection->getId(), $doc->getId());
                     });
                 } else {
                     $this->purgeCachedDocument($collection->getId(), $doc->getId());
                 }
                 $old = $chunk[$index]->getOld();

                 if (!$old->isEmpty()) {
                     $old = $this->adapter->castingAfter($collection, $old);
                     $old = $this->decode($collection, $old);
+                    if (isset($this->documentTypes[$collection->getId()])) {
+                        $old = $this->createDocumentInstance($collection->getId(), $old->getArrayCopy());
+                    }
                 }

                 try {
                     $onNext && $onNext($doc, $old->isEmpty() ? null : $old);
                 } catch (\Throwable $th) {

6367-6370: Return mapped type from increase/decrease helpers

These helpers currently return base Document. Convert before returning for consistency.

         $this->trigger(self::EVENT_DOCUMENT_INCREASE, $document);

-        return $document;
+        if (isset($this->documentTypes[$collection->getId()])) {
+            $document = $this->createDocumentInstance($collection->getId(), $document->getArrayCopy());
+        }
+        return $document;
         $this->trigger(self::EVENT_DOCUMENT_DECREASE, $document);

-        return $document;
+        if (isset($this->documentTypes[$collection->getId()])) {
+            $document = $this->createDocumentInstance($collection->getId(), $document->getArrayCopy());
+        }
+        return $document;

Also applies to: 6467-6470

tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php (2)

83-83: Fix formatting: add newline before function declaration.

The closing brace from the previous function and this function declaration should be separated by a blank line for proper code formatting.

Apply this diff:

         $database->setDocumentType('users', 'NonExistentClass');
-    }    public function testSetDocumentTypeWithNonDocumentClass(): void
+    }
+
+    public function testSetDocumentTypeWithNonDocumentClass(): void

190-235: LGTM! Consider simplifying variable assignments.

The test correctly validates find operations with custom document types. The static analysis warning about unused variables $post1 and $post2 is a false positive—these calls are necessary to populate the database. However, if you prefer to silence the warning, you can remove the variable assignments since the return values aren't used.

Optional simplification:

-        $post1 = $database->createDocument('customPosts', new Document([
+        $database->createDocument('customPosts', new Document([
             '$id' => ID::unique(),
             'title' => 'First Post',
             'content' => 'This is the first post',
             '$permissions' => [Permission::read(Role::any())],
         ]));
 
-        $post2 = $database->createDocument('customPosts', new Document([
+        $database->createDocument('customPosts', new Document([
             '$id' => ID::unique(),
             'title' => 'Second Post',
             'content' => 'This is the second post',
             '$permissions' => [Permission::read(Role::any())],
         ]));
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c34cb23 and 25173c1.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • src/Database/Database.php (7 hunks)
  • tests/e2e/Adapter/Base.php (2 hunks)
  • tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-25T10:26:49.291Z
Learnt from: ArnabChatterjee20k
Repo: utopia-php/database PR: 715
File: tests/e2e/Adapter/Scopes/DocumentTests.php:6038-6066
Timestamp: 2025-09-25T10:26:49.291Z
Learning: Database::addFilter() is a static method that stores filters in a global static map (self::$filters). This means filters persist across all Database instances and test runs, potentially causing test interdependence issues when the same filter name is used by multiple tests.

Applied to files:

  • README.md
📚 Learning: 2025-10-03T02:04:17.803Z
Learnt from: abnegate
Repo: utopia-php/database PR: 721
File: tests/e2e/Adapter/Scopes/DocumentTests.php:6418-6439
Timestamp: 2025-10-03T02:04:17.803Z
Learning: In tests/e2e/Adapter/Scopes/DocumentTests::testSchemalessDocumentInvalidInteralAttributeValidation (PHP), when the adapter reports getSupportForAttributes() === false (schemaless), the test should not expect exceptions from createDocuments for “invalid” internal attributes; remove try/catch and ensure the test passes without exceptions, keeping at least one assertion.

Applied to files:

  • tests/e2e/Adapter/Base.php
  • tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php
📚 Learning: 2025-10-16T09:37:33.531Z
Learnt from: fogelito
Repo: utopia-php/database PR: 733
File: src/Database/Adapter/MariaDB.php:1801-1806
Timestamp: 2025-10-16T09:37:33.531Z
Learning: In the MariaDB adapter (src/Database/Adapter/MariaDB.php), only duplicate `_uid` violations should throw `DuplicateException`. All other unique constraint violations, including `PRIMARY` key collisions on the internal `_id` field, should throw `UniqueException`. This is the intended design to distinguish between user-facing document duplicates and internal/user-defined unique constraint violations.

Applied to files:

  • tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php
🧬 Code graph analysis (2)
tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php (1)
src/Database/Database.php (14)
  • Database (38-8626)
  • setDocumentType (1235-1248)
  • getDocumentType (1256-1259)
  • clearDocumentType (1267-1272)
  • clearAllDocumentTypes (1279-1284)
  • createCollection (1527-1665)
  • create (1440-1459)
  • delete (1498-1512)
  • createAttribute (1895-1954)
  • createDocument (4485-4589)
  • getDocument (3748-3877)
  • deleteCollection (1826-1869)
  • find (7153-7298)
  • updateDocument (5045-5282)
src/Database/Database.php (5)
src/Database/Document.php (5)
  • Document (12-470)
  • getId (63-66)
  • getRead (101-104)
  • getArrayCopy (423-458)
  • setAttribute (244-261)
src/Database/Validator/Structure.php (1)
  • isValid (211-245)
src/Database/Validator/Authorization/Input.php (1)
  • Input (5-49)
src/Database/Adapter/Mongo.php (1)
  • castingAfter (1194-1269)
src/Database/Adapter.php (1)
  • castingAfter (1398-1398)
🪛 PHPMD (2.15.0)
tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php

208-208: Avoid unused local variables such as '$post1'. (undefined)

(UnusedLocalVariable)


215-215: Avoid unused local variables such as '$post2'. (undefined)

(UnusedLocalVariable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Adapter Tests (Schemaless/MongoDB)
  • GitHub Check: Adapter Tests (SharedTables/SQLite)
  • GitHub Check: Adapter Tests (SharedTables/Postgres)
  • GitHub Check: Adapter Tests (SharedTables/MongoDB)
  • GitHub Check: Adapter Tests (SharedTables/MariaDB)
  • GitHub Check: Adapter Tests (SharedTables/MySQL)
  • GitHub Check: Adapter Tests (Pool)
  • GitHub Check: Adapter Tests (MongoDB)
  • GitHub Check: Adapter Tests (SQLite)
  • GitHub Check: Adapter Tests (Postgres)
  • GitHub Check: Adapter Tests (MariaDB)
  • GitHub Check: Adapter Tests (Mirror)
  • GitHub Check: Adapter Tests (MySQL)
  • GitHub Check: Unit Test
🔇 Additional comments (14)
tests/e2e/Adapter/Base.php (1)

8-8: LGTM! Clean integration of the new test trait.

The addition of the CustomDocumentTypeTests trait follows the established pattern in this test base class, maintaining consistency with other test scopes.

Also applies to: 26-26

README.md (1)

35-70: Excellent documentation of the new feature!

The Custom Document Types section is well-written with:

  • Clear explanation of the feature and its purpose
  • Practical, easy-to-follow example code
  • Well-articulated benefits that highlight the value proposition
  • Clean formatting consistent with the rest of the README

The documentation effectively communicates both the "how" and the "why" of this feature.

src/Database/Database.php (6)

424-429: LGTM: mapping registry added

The dedicated per-collection type map is clear and scoped to the Database instance.


1250-1285: LGTM: simple, chainable management API

Get/clear/clearAll are straightforward and fluent.


3804-3813: Consistent typing applied in getDocument paths (cache, empty, post-cast, unauthorized)

Good use of the factory in all branches; ordering preserves decode/cast semantics and sets $collection after conversion.

Also applies to: 3828-3830, 3834-3838, 3846-3847


4581-4585: Create returns mapped type

Returning the mapped class after decode is correct and aligns with getDocument/find behavior.


5274-5278: Update returns mapped type

Post-decode conversion keeps return types consistent across CRUD.


7283-7287: Find yields mapped instances

Per-item conversion after decode is correct; preserves selection filtering.

tests/e2e/Adapter/Scopes/CustomDocumentTypeTests.php (6)

13-43: LGTM! Clean test document classes.

The TestUser and TestPost classes provide appropriate typed accessors for testing the custom document type feature. The implementations are straightforward and suitable for end-to-end testing.


47-61: LGTM! Proper cleanup included.

The test correctly verifies setting and retrieving document types, and includes cleanup to prevent test pollution.


124-142: LGTM! Fluent interface tested with cleanup.

The test properly validates method chaining and includes cleanup to prevent test pollution.


144-188: LGTM! Comprehensive integration test.

This test thoroughly validates the custom document type feature across create and get operations, verifying both type casting and custom method functionality. Proper cleanup is included.


237-284: LGTM! Update operation thoroughly tested.

The test validates that custom document types are preserved through update operations, including verification of custom methods with updated values. Proper cleanup is included.


286-312: LGTM! Default behavior validated.

This test correctly verifies that collections without custom type mappings continue to return standard Document instances, ensuring backward compatibility.

@lohanidamodar
Copy link
Contributor Author

closed in favor of
#756

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants