michel / paper-orm
PaperORM is a lightweight Object-Relational Mapping (ORM) library
Requires
- php: >=8.2
- ext-ctype: *
- ext-iconv: *
- ext-json: *
- ext-pdo: *
- michel/console: ^1.0
- michel/filesystem: ^1.0
- michel/lock: ^1.0
- michel/michel-package-starter: ^1.0
- michel/php-sql-mapper: ^2.0
- michel/psr14-event-dispatcher: ^1.0
- psr/log: ^1.1|^2.0|^3.0
Requires (Dev)
- michel/unitester: ^1.0.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-28 12:46:52 UTC
README
The ORM that does what you wrote, and nothing else.
Map your entities with attributes, query them through repositories, migrate your schema. No lazy loading, no cascade, no transaction you did not open. Every query runs because you asked for it.
PHP 8.2+ 路 SQLite 路 MariaDB / MySQL 路 Symfony bundle
馃摉 Documentation 路 Website
Install
composer require michel/paper-orm # any PHP project composer require michel/paper-orm-bundle # Symfony
Quick start
use Michel\PaperORM\EntityManager; use Michel\PaperORM\PaperConfiguration; $em = EntityManager::createFromConfig( PaperConfiguration::fromDsn('sqlite:///var/data/app.sqlite') );
use Michel\PaperORM\Entity\EntityInterface; use Michel\PaperORM\Mapping\Entity; use Michel\PaperORM\Mapping\Column\{PrimaryKeyColumn, StringColumn, BoolColumn}; #[Entity(table: 'user')] class User implements EntityInterface { #[PrimaryKeyColumn] private ?int $id = null; #[StringColumn] private ?string $email = null; #[BoolColumn(name: 'is_active')] private bool $active = true; public function getPrimaryKeyValue(): ?int { return $this->id; } // getters and setters... }
$em->persist((new User())->setEmail('jane@example.com')); $em->flush(); $users = $em->getRepository(User::class) ->findBy(['active' => true]) ->orderBy('email') ->limit(20) ->toObject(); // User[]
Why PaperORM
| No surprise query | No lazy loading: a relation is loaded with with() or has(), in the same query. Nothing runs when you read a property. |
| Paginate one-to-many | limit(10) returns 10 users, even when each brings 50 posts in the same join. Filter and sort on relations, the page stays right. |
| Your transactions | flush() never opens a transaction behind your back. |
| Migrations as SQL | paper:migration:diff writes a plain .sql file you can read; paper:migration:migrate runs it. |
| Small and fast | About 21,000 lines of PHP with its dependencies, against about 171,000 for Doctrine ORM. Faster than or level with Doctrine on every scenario of the bundled benchmark. |
What PaperORM will never do
lazy-load a relation 路 cascade a persist or a delete 路 open a transaction for you 路 invent a query language 路 query when you read a property 路 require a framework
Documentation
The documentation covers configuration, entities and relations, migrations, persistence, generated values, repositories, pagination, the query builder, transactions, events, the registry and the Symfony bundle.
Status
PaperORM is in alpha: the API may still change between 0.x versions. Bug reports and ideas are welcome in the issues.