{"id":15739699,"url":"https://github.com/dwgebler/doclite","last_synced_at":"2025-08-20T10:31:58.855Z","repository":{"id":42039874,"uuid":"343591152","full_name":"dwgebler/doclite","owner":"dwgebler","description":"PHP NoSQL database and document store","archived":false,"fork":false,"pushed_at":"2024-04-22T14:29:00.000Z","size":207,"stargazers_count":82,"open_issues_count":0,"forks_count":5,"subscribers_count":6,"default_branch":"main","last_synced_at":"2024-12-09T21:36:43.334Z","etag":null,"topics":["database","nosql","php","php7","php8","sqlite","sqlite3"],"latest_commit_sha":null,"homepage":"http://www.doclite.co.uk/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dwgebler.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":"FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":null,"patreon":"dwgebler","open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2021-03-01T23:56:10.000Z","updated_at":"2024-12-08T15:47:34.000Z","dependencies_parsed_at":"2024-04-22T16:09:01.008Z","dependency_job_id":null,"html_url":"https://github.com/dwgebler/doclite","commit_stats":{"total_commits":25,"total_committers":3,"mean_commits":8.333333333333334,"dds":0.07999999999999996,"last_synced_commit":"8a8fc3aa172aae321bf2dcc3c588ed42d49d59a4"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwgebler%2Fdoclite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwgebler%2Fdoclite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwgebler%2Fdoclite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dwgebler%2Fdoclite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dwgebler","download_url":"https://codeload.github.com/dwgebler/doclite/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230415318,"owners_count":18222158,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","nosql","php","php7","php8","sqlite","sqlite3"],"created_at":"2024-10-04T02:06:10.105Z","updated_at":"2024-12-19T10:09:20.092Z","avatar_url":"https://github.com/dwgebler.png","language":"PHP","readme":"# DocLite\nA powerful PHP NoSQL document store built on top of SQLite.\n\n[![Build Status](https://travis-ci.com/dwgebler/doclite.svg?token=uj4HfXm5wqJXVuPAd984\u0026branch=master)](https://app.travis-ci.com/github/dwgebler/doclite)\n\n## Table of contents\n\n- [About DocLite](#about-doclite)\n  - [Why DocLite?](#why-doclite)\n- [Getting Started](#getting-started)\n- [The Database](#the-database)\n  - [Creating a memory database](#creating-a-memory-database)\n  - [Creating a file database](#creating-a-file-database)\n  - [Error Handling \u0026 Logging](#error-handling--logging)\n  - [Import and export data](#import-and-export-data)\n    - [Importing Data](#importing-data)\n    - [Exporting data](#exporting-data)\n  - [Advanced options](#advanced-options)\n    - [Get DocLite version](#get-doclite-version)\n    - [Optimize database](#optimize-database)\n    - [Set synchronization mode](#set-synchronization-mode)\n    - [Set rollback journal mode](#set-rollback-journal-mode)\n- [Collections](#collections)\n  - [About Collections](#about-collections)\n  - [Obtain a collection](#obtain-a-collection)\n  - [Create a document](#create-a-document)\n  - [Save a document](#save-a-document)\n  - [Retrieve a document](#retrieve-a-document)\n  - [Map a document to a custom class](#map-a-document-to-a-custom-class)\n  - [Delete a document](#delete-a-document)\n  - [Query a collection](#query-a-collection)\n    - [Find single document by values](#find-single-document-by-values)\n    - [Find all matching documents by values](#find-all-matching-documents-by-values)\n    - [Find all documents in collection](#find-all-documents-in-collection)\n    - [Advanced queries](#advanced-queries)\n    - [Query operators](#query-operators)\n  - [Join collections](#join-collections)\n  - [Caching results](#caching-results)\n  - [Index a collection](#index-a-collection)\n  - [Unique indexes](#unique-indexes)\n  - [Delete a collection](#delete-a-collection)\n  - [Collection transactions](#collection-transactions)\n  - [Full text search](#full-text-search)\n- [Documents](#documents)\n  - [About Documents](#about-documents)\n  - [Getting and setting document data](#getting-and-setting-document-data)\n  - [Mapping document fields to objects](#mapping-document-fields-to-objects)\n  - [Document Unique Id](#document-unique-id)\n  - [Saving a document](#saving-a-document)\n  - [Deleting a document](#deleting-a-document)\n  - [Document validation](#document-validation)\n- [Other info](#other-info)\n  - [Symfony integration](#symfony-integration)\n  - [Licensing](#licensing)\n  - [Bugs, issues](#bugs-issues)\n  - [Contact the author](#contact-the-author)\n  \n## About DocLite\n\nDocLite is a powerful NoSQL document store for PHP built on top of SQLite. It uses the \nPHP PDO SQLite library to access a SQLite database and automatically manage \ndocuments organized in to named collections, which are stored as JSON.\n\nDocLite takes advantage of the SQLite JSON1 extension (this is usually bundled in to \nthe libsqlite included with your PHP distribution, so you probably already have it) \nto store, parse, index and query JSON documents - giving you the power and flexibility \nof a fully transactional and ACID compliant NoSQL solution, yet contained within the\nlocal file system. No need for more complex systems like Mongo, CouchDB or Elasticsearch \nwhen your requirements are slim. No need for any external dependencies, just PHP with \nPDO SQLite enabled.\n\nDocLite provides a simple, intuitive, flexible and powerful PHP library that you can \nlearn, install and start using in minutes.\n\n### Why DocLite?\n\nDocLite lends itself well to a variety of use cases, including but not limited to:\n\n- Agile development and rapid prototyping while your requirements are evolving.  \n  \n- Powerful, self-contained NoSQL database for small to medium websites or \n  applications, such as blogs, business website, CMS, CRM or forums.\n  \n- A fast and reliable cache for data retrieved from remote databases, APIs or \n  servers. Process your data in to documents, save in DocLite and easily query \n  and filter your data as needed.\n\n- Robust, performant, ACID compliant replacement for weaker, slower, flat-file \n  data stores utilizing JSON, XML or YAML.\n  \n- Application database for web apps installed and run on a local environment.\n\n- Database for microservices and middleware.\n\n- Fast in-memory database for data processing or machine learning algorithms.\n\nBroadly speaking, DocLite is suitable for the [same uses cases](https://www.sqlite.org/whentouse.html) \nas the underlying SQLite engine it is built on, but where you desire a NoSQL solution.\n\n## Getting Started\n\n\u003cdetails\u003e\n\u003csummary\u003eSystem requirements\u003c/summary\u003e\n\n- PHP 7.4 or above\n\n- With PDO SQLite enabled, built against libsqlite ≥ 3.18.0 with JSON1 extension.\n\n(on most systems, if you're running PHP 7.4 you probably already meet the second \nrequirement)\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eInstallation\u003c/summary\u003e\n\nInstall with [Composer](https://getcomposer.org/)\n\n`composer require dwgebler/doclite`\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eUsage Overview\u003c/summary\u003e\n\nDocLite provides both a `FileDatabase` and `MemoryDatabase` implementation. \nTo create or open an existing database, simply create a `Database` object, specifying the file path if using a `FileDatabase`.\n\nIf your `FileDatabase` does not exist, it will be created (ensure your script has the appropriate write permissions). \nThis will include creating any parent directories as required.\n\nIf you specify an existing directory without a filename, a default filename `data.db` will be used.\n\n```php\nuse Gebler\\Doclite\\{FileDatabase, MemoryDatabase};\n\n// To create or open an existing file database.\n$db = new FileDatabase('/path/to/db');\n\n// To open an existing file database in read-only mode.\n$db = new FileDatabase('/path/to/existing/db', true);\n\n// To create a new in-memory database.\n$db = new MemoryDatabase();\n```\n\nOnce you have opened a database, you can obtain a document `Collection` which will be automatically created \nif it does not exist.\n\n```php\n$users = $db-\u003ecollection(\"user\"); \n```\n\nThe `Collection` object can then be used to retrieve, create and manipulate documents.\n\n```php\n// Create a new User in the collection\n$user = $users-\u003eget();\n\n// Get the automatically generated document ID\n$id = $user-\u003egetId();\n\n// Set properties by magic set* methods\n$user-\u003esetUsername(\"dwgebler\");\n$user-\u003esetRole(\"admin\");\n$user-\u003esetPassword(password_hash(\"admin\", \\PASSWORD_DEFAULT));\n$user-\u003esetCreated(new \\DateTimeImmutable);\n\n// Update the user in the collection\n$user-\u003esave();\n\n// Retrieve this user later on by ID\n$user = $users-\u003eget($id);\n\n// Or search for a user by any field\n$user = $users-\u003efindOneBy([\"username\" =\u003e \"dwgebler\"]);\n```\n\nIn the example above, `$user` is an instance of a DocLite `Document`, but you can also \nhydrate objects of your own custom classes from a collection.\n\n```php\nclass CustomUser\n{\n    private $id;\n    private $username;\n    private $password;\n    \n    public function getId() {...}\n    public function setId($id) {...}\n    public function getUsername() {...}\n    public function setUsername($username) {...}    \n}\n\n// Retrieve a previously created user and map the result on to a CustomUser object.\n// You can also pass a null ID as the first parameter to create a new CustomUser.\n$user = $users-\u003eget($id, CustomUser::class);\n\n// $user is now an instance of CustomUser and can be saved through the Collection.\n$users-\u003esave($user);\n```\n\nTo learn more about the `Collection` object including how to query a document store, please read the full \ndocumentation below.\n\u003c/details\u003e\n\n## The Database\n\nDocLite is built on top of SQLite 3 and supports two types of database; file and memory. \nThe corresponding classes are `FileDatabase` and `MemoryDatabase`.\n\n### Creating a memory database\n\n`MemoryDatabase` is stored in volatile memory and is therefore ephemeral for the lifetime \nof your application scripts. Its constructor takes optional parameters:\n\n- a boolean flag indicating whether to enable full text search features (defaults to `false`) - this\n  feature requires SQLite to have been compiled with the [FTS5 extension](https://www.sqlite.org/fts5.html).\n- an integer representing the maximum connection timeout in seconds (defaults to `1`) which\n  is how long the connection should wait if the underlying SQLite database is locked.\n- A [PSR-3](https://www.php-fig.org/psr/psr-3/) compatible logger instance (defaults to `null`).\n\n```php\n\n```php\nuse Gebler\\Doclite\\MemoryDatabase;\n\n$db = new MemoryDatabase();\n\n// With full text search enabled and a 2-second connection timeout\n$logger = new \\Monolog\\Logger('my-logger');\n$db = new MemoryDatabase(true, 2, $logger); \n```\n\n### Creating a file database\n\n`FileDatabase` constructor takes one mandatory and then some optional parameters; \nonly the file or directory path to a new or existing database is required. \n\nOptional parameters are:\n\n- a boolean flag indicating whether the database should be opened in read-only mode, which defaults to `false`.\n- a boolean flag indicating whether to enable full text search features (defaults to `false`) - this\nfeature requires SQLite to have been compiled with the [FTS5 extension](https://www.sqlite.org/fts5.html).\n- an integer representing the maximum connection timeout in seconds (defaults to `1`) which\n  is how long the connection should wait if the underlying SQLite database is locked.\n- A [PSR-3 Logger](https://www.php-fig.org/psr/psr-3/) instance to use for logging database events.\n\nThe path supplied to `FileDatabase` can be a relative or absolute path which is any of:\n\n- An existing directory with read and write access.\n- A non-existent file in a directory with read-write access.\n- An existing database in a directory with read-write or read-only access (read-only mode).\n- A non-existing directory path which your script has permission to create.\n\nIf no file name is specified, a default file name `data.db` will be used for the underlying database.\n\n```php\nuse Gebler\\Doclite\\FileDatabase;\n\n// Open a new database\n$db = new FileDatabase('./data/mydb.db');\n\n// Open an existing database in read-only mode\n$db = new FileDatabase('./data/mydb.db', true);\n\n// Open a new database called data.db in existing directory /home/data\n$db = new FileDatabase('/home/data');\n\n// All options - path, read-only mode, full text search, connection timeout and logger\n$logger = new \\Monolog\\Logger('mylogger');\n$db = new FileDatabase('./data/mydb.db', false, true, 1, $logger);\n\n// Or, in PHP 8, named parameters:\n$db = new FileDatabase(path: './data/mydb.db', readOnly: true, ftsEnabled: true);\n```\n\nIf you open a database in read-only mode, you will be able to retrieve documents from a collection, \nbut you will not be able to save them or create new documents or collections. \nAttempting to do so will trigger an error.\n\nIt is good practice wrapping `FileDatabase` creation in a try-catch block. \nInitializing a `FileDatabase` may throw either an `IOException` (for errors relating to the file system) \nor a `DatabaseException` (for errors establishing the DB connection).\n\n```php\nuse Gebler\\Doclite\\Exception\\IOException;\nuse Gebler\\Doclite\\Exception\\DatabaseException;\nuse Gebler\\Doclite\\FileDatabase;\n\ntry {\n  $db = new FileDatabase('/path/to/db');\n} catch (IOException $e) {\n    var_dump($e-\u003egetMessage());\n} catch (DatabaseException $e) {\n    var_dump($e-\u003egetMessage());\n}\n```\n\n### Error Handling \u0026 Logging\n\nTo enable logging queries and parameters in the full and final SQL sent to the database, pass a PSR-3 logger instance in to you `FileDatabase` or `MemoryDatabase` \neither via the constructor or the `$database-\u003esetLogger(LoggerInterface $logger)` method at any time.\n\nThen call `$database-\u003eenableQueryLogging()` to enable logging of _all_ queries. These will be logged at the `debug` level.\n\nOr call `$database-\u003eenableSlowQueryLogging()` to enable logging of queries that take longer than 500ms. \nThese will be logged with a `warning` level.\n\nAs long as a `LoggerInterface` instance is set on a database, any exceptions will also be logged at the `error` level.\n\nYou can disable logging by calling `$database-\u003edisableQueryLogging()` or `$database-\u003edisableSlowQueryLogging()`.\n\nDocLite primarily throws a `DatabaseException` when any error occurs. This is true across \nthe `Database`, `Collection` and `Document` types. A `Database` exception will \ninclude a message, an error code (see below), any underlying system exception if there was one (so normal \n`Exception` behaviour up to this point), plus any SQL query which was being executed \n(DocLite hides these from you during normal operation, of course, as it is a NoSQL solution, \nbut they are useful for filing bug reports!), and an array of any relevant parameters - these \nmay be things like a document ID, the document data, etc.\n\n```php\nuse Gebler\\Doclite\\Exception\\DatabaseException;\n...\ntry {\n    $user-\u003esetUsername(\"dwgebler\");\n    $user-\u003esave();\n} catch (DatabaseException $e) {\n    var_dump($e-\u003egetMessage(), $e-\u003egetCode(), $e-\u003egetQuery(), $e-\u003egetParams());\n}\n```\n\nA `DatabaseException` can occur on any `Database`, `Collection` or `Document` \nmethod which interacts with the underlying database.\n\nError codes are represented by public constants in the `DatabaseException` class.  \nThe full list of error codes are as follows:\n\n| Constant                         | Meaning |\n| -------------                    | ------------ |\n| ERR_COLLECTION_IN_TRANSACTION    | Attempted to begin, rollback or commit on a collection while a transaction on a different collection was already in progress. |\n| ERR_CONNECTION                   | Unable to connect to database  |\n| ERR_NO_SQLITE                    | PDO SQLite extension is not installed |\n| ERR_NO_JSON1                     | SQLite does not have the JSON1 extension installed |\n| ERR_NO_FTS5                      | FTS5 extension not installed\n| ERR_INVALID_COLLECTION           | Invalid collection name |\n| ERR_MISSING_ID_FIELD             | Custom class for mapping a document does not have an ID field |\n| ERR_INVALID_FIND_CRITERIA        | Attempted to find a document by non-scalar value |\n| ERR_INVALID_ID_FIELD             | Specified unique ID field for custom class does not exist, nor does default |\n| ERR_ID_CONFLICT                  | Multiple documents in the same collection have the same ID |\n| ERR_CLASS_NOT_FOUND              | Custom class name being used for a document does not exist |\n| ERR_INVALID_UUID                 | Attempted to get the timestamp from an invalid UUID |\n| ERR_QUERY                        | Error executing SQL query |\n| ERR_READ_ONLY_MODE               | Attempted a write operation on a read only database |\n| ERR_INVALID_JSON_SCHEMA          | Attempted to import an invalid JSON schema |\n| ERR_INVALID_DATA                 | Data does not match loaded JSON schema |\n| ERR_MAPPING_DATA                 | Unable to map document to class |\n| ERR_IMPORT_DATA                  | Error importing data |\n| ERR_IN_TRANSACTION               | Attempting locking operation while in a transaction |\n| ERR_INVALID_TABLE                | Attempting to access invalid table |\n| ERR_UNIQUE_CONSTRAINT            | Attempting to insert a document with a unique field that already exists |\n\n### Import and export data\n\nDocLite can import data from and export data to JSON, YAML, XML and CSV files. \nFor this purpose, the `Database` object provides two methods, `import()` and \n`export()`.\n\n\u003e :warning: Import or export operations on very large collections may exhaust\n\u003e memory. This feature will (probably) be improved and made more efficient for\n\u003e working with large data sets in future.\n\u003e \n\u003e It is recommended to use JSON for exports you intend to reload in \n\u003e to a DocLite database. Support for other formats is experimental.\n\n#### Importing Data\n\nThe data you want to import can be organized either in to files, where each file \nrepresents a collection of multiple documents, or a directory where each \nsub-directory represents a collection and contains a number of files each \nrepresenting a single document.\n\n`import(string $path, string $format, int $mode)`\n\n`format` can be any of `json`, `yaml`, `xml`, or `csv`. This should also \nmatch the extension of the filename(s) containing your data.\n\nWhen using the `csv` format, the first line of a CSV file is assumed to be a\nheader line containing field names.\n\n`mode` can be either of the constants `Database::MODE_IMPORT_COLLECTIONS` or \n`Database::MODE_IMPORT_DOCUMENTS`.\n\nCollection names are inferred from the subdirectory or file names. For example,\n`/path/to/collections/users.json` will import to the `users` collection, as will\na sub-directory `/path/to/collections/users/` when importing a collection from\nmultiple files.\n\n```php\n// Create a new, empty database\n$db = new FileDatabase('/path/to/data.db');\n\n// Import the contents of a directory where each file is a collection\n$db-\u003eimport('/path/to/collections', 'json', Database::IMPORT_COLLECTIONS);\n\n// Import the contents of a directory where each sub directory is a collection \n// of files representing single documents.\n$db-\u003eimport('/path/to/collections', 'json', Database::IMPORT_DOCUMENTS);\n```\n\nWhen you import documents in to a collection, any documents which have a unique \nID matching an existing document in the database will overwrite that document. \nOtherwise, new documents will be created for any documents with an unmatched or \nmissing ID.\n\n\u003e :bulb: Each Collection import will be wrapped in a single transaction, so \n\u003e imports are atomic per collection. You can also speed up bulk imports by \n\u003e setting the advanced options (see below) to alter the database's \n\u003e synchronization and rollback journal modes to something a little more \n\u003e permissive, if you understand the implications of doing so.\n\n#### Exporting data\n\nYou can export the entire contents of one or more collections. Much like importing \ndata, you can choose whether DocLite should export this as one file per collection \ncontaining multiple documents, or one directory per collection with one file per \ndocument.\n\n`export(string $path, string $format, int $mode, array $collections = [])`\n\n`format` can be any of `json`, `yaml`, `xml`, or `csv`.\n\n`mode` can be either of the constants `Database::MODE_EXPORT_COLLECTIONS` or\n`Database::MODE_EXPORT_DOCUMENTS`.\n\n`collections` can be a mix of strings of collection names and/or `Collection` \nobjects. If this is empty, all collections in the database will be exported.\n\n```php\n// Export the entire database to one file per collection in the specified \n// output directory.\n$db-\u003eexport('/path/to/export', 'json', Database::EXPORT_COLLECTIONS);\n\n// Export the entire database to a directory structure with one file per document.\n$db-\u003eexport('/path/to/export', 'json', Database::EXPORT_DOCUMENTS);\n\n// Export only the \"User\" and \"Person\" collections.\n// Assume Collection $persons = $db-\u003eget(\"Person\");\n$db-\u003eexport(\n    '/path/to/export', \n    'json',\n    Database::EXPORT_COLLECTIONS,\n    ['User', $persons]\n);\n```\n\n\u003e :warning: The XML standard imposes some restrictions on entity names. When \n\u003e exporting to this format, DocLite will replace any invalid characters in \n\u003e document fields with underscores. This means you may not be able to recreate \n\u003e your document store exactly as it was should you subsequently import these \n\u003e files in to a DocLite database.\n\n### Advanced options\n\nDocLite `Database` objects have a few methods for more advanced options.\n\n#### Get DocLite version\n```php\n// Return the version of DocLite as a SemVer string, e.g. 1.0.0\n$db-\u003egetVersion();\n```\n\n#### Optimize database\nCall `$db-\u003eoptimize()` to attempt database optimization. This function does \nnot return anything, though can throw a `DatabaseException` if something goes \nwrong. Periodic optimization can reduce database file size and improve performance.\n\n#### Set synchronization mode\n\nThe underlying SQLite sync mode can be set to one of the following constants in\nthe `Database` class.\nSee [SQLite documentation](https://www.sqlite.org/pragma.html#pragma_synchronous) \nfor details of the implications of changing this value; disabling sync can lead \nto data loss in the event of a crash or power loss.\n\n| Constant              | Meaning |\n| --------              | ------- |\n| MODE_SYNC_OFF         | Disable sync |\n| MODE_SYNC_NORMAL      | Normal sync **Default setting** |\n| MODE_SYNC_FULL        | Full sync |\n| MODE_SYNC_EXTRA       | Extra sync |\n\nCall `$db-\u003esetSyncMode(Database::MODE_CONSTANT)` to set the mode. \nFor example to set Full Sync mode, call `$db-\u003esetSyncMode(Database::MODE_SYNC_FULL)`.\n\nThis function returns `true` on success or `false` on failure.\n\nCall `$db-\u003egetSyncMode()` to get the current mode which can be compared to one \nof the constants. The return type is `int`.\n\n#### Set rollback journal mode\n\nThe underlying SQLite management of the rollback journal can be set to one of \nthe following constants. \nSee [SQLite documentation](https://www.sqlite.org/pragma.html#pragma_journal_mode) \nfor the implications of changing this value; disabling the rollback journal can \nlead to unintended data state.\n\n\u003e :warning: **Warning:** If you disable the rollback journal, transactions, atomic commits \n\u003e and rollbacks will no longer work. The behaviour of transaction methods on a \n\u003e collection in this mode is undefined and may lead to unpredictable results or \n\u003e data corruption. You should therefore not use the \n\u003e [transaction methods](#collection-transactions) in MODE_JOURNAL_NONE.\n\n| Constant              | Meaning |\n| --------              | ------- |\n| MODE_JOURNAL_NONE     | Disable the rollback journal |\n| MODE_JOURNAL_MEMORY   | In-memory rollback journal only |\n| MODE_JOURNAL_WAL      | Use the write ahead log **Default setting** |\n| MODE_JOURNAL_DELETE   | Delete rollback journal at end of each transaction |\n| MODE_JOURNAL_TRUNCATE | Truncate rollback journal at end of each transaction |\n| MODE_JOURNAL_PERSIST  | Prevent the rollback journal being deleted |\n\nCall `$db-\u003esetJournalMode(Database::MODE_CONSTANT)` to set the mode.  \nFor example to set WAL mode, call `$db-\u003esetJournalMode(Database::MODE_JOURNAL_WAL)`.\n\nThis function returns `true` on success or `false` on failure.\n\nCall `$db-\u003egetJournalMode()` to get the current  mode which can be compared to one \nof the constants. The return type is `string`.\n\n## Collections\n\n### About Collections\n\nCollections are at the heart of DocLite. A `Collection` represents a named group of documents \n(for example, \"Users\") and is analogous to a table in a structured database.\n\n\u003e :bulb: **Note**: Collections are represented in the underlying SQLite database as tables. \nThey must therefore obey a few rules:\n\u003e \n\u003e - Collection names cannot start with `sqlite_`\n\u003e - Collection names cannot start with a number.\n\u003e - Collection names may contain only alphanumeric characters and underscores.\n\u003e - Collection names cannot be longer than 64 characters.\n\nA `Collection` object is the means by which you create, find, update and delete documents.\n\nEvery document in a collection must have a unique ID. You can either supply this yourself, \nor one will be created for you when you first instantiate a document. Auto generated IDs \ntake the form of a [v1 UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address)) \nwhich includes a timestamp of when the document was first created.\n\n### Obtain a collection\n\nCollections are obtained from a `FileDatabase` or `MemoryDatabase` by calling the `collection` \nmethod. If the collection does not exist, it will be automatically created.\n\n```php\n$userCollection = $db-\u003ecollection(\"Users\");\n```\n\n### Create a document\n\nOnce you have a collection, create a new document by calling the collection's `get` method.\n\n```php\n$newUser = $userCollection-\u003eget();\n```\n\n### Save a document\n\nBy default, documents are returned as a DocLite `Document` object, which provides \na `save()` method. You can also save a document of any type by calling `save()` \non the collection with the document object as a parameter.\n\n```php\n// works for DocLite Document objects\n$newUser-\u003esave();\n\n// works for both DocLite documents and documents mapped to custom types\n$userCollection-\u003esave($newUser);\n```\n\n### Retrieve a document\n\n`get` can also be used to retrieve a document by its ID.\n\n```php\n$existingUser = $userCollection-\u003eget($id);\n```\n\n### Map a document to a custom class\n\nBy default, retrieving a document will return a DocLite `Document` object, which provides magic \nmethods and properties for you to access and manipulate the document data. It is however also \npossible to create or retrieve a document as an object of any custom class, provided that class \nhas either public properties or getter/setter methods for the document fields you wish to hydrate.\n\n```php\n// Get a user as an object of type CustomUser.\n$user = $userCollection-\u003eget($id, CustomUser::class);\n```\n\nBy default, DocLite will look for a property called `id` to populate with the document's unique id. \nIf you want to use a different property on a custom class to store this id, for example because your class \ndoes not have an `id` property, or you are using it for something else, you can specify a custom ID property \nname as a third parameter to `get`.\n\n```php\n$user = $userCollection-\u003eget($id, CustomUser::class, 'databaseId');\n```\n\nAlternatively, you can add a public property or getter/setter to your class called `docliteId` \nand DocLite will automatically attempt to populate this instead in the absence of an `id` property. \n\nWhile the `Document` class provides a built-in `save()` method as a convenience to update a Document \nin storage, documents represented as your own custom classes must be saved through the collection object.\n\n```php\n$userCollection-\u003esave($user);\n```\n\nIf you are using a custom property on your class to hold the document's unique ID, you \nshould supply the ID as an additional parameter.\n\n```php\n$userCollection-\u003esave($user, $user-\u003egetDatabaseId());\n```\n\nFinally, when saving a document represented as a custom class, you can specify an optional \nthird parameter to list any properties on the object you do _not_ want to be stored in the \ndocument. It is only necessary to do this either for properties you wish to be excluded which\nare public / have getter/setter methods, or public get methods which do not represent properties.\n\n```php\n$userCollection-\u003esave($user, $user-\u003egetDatabaseId(), ['nonDatabaseField']);\n```\n\n### Delete a document\n\nMuch like `save()`, there is both a convenience `delete()` method on DocLite \n`Document` objects and a `deleteDocument(object $document)` method on the \ncollection itself.\n\n```php\n// Works for DocLite Document objects.\n$user-\u003edelete();\n\n// works for both DocLite documents and documents mapped to custom types\n$userCollection-\u003edeleteDocument($user);\n```\n\n\n### Query a collection\n\nThe `Collection` object provides a range of methods to find documents by arbitrary criteria.\n\n#### Find single document by values\nFind a single document where all keys match the specified values by calling `findOneBy`.\n\n```php\n$user = $userCollection-\u003efindOneBy([\n    'role' =\u003e 'admin',\n    'name' =\u003e 'Mr Administrator',\n]);\n```\n\n`findOneBy` takes optional custom class name and custom class ID field parameters in the same \nmanner as `get`.\n\n```php\n$user = $userCollection-\u003efindOneBy(['username' =\u003e 'admin'], CustomUser::class, 'databaseId');\n```\n\nIf a document which matches the criteria cannot be found, `null` is returned.\n\n#### Find all matching documents by values\n\nThe function `findAllBy` works the same way as `findOneBy` but will return a \ngenerator which you can iterate over, or convert to array via PHP's \n`iterator_to_array` function.\n\n```php\nforeach($userCollection-\u003efindAllBy(['active' =\u003e true]) as $user) {\n   ...\n}\n```\n\n#### Find all documents in collection\n\nTo retrieve all documents in a collection, use `findAll()`. Like the previous two functions, \n`findAll` can take an optional custom classname and ID property as parameters.\n\n```php\nforeach($userCollection-\u003efindAll() as $user) {\n   ...\n}\n```\n\n#### Advanced queries\n\nDocLite includes a powerful query building mechanism to retrieve or delete all documents \nin a collection matching arbitrary criteria.\n\nTo build a query, use any combination of the `where()`, `and()`, `or()`, `limit()`,\n`offset()` and `orderBy()` functions on the collection object, followed by a \ncall to `fetch()`, `delete()` or `count()`.\n\nYou can also run nested queries to group clauses together via `union()` \n(for grouping clauses by `OR`) and `intersect()` (for grouping clauses by `AND`).\n\nYou can query a document to any depth by separating nested fields with a `.` dot character, \nyou can also add square brackets `[]` to the end of a field which is a list to query \nall the values inside that list for any match.\n\nThe advanced queries APIs are better understood by example.\n\nFor the following code snippets, imagine each document of your user collection looks \nlike the following data example, expressed here as YAML:\n\n\u003cdetails\u003e\u003csummary\u003eSample user document\u003c/summary\u003e\n\n```yaml\nusername: adamjones\nfirst_name: Adam\nlast_name: Jones\npassword: \"$2y$10$LRS.0xUCJjWSmQuWMMRsuurZ0OGlU.NH7KYXsipzkfUa0YREEarj2\"\naddress:\n  street: 123 Fake Street\n  area: Testville\n  county: Testshire\n  postcode: TE1 3ST\nroles:\n- USER\n- EDITOR\ntelephone: \"+441234567890\"\nregistered: true\nactive: true\nlastLogin: \"2021-02-13T10:34:40+00:00\"\nemail: adamjones@example.com\napi_access:\n  \"/v1/pages/\":\n  - POST\n  - GET\n  \"/v1/contributors/\":\n  - GET\n```\n\u003c/details\u003e\n\nHere are some example queries you could run against a collection of these documents.\n\n\u003cdetails\u003e\u003csummary\u003eSample queries\u003c/summary\u003e\n\n```php\n$users = $db-\u003ecollection(\"Users\");\n\n$activeUsers = $users-\u003ewhere('active', '=', true)-\u003efetch();\n\n$gmailUsers = $users-\u003ewhere('email', 'ENDS', '@gmail.com')-\u003efetch();\n\n$registeredAndNotActiveUsers = $users-\u003ewhere('registered', '=', true)\n                                     -\u003eand('active', '=', false)\n                                     -\u003efetch();\n\n$usersInPostalArea = $users-\u003ewhere('address.postcode', 'STARTS', 'TE1')-\u003efetch();\n\n$usersWith123InPhone = $users-\u003ewhere('telephone', 'CONTAINS', '123')-\u003efetch();\n\n$usersWithNoNumbersInUsername = $users-\u003ewhere('username', 'MATCHES', '^[A-Za-z]*$')\n                                       -\u003efetch();\n                                       \n$usersWithEditorRole = $users-\u003ewhere('roles[]', '=', 'EDITOR')-\u003efetch();\n\n$usersWithEditorOrAdminRole = $users-\u003ewhere('roles[]', '=', 'ADMIN')\n                                    -\u003eor('roles[]', '=', 'EDITOR')\n                                    -\u003efetch();\n                                    \n$usersWithEditorAndAdminRole = $users-\u003ewhere('roles', '=', ['ADMIN', 'EDITOR']);                                    \n                                    \n$usersWhoHaveAtLeastOneRoleWhichIsNotAdmin = $users-\u003ewhere('roles[]', '!=', 'ADMIN')-\u003efetch();\n\n/* \n * This next one is trickier. \"roles\" is a list of values in our document.\n * As we can see above, roles[] != ADMIN would return all users who\n * have at least one role in their list which is not ADMIN.\n * But this means if a user has roles [\"USER\",\"ADMIN\"], they would\n * be matched.\n * So for users who do NOT have the ADMIN role at all, we can\n * quote the value \"ADMIN\" and ask for matches where the entire list of roles\n * (so no square brackets) does not contain this value.\n*/\n$usersDoNotHaveAdminRole = $users-\u003ewhere('roles', 'NOT CONTAINS', '\"ADMIN\"')-\u003efetch();\n\n$deleteAllUsersWithEditorRole = $users-\u003ewhere('roles[]', '=', 'EDITOR')-\u003edelete();\n\n$first10UsersOrderedByFirstName = $users-\u003eorderBy('first_name', 'ASC')\n                                        -\u003elimit(10)\n                                        -\u003efetch();                                                               \n\n$next10UsersOrderedByFirstName = $users-\u003eorderBy('first_name', 'ASC')\n                                       -\u003elimit(10)\n                                       -\u003eoffset(10)\n                                       -\u003efetch();                                                               \n\n// Use [] on any field which is a list to search within its sub-items\n$usersWithPostAccessToPagesApi = $users-\u003ewhere(\n    'api_access./v1/pages/[]', '=', 'POST')-\u003efetch();                                     \n\n$allUsersWithPostAccessToAnyApi = $users-\u003ewhere('api_access[]', '=', 'POST')\n                                        -\u003efetch();\n\n$start = new DateTimeImmutable('2021-03-01 00:00:00');\n$end = new DateTime('2021-06-30');\n$usersWhoSignedUpBetweenMarchAndJune = $users-\u003ewhere('date', 'BETWEEN', $start, $end)-\u003efetchArray();                                     \n\n/**\n * Nested queries are also possible.\n * To get all users where \n * (active=true and address.postcode matches '^[A-Za-z0-9 ]*$')\n * OR\n * (roles[] list contains \"EDITOR\" and lastLogin \u003e 2021-01-30)\n */\n$nestedUsers = $users-\u003ewhere('active', '=', true)\n                     -\u003eand('address.postcode', 'MATCHES', '^[A-Za-z0-9 ]*$')\n                     -\u003eunion()\n                     -\u003ewhere('roles[]', '=', 'EDITOR')\n                     -\u003eand('lastLogin', '\u003e', '2021-01-30')\n                     -\u003efetch();\n```\n\u003c/details\u003e\n\n\u003e :bulb: Like `findAllBy()`, the `fetch()` method returns a generator, not an \n\u003e array. If you would like all results at once, replace `fetch()` with \n\u003e `fetchArray()`.\n\n\u003e :bulb: The `fetch()` method on advanced queries can take a custom class name\n\u003e and custom ID field as optional parameters, just like the `findOneBy`, \n\u003e `findAllBy` and `findAll()` methods.\n\n\u003e :bulb: Speed up complex queries by enabling DocLite's caching feature.\n\n#### Query operators\n\nAdvanced queries support the following operators:\n\n| Operator | Meaning             |\n| ----     | -------             |\n| =        | Equals, exact match |\n| !=       | Not equals |\n| \u003c        | Less than |\n| \\\u003e       | Greater than |\n| \u003c=        | Less than or equal |\n| \\\u003e=       | Greater than or equal |\n| BETWEEN  | Between two values, inclusive. Equivalent to \u003e= AND \u003c= |\n| NOT BETWEEN  | Not between two values, inclusive. Equivalent to \u003c OR \u003e |\n| STARTS   | Text starts with |\n| NOT STARTS  | Text does not start with |\n| ENDS     | Text ends with |\n| NOT ENDS     | Text does not end with |\n| CONTAINS | Text contains |\n| NOT CONTAINS | Text does not contain |\n| MATCHES  | Text regular expression match |\n| NOT MATCHES  | Text negative regular expression match |\n| EMPTY | Has no value, null |\n| NOT EMPTY | Has any value, not null |\n\n### Join Collections\n\nIt is possible to join a collection to one or more other collections when running a query, \nto include matching results from these collections in the documents returned. This works much \nlike a foreign key in a relational database.\n\nFor example, if you have a `users` collection and a `comments` collection, where some documents in \n`comments` contain a field `user_id`. You can query `users` and join on `comments`, such that any documents \nmatching in `comments` for the same user ID will be included in the `users` document, under a field called `comments`.\n\n```php\n/**\n * Imagine a user document like:\n * {\"__id\":\"1\", \"name\":\"John Smith\"}\n * \n * and a corresponding comments document like:\n * {\"__id\":\"5\", \"user_id\": \"1\", \"comment\":\"Hello world!\"} \n * \n * You can query the users collection with a join to retrieve an aggregated document like this:\n * {\"__id\":\"1\",\"name\":\"John Smith\",\"comments\":[{\"__id\":\"5\",\"comment\":\"Hello world!\"}]}\n */\n$users = $db-\u003ecollection('Users');\n$comments = $db-\u003ecollection(\"Comments\");\n$users-\u003ewhere('__id', '=', '1')-\u003ejoin($comments, 'user_id', '__id')-\u003efetchArray();\n```\n\nThe `Collection::join` method takes the collection to join as the first parameter, the name of the \ndocument field in that collection to use as a foreign key as the second parameter, and \nthe corresponding field in documents in the joining collection (e.g. Users) to match against.\n\nThe above example therefore is looking for documents in `Comments` where the field `user_id` matches the \nfield `__id` in Users.\n\nAs `join` is part of the standard query building interface on a `Collection`, you can combine with other \nquery operators such as `where`, `and` etc. or other joins.\n\n### Caching results\n\nDocLite can cache the results of queries to speed up retrieval of complex result sets. \nFor very simple queries, however, this may provide no benefit or even incur a small \nperformance penalty, so you should only turn it on if you need to.\n\nTo turn on caching for a collection, call the collection object's \n`enableCache()` method.\n\nLikewise, you can disable caching by calling `disableCache()`.\n\nCache results are valid for the cache lifetime, which defaults to 60 seconds. You can \nchange the cache validity period by calling `setCacheLifetime($seconds)`. A cache \nlifetime of zero means cached results will never expire.\n\nYou can manually flush the cache by calling `clearCache()`.\n\n```php\n$userCollection-\u003eenableCache();\n\n// Set the cache validity period to 1 hour.\n$userCollection-\u003esetCacheLifetime(3600);\n\n$userCollection-\u003edisableCache();\n\n$userCollection-\u003eclearCache();\n```\n\nFinally, the `Database` object can be set to automatically prune expired \nentries whenever the cache is queried. This behaviour is disabled by default; \nto enable auto-pruning, call `enableCacheAutoPrune()` on the database object.\n\n```php\n$db-\u003eenableCacheAutoPrune();\n```\n\n\u003e :bulb: For complex queries, the cache is very fast. If you are running a large \n\u003e number of complex queries on a large data set and these queries are likely to \n\u003e be repeated without the data changing in storage for the lifetime of the cache, \n\u003e it is a good idea to make use of DocLite's caching.\n\n### Index a collection\n\nIt is possible to build indexes on any document fields inside a collection to \nspeed up queries against that field.\n\nWhen you create a collection, an index is automatically added for the internal\nID field. To add a custom index, call the `addIndex` method with the name of a \ndocument field.\n\n```php\n$userCollection-\u003eaddIndex('email');\n```\n\nTo add a single index on multiple fields (as per a multi-column index), \nsimply call `addIndex` with the additional field names as separate \nparameters.\n\n```php\n$userCollection-\u003eaddIndex('first_name', 'last_name');\n```\n\n### Unique Indexes\n\nYou can also add a unique index which acts as a constraint on the field, ensuring \nthat no two documents in the collection can have the same value for that field or field combination.\n\n```php\n$userCollection-\u003eaddUniqueIndex('email');\n```\n\nIf you attempt to add a document to a collection with a unique index and a value\nthat already exists in the collection, a `DatabaseException` will be thrown with the \ncode `DatabaseException::ERR_UNIQUE_CONSTRAINT`.\n\n\u003e :bulb: **Note:** indexes are an advanced feature which work the same way they do \nin any other SQLite database, the only difference being they are created \non document fields rather than a table column. Poorly chosen indexes may \nprovide no benefit or even slow down queries.\n\n### Delete a collection\n\nTo delete all documents in a collection entirely, call the `deleteAll()` method.\n\n```php\n$userCollection-\u003edeleteAll();\n```\n\n### Collection transactions\n\nIt is possible to wrap a sequence of database operations inside a transaction. \nTo do this, use the `Collection`'s `beginTransaction()`, `commit()` and \n`rollback()` methods.\n\n```php\n$collection = $db-\u003ecollection(\"Users\");\n$collection-\u003ebeginTransaction();\n\n// ...do some stuff, insert a bunch of records or whatever...\n\n// commit the results and end the transaction\n$collection-\u003ecommit();\n\n// or rollback the changes and end the transaction\n$collection-\u003erollback();\n```\n\n### Full text search\n\nDocLite is able to build powerful full text indexes against collections to allow you to \nsearch and produce a list of documents, ordered by relevance, where specified fields match some text or phrase.\n\nFull text search capability requires your PHP's `libsqlite` to be built with the FTS5 extension. \nJust like the JSON1 extension, this is usually bundled in to the standard distribution so you probably already have it.\n\nTo search a collection, ensure you have initialized your `Database` with the full text parameter set to `true` to enable \nthis feature, then simply call the `search()` method on any collection, with the search phrase followed by an array of\nthe names of any document fields you wish to search against.\n\n```php\n$path = '/path/to/db';\n$readOnly = false;\n$ftsEnabled = true;\n$timeout = 1;\n$db = new FileDatabase($path, $readOnly, $ftsEnabled, $timeout);\n$blogPosts = $db-\u003ecollection(\"posts\");\n$results = $blogPosts-\u003esearch('apache', ['title', 'summary', 'content']);\n```\n\nResults are automatically ordered by relevance. \n\n\u003e :bulb: DocLite will intelligently manage your full text indexes to keep your database optimized.\n\u003e When you call `search()`, if there is no index for the set of fields you are searching on, it will be created automatically on the first search.\n\u003e If you later call `search()` on a superset of fields for an existing index, the original index \n\u003e will be destroyed and a new, larger index encompassing all searched fields created. This is so DocLite can use the smallest possible index for _all_ the fields you wish to search against.\n\u003e \n\u003e On small collections, this process is so fast you may not see an impact. If, however, you have a very\n\u003e large collection, the recommendation is to create your full text indexes by calling `search()` once from a \n\u003e separate script, so that when your application first runs and calls `search()`, the relevant indexes already exist.\n\nBecause `search()` is part of the standard query fetching interface on a collection (same as `fetch()` and `count()`), it can be preceded by \nnormal query filters using `where()`, `and()` etc. Similar to `fetch()`, the `search()` method returns \na generator. You can convert the results to an array by using PHP's `iterator_to_array()` function.\n\n## Documents\n\n### About Documents\n\nDocuments are a variadic structured store of data in the form of key-value pairs, \nstored in the database as JSON; that is, each document inside a collection can have its own freeform\nstructure. It does not matter whether this matches the structure of any other documents \nin the same collection, that is up to how your application decides to use DocLite.\n\nA document will by default be represented by the DocLite `Document` class, however it is also \npossible to create or retrieve documents from a collection and map them on to your own classes. \nSee the Collection documentation for more details on this.\n\n### Getting and setting document data\n\nThe `Document` class provides magic get and set methods and property accessors for arbitrary document \nkeys. That is, once you have a `Document` from a `Collection`, you can set or read any properties \nyou like by either method:\n\n```php\n$users = $db-\u003ecollection(\"Users\");\n// Create a new Document with an auto generated UUID.\n$user = $db-\u003eget();\n// Create a new property called username via a magic setter.\n$user-\u003esetUsername('dwgebler');\n// Create a new property called password via a magic property.\n$user-\u003epassword = password_hash(\"admin\", \\PASSWORD_DEFAULT);\n// Read the username property via a magic property.\necho $user-\u003eusername;\n// Read the password property via a magic getter.\necho $user-\u003egetPassword();\n// Properties can contain scalar values, arrays, or even other Documents and\n// custom objects.\n$user-\u003esetRoles(['user', 'admin']);\n```\n\nThere is one small semantic difference between the magic method and property access \ntechniques; when using magic methods, the property names are converted from camelCase \nto snake_case, whereas direct property access is literal e.g.\n\n```php\n// setter uses camel case\n$user-\u003esetFirstName('Dave');\n\n// but the corresponding property created will be lower cased and snake_cased\necho $user-\u003efirst_name;\n\n// if you want a key in a document to be case sensitive, set it as a property only\n$user-\u003eFirstName = 'Dave';\n\n// you should now use the property access to retrieve its value later on\necho $user-\u003eFirstName;\n\n// This will not work and will raise a ValueError on getFooBar(),\n// because the method call will look for a property called foo_bar\n$user-\u003eFooBar = 'baz';\n$user-\u003egetFooBar();\n```\n\nThe `Document` class also provides two further methods, `getValue` and `setValue`, \nto query a document by nested keys using a path in dot `.` notation. These \nmethods can also be used to get or set fields with names which can't be \nexpressed through magic set methods or properties.\n\n`getValue()` raises a `ValueError` if the specified path cannot be found.\n\n`setValue()` will automatically create any parent properties on a nested path.\n\n```php\n// This is the same as:\n// $address = $user-\u003egetAddress();\n// $postcode = $address['postcode'];\n$user-\u003egetValue('address.postcode');\n\n// Assume \"roles\" is a list, this will return an array\n$user-\u003egetValue('roles');\n\n// Retrieve the first role\n$user-\u003egetValue('roles.0');\n\n// Assume api_access is a dictionary of keys mapped to lists.\n// This will return the list of data under the /v1/users/ key\n// as an array.\n$access = $user-\u003egetValue('api_access./v1/users/');\nif (!in_array('POST', $access)) { ... }\n\n// If address does not exist, it will be created with postcode as a key.\n$user-\u003esetValue('address.postcode', 'TE1 3ST');\n\n// Or set a value with special characters in the name:\n$user-\u003esetValue('api_access./v1/users/', ['GET', 'POST']);\n```\n\n\u003e :bulb: The values of document fields are arbitrary. Scalar values, arrays and \n\u003e even objects of custom classes can all be stored in a document.\n\n### Mapping document fields to objects\n\nIf you've retrieved a document as a default `Document` object, it is still \npossible to map document fields which represent custom objects to custom classes. \nTo do this, use the `Document`'s `map()` method and pass it a field name (which \ncan use the nested dot `.` notation as described above), along with either a \nclass name or existing object instance if you wish to populate an existing object.\n\nConsider you have the following custom class in your application:\n\n\u003cdetails\u003e\u003csummary\u003eSample class\u003c/summary\u003e\n\n```php\nclass Person\n{\n    private $id;\n\n    private $firstName;\n\n    private $lastName;\n\n    private $address = [];\n\n    private $postcode;\n\n    private $dateOfBirth;\n\n    private $identityVerified;\n\n    public function getId(): ?int\n    {\n        return $this-\u003eid;\n    }\n    \n    public function setId(string $id): self\n    {\n        $this-\u003eid = $id;\n        return $this;\n    }    \n\n    public function getFirstName(): ?string\n    {\n        return $this-\u003efirstName;\n    }\n\n    public function setFirstName(string $firstName): self\n    {\n        $this-\u003efirstName = $firstName;\n        return $this;\n    }\n\n    public function getLastName(): ?string\n    {\n        return $this-\u003elastName;\n    }\n\n    public function setLastName(string $lastName): self\n    {\n        $this-\u003elastName = $lastName;\n        return $this;\n    }\n\n    public function getAddress(): ?array\n    {\n        return $this-\u003eaddress;\n    }\n\n    public function setAddress(array $address): self\n    {\n        $this-\u003eaddress = $address;\n        return $this;\n    }\n\n    public function getPostcode(): ?string\n    {\n        return $this-\u003epostcode;\n    }\n\n    public function setPostcode(string $postcode): self\n    {\n        $this-\u003epostcode = $postcode;\n        return $this;\n    }\n\n    public function getDateOfBirth(): ?\\DateTimeImmutable\n    {\n        return $this-\u003edateOfBirth;\n    }\n\n    public function setDateOfBirth(\\DateTimeImmutable $dateOfBirth): self\n    {\n        $this-\u003edateOfBirth = $dateOfBirth;\n        return $this;\n    }\n\n    public function getIdentityVerified(): ?bool\n    {\n        return $this-\u003eidentityVerified;\n    }\n\n    public function setIdentityVerified(bool $identityVerified): self\n    {\n        $this-\u003eidentityVerified = $identityVerified;\n        return $this;\n    }\n}\n```\n\n\u003c/details\u003e\n\nAnd a `User` document with the following structure:\n\n\u003cdetails\u003e\u003csummary\u003eSample document\u003c/summary\u003e\n\n```yaml\n__id: b83e319a-7887-11eb-8deb-b9e03d2e720d\nusername: daniel_johnson1\nactive: false\nroles:\n- CONTRIBUTOR\n- AUTHOR\ntelephone: \"+441254220959364\"\npassword: \"$2y$10$y8P2Cjph1F.iIc.s2j9aM.GW9qy8aOMeEfzDulQox465mgBJF.pPG\"\nperson:\n  firstName: Daniel\n  lastName: Johnson\n  address:\n    house: '123'\n    street: Test Road\n    city: Testville\n    Country: Testshire\n  postcode: \"TE1 3ST\"\n  dateOfBirth: '1980-03-23T00:00:00+00:00'\n  identityVerified: true\n```\n\n\u003c/details\u003e\n\nWhen you initially retrieve the `Document`, the `person` key will contain an \narray. But you can map this to your `Person` class as follows:\n\n```php\n$user = $collection-\u003eget(\"b83e319a-7887-11eb-8deb-b9e03d2e720d\");\n$user-\u003emap('person', Person::class);\n\n// $user-\u003egetPerson() now returns a Person object.\n\n// Or you can map to an existing Person object.\n$person = new Person();\n$user-\u003emap('person', $person);\n```\n\n### Document Unique Id\n\nEvery document in the same collection must have a unique ID. \n\nBy default, when you create a new document, an ID is generated for you as a\nv1 UUID.\n\nYou can get or set a `Document` ID with the `getId()` and `setId(string $id)` \nmethods.\n\n\u003e :bulb: **Note:** Changing a `Document` ID essentially treats it as a different document, i.e. \nproviding a new unique ID will result in a new document being inserted in to your database \nwhen you save it. Likewise changing a document's ID to the ID of another document in the \ncollection will cause that document to be overwritten.\n\nIf the ID was auto generated, you can obtain a `DateTimeImmutable` representing the \ndocument's time of creation by calling its `getTime()` method:\n\n```php\n$users = $db-\u003ecollection(\"Users\");\n// Create a new Document with an auto generated UUID.\n$user = $users-\u003eget();\n// $date is a \\DateTimeImmutable\n$date = $user-\u003egetTime();\necho $date-\u003eformat('d m Y H:i');\n```\n\nIf you don't want to use an auto-generated ID for a new document, simply pass in your \nown ID to the collection's `get()` method. As long as the ID does not match any document \nin the collection's database storage, a new document will be created. Document IDs are \nstrings.\n\n```php\n$users = $db-\u003ecollection(\"Users\");\n// Create a new Document with a custom ID.\n// If this ID already exists in the Users collection, that document will be returned.\n$user = $users-\u003eget(\"user_3815\");\n```\n\n### Saving a document\n\nDocuments represented as a DocLite `Document` object provide a convenience method to \nsave the document to its collection. To save a `Document` in storage, call `save()`.\n\n```php\n$users = $db-\u003ecollection(\"Users\");\n$user = $users-\u003eget();\n$user-\u003esetUsername(\"admin\");\n$user-\u003esave();\n```\n\nIf a document has been mapped on to a custom class, you will need to save it through \nits collection instead.\n\n```php\n$users = $db-\u003ecollection(\"Users\");\n// Create a new document with an automatically generated UUID and\n// retrieved as an object of type CustomUser.\n$user = $users-\u003eget(null, CustomUser::class);\n$user-\u003esetUsername(\"admin\");\n$users-\u003esave($user);\n```\n\n### Deleting a document\n\nDocuments represented as a DocLite `Document` object provide a convenience method to\ndelete the document from its collection. To delete a `Document` in storage, \ncall `delete()`.\n\n```php\n$users = $db-\u003ecollection(\"Users\");\n$user = $users-\u003eget(\"12345\");\n$user-\u003edelete();\n```\n\nIf a document has been mapped on to a custom class, you will need to delete it \nthrough its collection instead.\n\n```php\n$users = $db-\u003ecollection(\"Users\");\n// Create a new document with an automatically generated UUID and\n// retrieved as an object of type CustomUser.\n$user = $users-\u003eget(\"12345\", CustomUser::class);\n$users-\u003edeleteDocument($user);\n```\n\n### Document validation\n\nIt is possible to add [JSON Schema](https://json-schema.org/) validation to a `Document` via the `addJsonSchema()` \nmethod. This takes a single string parameter of a valid JSON schema. If the schema \ncannot be validated, a `DatabaseException` will be thrown.\n\n```php\n$user-\u003eaddJsonSchema(file_get_contents('schema.json'));\n```\n\nOnce you have loaded a schema, every time you set a document property or try to save \nthe document, the document data will be validated against your schema. If the data fails \nto validate, a `DatabaseException` will be thrown.\n\nYou can also manually validate at any time by calling `validateJsonSchema()`.\n\n```php\n$user-\u003eaddJsonSchema(file_get_contents('schema.json'));\ntry {\n    $user-\u003evalidateJsonSchema();\n    // This will automatically call validateJsonSchema() anyway.\n    $user-\u003esave();\n    // As will this.\n    $user-\u003esetUsername(\"foobar\");\n} catch (DatabaseException $e) {\n    $params = $e-\u003egetParams();\n    $error = $params['error'];\n    echo \"Document failed to validate against JSON Schema because:\\n\".$error;\n}\n```\n\nFinally, you can unload a JSON Schema and remove the validaton by calling \n`removeJsonSchema()`.\n\n```php\n$user-\u003eremoveJsonSchema();\n```\n\n## Other info\n\n### Symfony integration\n\nAlthough there is not a specific integration with the Symfony framework, it's \ntrivial to inject DocLite as a service in to any Symfony application. Simply \ninstall DocLite via Composer as an app dependency, then modify your \n`services.yaml` as per the following example.\n\n```yaml\n    app.filedatabase:\n        class: Gebler\\Doclite\\FileDatabase\n        arguments:\n            $path: \"../var/data/app.db\"\n            $readOnly: false\n    app.memorydatabase:\n        class: Gebler\\Doclite\\MemoryDatabase\n\n    Gebler\\Doclite\\DatabaseInterface: '@app.filedatabase'\n    Gebler\\Doclite\\DatabaseInterface $memoryDb: '@app.memorydatabase'\n```\n\nYou can now typehint a `DatabaseInterface` like any other service, using the \nalias `$memoryDb` as the parameter name if you'd like a `MemoryDatabase`.\n\n### Licensing\n\nDocLite is available under the MIT license as open source software. \n\nIf you use DocLite and find it useful, I am very grateful for any support \ntowards its future development. \n\n[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate?business=info%40doclite.co.uk\u0026item_name=DocLite+PHP+library\u0026currency_code=GBP)\n\n### Bugs, issues\n\nPlease raise an issue on the project GitHub if you encounter any problems. I am \nalways interested in improving the software.\n\n### Contact the author\n\nYou can email me on info@doclite.co.uk\n","funding_links":["https://patreon.com/dwgebler","https://www.paypal.com/donate?business=info%40doclite.co.uk\u0026item_name=DocLite+PHP+library\u0026currency_code=GBP"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwgebler%2Fdoclite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdwgebler%2Fdoclite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdwgebler%2Fdoclite/lists"}