From afc5d8009507a297c8169be93aee1085e652eedc Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Tue, 4 Jun 2024 08:10:57 -0400 Subject: [PATCH] Migrate remaining implementation --- composer.json | 3 +- composer.lock | 6 +- src/Document.php | 15 ++++ src/Exists.php | 44 +++++++++++ src/Find.php | 78 +++++++++++++++++++ .../{JsonMapper.php => DocumentMapper.php} | 5 +- src/Patch.php | 44 +++++++++++ src/Query/RemoveFields.php | 70 +++++++++++++++++ src/RemoveFields.php | 46 +++++++++++ ...nMapperTest.php => DocumentMapperTest.php} | 14 ++-- 10 files changed, 313 insertions(+), 12 deletions(-) create mode 100644 src/Exists.php create mode 100644 src/Find.php rename src/Mapper/{JsonMapper.php => DocumentMapper.php} (91%) create mode 100644 src/Patch.php create mode 100644 src/Query/RemoveFields.php create mode 100644 src/RemoveFields.php rename tests/unit/Mapper/{JsonMapperTest.php => DocumentMapperTest.php} (77%) diff --git a/composer.json b/composer.json index 9781acb..a792038 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,8 @@ { "name": "bit-badger/pdo-document", "require": { - "netresearch/jsonmapper": "^4" + "netresearch/jsonmapper": "^4", + "ext-pdo": "*" }, "require-dev": { "phpunit/phpunit": "^11" diff --git a/composer.lock b/composer.lock index 56df984..50d47e2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "38b71eefd32cb528da22dd5908fb8d02", + "content-hash": "eada4b7eb6f976e0aaf0b54b92e2ca32", "packages": [ { "name": "netresearch/jsonmapper", @@ -1696,7 +1696,9 @@ "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, - "platform": [], + "platform": { + "ext-pdo": "*" + }, "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/src/Document.php b/src/Document.php index 663eed0..cb56cd7 100644 --- a/src/Document.php +++ b/src/Document.php @@ -34,4 +34,19 @@ class Document { Custom::nonQuery(Query::save($tableName), Parameters::json('@data', $document), $pdo); } + + /** + * Update (replace) an entire document by its ID + * + * @param string $tableName The table in which the document should be updated + * @param mixed $docId The ID of the document to be updated + * @param array|object $document The document to be updated + * @param PDO|null $pdo The database connection to use (optional; will obtain one if not provided) + * @throws DocumentException If any is encountered + */ + public static function update(string $tableName, mixed $docId, array|object $document, ?PDO $pdo = null): void + { + Custom::nonQuery(Query::update($tableName), + array_merge(Parameters::id($docId), Parameters::json('@data', $document)), $pdo); + } } diff --git a/src/Exists.php b/src/Exists.php new file mode 100644 index 0000000..5b4e6cd --- /dev/null +++ b/src/Exists.php @@ -0,0 +1,44 @@ + $className The name of the class to be retrieved + * @return DocumentList A list of all documents from the table + * @throws DocumentException If any is encountered + */ + public static function all(string $tableName, string $className): DocumentList + { + return Custom::list(Query::selectFromTable($tableName), [], new DocumentMapper($className)); + } + + /** + * Retrieve a document by its ID (returns false if not found) + * + * @template TDoc The type of document to be retrieved + * @param string $tableName The table from which the document should be retrieved + * @param mixed $docId The ID of the document to retrieve + * @param class-string $className The name of the class to be retrieved + * @return false|TDoc The document if it exists, false if not + * @throws DocumentException If any is encountered + */ + public static function byId(string $tableName, mixed $docId, string $className): mixed + { + return Custom::single(Query\Find::byId($tableName), Parameters::id($docId), new DocumentMapper($className)); + } + + /** + * Retrieve documents via a comparison on JSON fields + * + * @template TDoc The type of document to be retrieved + * @param string $tableName The table from which documents should be retrieved + * @param array|Field[] $fields The field comparison to match + * @param class-string $className The name of the class to be retrieved + * @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`) + * @return DocumentList A list of documents matching the given field comparison + * @throws DocumentException If any is encountered + */ + public static function byFields(string $tableName, array $fields, string $className, + string $conjunction = 'AND'): DocumentList + { + $namedFields = Parameters::nameFields($fields); + return Custom::list(Query\Find::byFields($tableName, $namedFields, $conjunction), + Parameters::addFields($namedFields, []), new DocumentMapper($className)); + } + + /** + * Retrieve documents via a comparison on JSON fields, returning only the first result + * + * @template TDoc The type of document to be retrieved + * @param string $tableName The table from which the document should be retrieved + * @param array|Field[] $fields The field comparison to match + * @param class-string $className The name of the class to be retrieved + * @param string $conjunction How to handle multiple conditions (optional; defaults to `AND`) + * @return false|TDoc The first document if any matches are found, false otherwise + * @throws DocumentException If any is encountered + */ + public static function firstByFields(string $tableName, array $fields, string $className, + string $conjunction = 'AND'): mixed + { + $namedFields = Parameters::nameFields($fields); + return Custom::single(Query\Find::byFields($tableName, $namedFields, $conjunction), + Parameters::addFields($namedFields, []), new DocumentMapper($className)); + } +} diff --git a/src/Mapper/JsonMapper.php b/src/Mapper/DocumentMapper.php similarity index 91% rename from src/Mapper/JsonMapper.php rename to src/Mapper/DocumentMapper.php index 10919b4..a4d20ac 100644 --- a/src/Mapper/JsonMapper.php +++ b/src/Mapper/DocumentMapper.php @@ -3,6 +3,7 @@ namespace BitBadger\PDODocument\Mapper; use BitBadger\PDODocument\DocumentException; +use JsonMapper; use JsonMapper_Exception; /** @@ -11,7 +12,7 @@ use JsonMapper_Exception; * @template TDoc The type of document returned by this mapper * @implements Mapper Provide a mapping from JSON */ -class JsonMapper implements Mapper +class DocumentMapper implements Mapper { /** * Constructor @@ -35,7 +36,7 @@ class JsonMapper implements Mapper if (is_null($json)) { throw new DocumentException("Could not map document for $this->className: " . json_last_error_msg()); } - return (new \JsonMapper())->map($json, $this->className); + return (new JsonMapper())->map($json, $this->className); } catch (JsonMapper_Exception $ex) { throw new DocumentException("Could not map document for $this->className", previous: $ex); } diff --git a/src/Patch.php b/src/Patch.php new file mode 100644 index 0000000..40afa7e --- /dev/null +++ b/src/Patch.php @@ -0,0 +1,44 @@ +assertEquals('data', $mapper->fieldName, 'Default field name should have been "data"'); } public function testConstructorSucceedsWithSpecifiedField(): void { - $mapper = new JsonMapper(Field::class, 'json'); + $mapper = new DocumentMapper(Field::class, 'json'); $this->assertEquals('json', $mapper->fieldName, 'Field name not recorded correctly'); } #[TestDox('Map succeeds with valid JSON')] public function testMapSucceedsWithValidJSON(): void { - $doc = (new JsonMapper(TestDocument::class))->map(['data' => '{"id":7,"subDoc":{"id":22,"name":"tester"}}']); + $doc = (new DocumentMapper(TestDocument::class))->map(['data' => '{"id":7,"subDoc":{"id":22,"name":"tester"}}']); $this->assertNotNull($doc, 'The document should not have been null'); $this->assertEquals(7, $doc->id, 'ID not filled correctly'); $this->assertNotNull($doc->subDoc, 'The sub-document should not have been null'); @@ -52,6 +52,6 @@ class JsonMapperTest extends TestCase public function testMapFailsWithInvalidJSON(): void { $this->expectException(DocumentException::class); - (new JsonMapper(TestDocument::class))->map(['data' => 'this is not valid']); + (new DocumentMapper(TestDocument::class))->map(['data' => 'this is not valid']); } }