toutaphp / ogam-data-mapper
A MyBatis-inspired SQL mapping framework for PHP. Write the SQL you want, map results to objects automatically.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/toutaphp/ogam-data-mapper
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- pestphp/pest: ^3.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
- symfony/console: ^7.0
This package is auto-updated.
Last update: 2026-02-21 16:08:29 UTC
README
Ogam (ᚑᚌᚐᚋ) is a MyBatis-inspired SQL mapping framework for PHP. Write the SQL you want, map results to objects automatically.
Ogam is the ancient Celtic alphabet used by Druids to inscribe sacred knowledge. Like Ogam mapped symbols to meaning, this library maps SQL results to PHP objects.
Features
- SQL-First: Write exactly the SQL you want, optimized for your use case
- Declarative Mapping: Configure how results become objects via XML or attributes
- Dynamic SQL: Build queries conditionally with
<if>,<foreach>,<where>,<set> - Type Handlers: Automatic conversion between PHP and SQL types (DateTime, Enums, JSON)
- Zero Magic: Every SQL executed is visible and predictable
- Framework Agnostic: Works standalone or with Symfony/Laravel
Requirements
- PHP 8.3+
- PDO extension
- MySQL, PostgreSQL, or SQLite
Installation
composer require toutaphp/ogam-data-mapper
Quick Start
1. Define Your Entity
<?php namespace App\Entities; class User { public function __construct( public int $id, public string $name, public string $email, public DateTimeImmutable|null $email_verified_at, public string $password, public string|null $remember_token, public datetime $created_at, public datetime $updated_at, ) {} }
2. Data mapper configuration
<!-- config/ogam.xml --> <?xml version="1.0" encoding="UTF-8"?> <configuration> <typeAliases> <typeAlias alias="User" type="App\Entity\User"/> </typeAliases> <typeHandlers> <typeHandler phpType="App\Enum\Status" handler="App\Handlers\StatusTypeHandler"/> </typeHandlers> <environments default="development"> <environment id="development"> <dataSource type="POOLED"> <property name="driver" value="mysql"/> <property name="host" value=""/> <property name="port" value=""/> <property name="dbname" value=""/> <property name="username" value=""/> <property name="password" value=""/> </dataSource> </environment> </environments> <mappers> <mapper resource="../database/mappers/UserMapper.xml"/> </mappers> </configuration>
3. Create XML Mapper
<!-- mappers/UserMapper.xml --> <?xml version="1.0" encoding="UTF-8"?> <mapper namespace="App\Mappers\UserMapper"> <select id="findById" resultType="App\Entity\User"> SELECT id, name, email, email_verified_at, password, remember_token, created_at, updated_at FROM users WHERE id = #{id} </select> <select id="findByStatus" resultType="App\Entity\User"> SELECT id, name, email, email_verified_at, password, remember_token, created_at, updated_at FROM users <where> <if test="status != null"> AND status = #{status} </if> </where> </select> <insert id="insert" useGeneratedKeys="true" keyProperty="id"> INSERT INTO users (name, email, email_verified_at, password, remember_token, created_at, updated_at) VALUES (#{name}, #{email}, #{emailVerifiedAt}, #{password}, #{rememberToken}, #{createdAt}, #{updatedAt}) </insert> </mapper>
4. Define Mapper Interface
<?php namespace App\Mappers; use Touta\Ogam\Attribute\Mapper; use App\Entities\User; #[Mapper(resource: 'database/mappers/UserMapper.xml')] interface UserMapper { public function findById(int $id): ?User; public function findByStatus(?Status $status): array; public function insert(User $user): int; }
5. Use the Mapper
<?php use Touta\Ogam\SessionFactoryBuilder; // Build session factory $factory = (new SessionFactoryBuilder()) ->withConfiguration(base_path('config/ogam.xml')) ->build(); // Open session $session = $factory->openSession(); // Get mapper $userMapper = $session->getMapper(UserMapper::class); // Query $user = $userMapper->findById(1); // Insert $newUser = new User(0, 'john', 'john@example.com', Status::ACTIVE, new DateTimeImmutable()); $userMapper->insert($newUser); echo $newUser->id; // Generated ID // Commit and close $session->commit(); $session->close();
Documentation
Parameter Syntax
Ogam uses MyBatis-style parameter syntax:
<!-- Value binding (safe, parameterized) --> SELECT * FROM users WHERE id = #{id} AND status = #{status} <!-- Identifier substitution (for column/table names) --> SELECT * FROM users ORDER BY ${orderColumn} ${orderDir}
Dynamic SQL
<select id="search" resultType="User"> SELECT * FROM users <where> <if test="name != null"> AND name LIKE #{name} </if> <if test="email != null"> AND email = #{email} </if> <if test="statuses != null"> AND status IN <foreach collection="statuses" item="s" open="(" separator="," close=")"> #{s} </foreach> </if> </where> </select>
Why Ogam?
| vs Doctrine | Ogam Advantage |
|---|---|
| Hydration overhead | Constructor injection, no reflection |
| Hidden SQL | Full SQL visibility and control |
| Learning curve | Write SQL you know, not DQL |
| vs Eloquent | Ogam Advantage |
|---|---|
| Performance | 2-3x faster (no Active Record overhead) |
| SQL control | Write optimized SQL directly |
| Testability | Clean separation, no database for unit tests |
Part of Toutā Framework
Ogam is part of the Toutā Framework ecosystem:
- Toutā: Core framework
- Cosan: HTTP router
- Fíth: Template engine
- Nasc: Dependency injection
- Ogam: Data mapping (this library)
Each component works standalone or together.
Contributing
See CONTRIBUTING.md for guidelines.
License
MIT License. See LICENSE for details.