vardumper / dom-orm
DOMDocument Object Relational Mapper (ORM)
Requires
- php: ^8.2
- ext-dom: *
- ext-json: *
- ext-libxml: *
- ext-mbstring: *
- ext-xml: *
- league/config: ^1.2
- league/flysystem: ^3.28
- mnapoli/silly: ^1.9.0
- ramsey/collection: ^2.0
- ramsey/uuid: ^4.7.6
- symfony/serializer: ^6.4.8 || ^7.1.1
- symfony/yaml: ^6.4.8 || ^7.0.0
Requires (Dev)
- brainmaestro/composer-git-hooks: @dev
- friendsofphp/php-cs-fixer: ^3.59.3
- phpstan/phpstan: ^1.11.5
- phpunit/phpunit: ^10.5.20 || ^11.2.2
- symplify/easy-coding-standard: ^12.3.0
- dev-main
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1
- v1.0
- dev-dependabot/composer/phpunit/phpunit-11.5.0
- dev-dependabot/composer/phpstan/phpstan-2.0.3
- dev-dependabot/composer/symfony/yaml-7.2.0
- dev-dependabot/composer/symfony/serializer-7.2.0
- dev-dependabot/composer/composer-feae8101c0
- dev-dependabot/composer/league/flysystem-3.29.1
- dev-develop
This package is auto-updated.
Last update: 2025-02-18 21:36:53 UTC
README
This is a storage adapter for small web projects. Like any other ORM, it stores enitites (objects) into a flexible XML file. This is for developers who want to start a small project without having to spin up a database.
Features
- A very lightweight approach to persisting data into a single XML file.
- Supports local and external file storage via Flysystem (S3,Azure,Google Cloud,(S)FTP,etc.)
- Supports Many-to-one, One-to-many and Many-to-many relationships.
Full Documentation
Read the Documentation
Getting started
composer require vardumper/dom-orm
By default, the XML file is stored on your local filesystem as storage/data.xml
under the root of your project.
You can change the storage location by changing the Flysystem adapter and configuring dom-orm to use it, like so:
// config/dom-orm.php <?php return [ 'dom-orm' => [ 'flysystem' => new LocalAdapter(__DIR__ . '/storage'), 'filename' => 'data.xml', ], ];
Basic Usage
Entity
By adding PHP8 Attributes to your entity class, DOM ORM knows how to persist it.
// src/Entity/Tag.php use DOM\ORM\Entity\AbstractEntity; use DOM\ORM\Mapping as ORM; #[ORM\Item(entityType: 'tag')] class Tag extends AbstractEntity { public function __construct( #[ORM\Fragment] private readonly string $name, ) { parent::__construct(); } }
Persistance
An EntityManagerTrait can be used in controllers or services to persist entities to the XML file.
// src/Service/SomeService.php class SomeService { use DOM\ORM\Traits\EntityManagerTrait; ... public function addTag(string $name) { $tag = new Tag($name); $this->persist($tag); } }
When you want to update an existing Entity, you can use the persist
method as well.
// src/Service/SomeService.php class SomeService { use DOM\ORM\Traits\EntityManagerTrait; ... public function updateTag(string $id, string $name) { $tag = (new EntityRepository(Tag::class))->find($id); $tag->setName($name); $this->persist($tag); } }
When you want to remove an existing Entity, you can use the remove
method.
// src/Service/SomeService.php class SomeService { use DOM\ORM\Traits\EntityManagerTrait; ... public function removeTag(string $id) { (new EntityRepository(Tag::class))->remove($id); } }
Serialization
When persisting the entity, DOM ORM automatically generates a UID and adds a creation date for the entity. The built-in normalizer and encoder transforms the object into a standardized XML format and saves it.
<!-- storage/data.xml --> <data> <item type="tag" id="e34cbf80edaf490aa39113254b6cdfa9"> <fragment name="name"><![CDATA[Tagname]]></fragment> <fragment name="createdAt"><![CDATA[2024-06-17T06:30:37+00:00]]></fragment> </item> ... </data>
Querying data
Just like persisting a PHP Object to a XML format, querying data is just as easy. When you query data, internally XPath is used to find the elements, the resulting DOMNodeList is then mapped back to its Entity class object(s).
Querying data with an Entity Repository
By using the EntityRepository class, you can query data in an object-oriented way, always retrieving instances of Entity object(s).
$tagRepository = new EntityRepository(Tag::class); $tag = $tagRepository->findOneBy(['name' => 'Tagname']); // returns a single Tag object $tag = $tagRepository->find('fec69a494c3145f89af03ae3b3702e19'); // return a single Tag object $tags = $tagRepository->findAll(); // returns a Collection of Tag objects $tags = $tagRepository->findBy(['name' => 'Tagname']); // returns a Collection of Tag objects
Querying data using DOMXPath
$xml = (new DOM\ORM\Storage\StorageService())->read(); $dom = (new DOMDocument())->loadXML($xml); $xpath = new DOMXPath($dom); $tags = $xpath->query('//item[@type="tag"]'); // eg: retrieve all tags at any depth $tag = $xpath->query('//item[@type="tag" and @id="fec69a494c3145f89af03ae3b3702e19"]'); // eg: retrieve a single tag with a specific ID
Querying data using DOMDocument
$xml = (new DOM\ORM\Storage\StorageService())->read(); $dom = (new DOMDocument())->loadXML($xml); $entities = $dom->getElementsByTagName('item'); // returns a DOMNodeList of all entities
Templating
Twig
Probably the easiest way is to query for entities and pass them to your Twig templates:
$twig->render('index.twig', [ 'title' => 'Hello there!', 'tag' => (new EntityRepository(Tag::class))->find('fec69a494c3145f89af03ae3b3702e19'), ]);
Or you could just decoded some DOMElements and pass an array to Twig templates (without instantiating the object):
use EntityManagerTrait; $serializer = $this->getSerializer(); $item = $serializer->decode($dom->getELementsByTagName('item')->item(0)); // example: decode the first item into an array echo $twig->render('index.twig', [ 'title' => 'Hello there!', 'item' => $item, ]);
XSLT
You can use the XML data to transform it into HTML using XSLT.
$xml = (new DOM\ORM\Storage\StorageService())->read(); $dom = (new DOMDocument())->loadXML($xml); $xslt = (new XSLTProcessor())->importStylesheet(DOMDocument::load('path/to/stylesheet.xsl')); echo $xslt->transformToXML($dom);
Roadmap
- Add support for Many-to-many relationships using hash maps.
- Add ordering/sorting to the EntityRepository pattern.
- By providing a GraphQL endpoint, you can interact with your DOM-ORM database in a more flexible, headless way.
- Adding support for migrations (or rather a cleanup) so that removed fragments are removed from the XML file as well.
- Add support to encrypt parts or the entire XML file for better security.