PHPackages                             marekskopal/orm - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. marekskopal/orm

ActiveLibrary[Database &amp; ORM](/categories/database)

marekskopal/orm
===============

ORM

v1.0.1(2mo ago)0421↓16.7%2MITPHPPHP &gt;=8.4

Since Dec 31Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/marekskopal/orm)[ Packagist](https://packagist.org/packages/marekskopal/orm)[ RSS](/packages/marekskopal-orm/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (16)Versions (11)Used By (2)

ORM
===

[](#orm)

A lightweight Object-Relational Mapping (ORM) library for PHP.

Features
--------

[](#features)

- Simple and intuitive declaration of entities by adding `Column` Attributes to class properties
- Supports various property types including Uuid, DateTime and Enums
- Handles one-to-many, many-to-one, one-to-one, and many-to-many relationships
- Query provider for database interactions
- [Migration module](https://github.com/marekskopal/orm-migrations) for creating and updating database schema
- Very fast in comparison to other ORM libraries - see [benchmarks](https://github.com/marekskopal/orm-benchmark)

Supported Databases
-------------------

[](#supported-databases)

- MySQL
- PostgreSQL
- SQLite

Installation
------------

[](#installation)

Install via Composer:

```
composer require marekskopal/orm
```

Basic Usage
-----------

[](#basic-usage)

```
//Create DB connection - MySQL
$database = new MysqlDatabase('localhost', 'root', 'password', 'database');

//Create DB connection - PostgreSQL
$database = new PostgresDatabase('localhost', 'postgres', 'password', 'database');

//Create DB connection - SQLite
$database = new SqliteDatabase('/path/to/database.sqlite');

//Create schema
$schema = new SchemaBuilder()
    ->addEntityPath(__DIR__ . '/Entity')
    ->build();

$orm = new ORM($database, $schema);

//Create new entity
$user = new User(
    'John',
    'Doe',
);
$orm->getRepository(User::class)->persist($user);

//Find entity by id
$user = $orm->getRepository(User::class)
    ->findOne(['id' => 1]);

//Update entity
$user->firstName = 'Jane';
$orm->getRepository(User::class)->persist($user);

//Delete entity
$orm->getRepository(User::class)->delete($user);
```

Entity Declaration
------------------

[](#entity-declaration)

You can declare entities by adding `Entity` attribute to class and `Column` attribute to class properties.

```
use MarekSkopal\ORM\Attribute\Column;
use MarekSkopal\ORM\Attribute\ColumnEnum;
use MarekSkopal\ORM\Attribute\Entity;
use MarekSkopal\ORM\Enum\Type;

#[Entity]
final class User
{
    #[Column(type: Type::Int, primary: true, autoIncrement: true)]
    public int $id;

    public function __construct(
        #[Column(type: Type::Timestamp)]
        public DateTimeImmutable $createdAt,
        #[Column(type: Type::String)]
        public string $name,
        #[Column(type: Type::String, nullable: true, size: 50)]
        public ?string $email,
        #[Column(type: Type::Boolean)]
        public bool $isActive,
        #[ColumnEnum(enum: UserTypeEnum::class)]
        public UserTypeEnum $type,
    ) {
    }
}
```

Table and column names are derived from class name and parameters, but can be customized by providing additional parameters to attributes.

```
#[Entity(table: 'users')]
final class User
{
    #[Column(type: Type::String, name: 'my_last_name_column')]
    public string $lastName;
}
```

### Relationships

[](#relationships)

#### ManyToOne and OneToMany

[](#manytoone-and-onetomany)

```
#[Entity]
final class User
{
    #[ManyToOne(entityClass: Address::class)]
    public Address $address;

    #[OneToMany(entityClass: User::class)]
    public \Iterator $children;
}
```

#### OneToOne

[](#onetoone)

Use `#[OneToOne]` for a unique relationship between two entities. The owning side holds the foreign key column; the inverse side uses `mappedBy` pointing to the owning property name.

```
#[Entity]
final class User
{
    // Owning side — stores profile_id column
    #[OneToOne(entityClass: Profile::class)]
    public Profile $profile;
}

#[Entity]
final class Profile
{
    // Inverse side — no column, loaded via User::$profile FK
    #[OneToOne(entityClass: User::class, mappedBy: 'profile')]
    public ?User $user;
}
```

#### ManyToMany

[](#manytomany)

Use `#[ManyToMany]` for a join-table relationship. The owning side declares the join table and column names; the inverse side uses `mappedBy`.

Column names default to the entity short name with an `Id` suffix (e.g. `user_id`, `tag_id`) when not specified explicitly.

```
#[Entity]
final class User
{
    // Owning side — manages the user_tags join table
    #[ManyToMany(
        entityClass: Tag::class,
        joinTable: 'user_tags',
        joinColumn: 'user_id',        // defaults to user_id
        inverseJoinColumn: 'tag_id',  // defaults to tag_id
    )]
    public Collection $tags;
}

#[Entity]
final class Tag
{
    // Inverse side — loaded via User::$tags join table info
    #[ManyToMany(entityClass: User::class, mappedBy: 'tags')]
    public Collection $users;
}
```

### Cascade Operations

[](#cascade-operations)

Relations support cascade operations via the `cascade` parameter. Supported values are `CascadeEnum::Persist` and `CascadeEnum::Remove`.

```
use MarekSkopal\ORM\Schema\Enum\CascadeEnum;

#[Entity]
final class Author
{
    #[OneToMany(entityClass: Post::class, cascade: [CascadeEnum::Persist, CascadeEnum::Remove])]
    public Collection $posts;
}
```

**`CascadeEnum::Persist`** — when `persist()` is called on the owning entity, all related entities are persisted automatically:

```
$post1 = new Post('First Post', $author);
$post2 = new Post('Second Post', $author);
$author = new Author('John', new Collection([$post1, $post2]));

// Persists author and both posts in the correct order
$orm->getRepository(Author::class)->persist($author);
```

**`CascadeEnum::Remove`** — when `delete()` is called on the owning entity, all related entities are deleted automatically before the owner (to avoid FK constraint violations):

```
$author = $orm->getRepository(Author::class)->findOne(['id' => 1]);

// Deletes all posts belonging to the author, then deletes the author
$orm->getRepository(Author::class)->delete($author);
```

Cascade is supported on `OneToMany`, `ManyToOne`, `OneToOne`, and `ManyToMany` relations. For `ManyToMany`, cascade remove deletes the join table rows; cascade persist syncs the join table after persisting.

### Dates

[](#dates)

You can use `DateTime` or `DateTimeImmutable` properties in entities. The library will automatically convert datetime or timestamp columns values to those to objects.

```
#[Entity]
final class User
{
    #[Column(type: Type::Timestamp)]
    public DateTimeImmutable $createdAt;

    #[Column(type: Type::DateTime)]
    public DateTime $updatedAt;
}
```

### Enums

[](#enums)

You can use native PHP enums in entities. The library will automatically convert enum column values to enum objects.

```
use MarekSkopal\ORM\Attribute\ColumnEnum;

#[Entity]
final class User
{
    #[ColumnEnum(enum: UserTypeEnum::class)]
    public UserTypeEnum $type;
}
```

Repository declaration
----------------------

[](#repository-declaration)

You can declare you repositories by extending `AbstractRepository` class and providing repository class in `Entity` attribute on entity class.

```
use MarekSkopal\ORM\Repository\AbstractRepository;

/** @extends AbstractRepository */
class UserRepository extends AbstractRepository
{

}
```

```
#[Entity(repositoryClass: UserRepository::class)]
final class User
{

}
```

Queries
-------

[](#queries)

You can use `QueryProvider` to create queries.

```
$queryProvider = $orm->getQueryProvider();
```

### Select

[](#select)

You can create select queries using `Select` builder.

```
$user = $queryProvider->select(User::class)
    ->where(['id' => 1])
    ->fetchOne();
```

#### Where

[](#where)

You can use `where` method to filter results.

Multiple AND conditions can be crated either by passing array of conditions or by chaining `where` method.

```
//Array of conditions
$user = $queryProvider->select(User::class)
    ->where([
        'id' => 1,
        'isActive' => true
    ])
    ->fetchOne();

//Chained conditions
$user = $queryProvider->select(User::class)
    ->where(['id' => 1])
    ->where(['isActive' => true])
    ->fetchOne();
```

OR conditions can be created by using `orWhere` method.

```
$user = $queryProvider->select(User::class)
    ->where(['id' => 1])
    ->orWhere(['first_name' => 'John'])
    ->fetchOne();
```

You can also use `where` method with nested conditions by passing function.

```
// Create nested condition: (id = 1 AND (first_name = 'John' OR last_name = 'Doe'))
$user = $queryProvider->select(User::class)
    ->where(['id' => 1])
    ->where(function (Where $where) {
        $where->where(['first_name' => 'John'])
            ->orWhere(['last_name' => 'Doe']);
    })
    ->fetchOne();
```

You can pass another instance of `Select` object to where method to create subquery.

```
$subquery = $queryProvider->select(Address::class)
    ->columns(['user_id'])
    ->where(['city' => 'Brno']);

$user = $queryProvider->select(User::class)
    ->where('address_id', 'in', $subquery)
    ->fetchOne();
```

### Insert

[](#insert)

You can insert entities using `Insert` builder.

```
$user = new User(
    'John',
    'Doe',
);

$queryProvider->insert(User::class)
    ->entity($user)
    ->execute();

//created entity will have id set automatically
```

#### Insert multiple entities

[](#insert-multiple-entities)

You can insert multiple entities at once in one insert query.

```

$userA = new User(
    'John',
    'Doe',
);

$userB = new User(
    'Jane',
    'Doe',
);

$queryProvider->insert(User::class)
    ->entity($userA)
    ->entity($userB)
    ->execute();
```

### Update

[](#update)

You can update entities using `Update` builder.

```
$user = $queryProvider->select(User::class)
    ->where(['id' => 1])
    ->fetchOne();

$user->firstName = 'Jane';

$queryProvider->update(User::class)
    ->entity($user)
    ->execute();
```

### Delete

[](#delete)

You can delete entities using `Delete` builder.

```
$user = $queryProvider->select(User::class)
    ->where(['id' => 1])
    ->fetchOne();

$queryProvider->delete(User::class)
    ->entity($user)
    ->execute();
```

#### Delete multiple entities

[](#delete-multiple-entities)

You can delete multiple entities at once in one delete query.

```

$userA = $queryProvider->select(User::class)
    ->where(['id' => 1])
    ->fetchOne();

$userB = $queryProvider->select(User::class)
    ->where(['id' => 2])
    ->fetchOne();

$queryProvider->delete(User::class)
    ->entity($userA)
    ->entity($userB)
    ->execute();
```

Transactions
------------

[](#transactions)

Use `getTransactionProvider()` to run operations inside a database transaction. The callback is committed on success and automatically rolled back if any exception is thrown.

```
$orm->getTransactionProvider()->transaction(function () use ($orm): void {
    $orm->getRepository(User::class)->persist($userA);
    $orm->getRepository(User::class)->persist($userB);
});
```

You can also manage the transaction manually:

```
$tp = $orm->getTransactionProvider();

$tp->beginTransaction();

try {
    $orm->getRepository(User::class)->persist($user);
    $tp->commit();
} catch (\Throwable $e) {
    $tp->rollback();
    throw $e;
}
```

Nesting transactions is not supported — calling `transaction()` inside an active transaction throws a `TransactionException`.

Long-running applications
-------------------------

[](#long-running-applications)

If you are using ORM in long-running PHP applications like FrankenPHP, Roadrunner or Swoole, you should call `clear` method on ORM cache after each request to free memory.

```
$orm->getEntityCache()->clear();
```

###  Health Score

47

—

FairBetter than 94% of packages

Maintenance86

Actively maintained with recent releases

Popularity17

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~48 days

Recently: every ~13 days

Total

10

Last Release

70d ago

Major Versions

v0.9.7 → v1.0.02026-03-08

### Community

Maintainers

![](https://www.gravatar.com/avatar/2eeb4a0e8bc7e2ce26f00193dac3973909911a18079b18956b03af66b901421a?d=identicon)[marekskopal](/maintainers/marekskopal)

---

Top Contributors

[![marekskopal](https://avatars.githubusercontent.com/u/33967656?v=4)](https://github.com/marekskopal "marekskopal (120 commits)")

---

Tags

ormmysqlsqlitesqlquery builderdata mapper

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/marekskopal-orm/health.svg)

```
[![Health](https://phpackages.com/badges/marekskopal-orm/health.svg)](https://phpackages.com/packages/marekskopal-orm)
```

###  Alternatives

[cycle/orm

PHP DataMapper ORM and Data Modelling Engine

1.3k835.4k65](/packages/cycle-orm)[usmanhalalit/pixie

A lightweight, expressive, framework agnostic query builder for PHP.

6872.2M15](/packages/usmanhalalit-pixie)[cycle/database

DBAL, schema introspection, migration and pagination

64690.9k31](/packages/cycle-database)[nilportugues/sql-query-builder

An elegant lightweight and efficient SQL QueryInterface BuilderInterface supporting bindings and complicated query generation.

425239.4k6](/packages/nilportugues-sql-query-builder)[opis/database

A database abstraction layer over PDO, that provides a powerful and intuitive query builder, bundled with an easy to use schema builder

10184.2k3](/packages/opis-database)[pecee/pixie

Lightweight, fast query-builder for PHP based on Laravel Eloquent but with less overhead.

4128.7k8](/packages/pecee-pixie)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
