PHPackages                             alin-o/my-simple-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. alin-o/my-simple-orm

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

alin-o/my-simple-orm
====================

Mysql/Mariadb Simple ORM

2.0.9(2mo ago)1637↓50%GPL-3.0-or-laterPHPPHP &gt;=7.4.0

Since Mar 2Pushed 2mo ago1 watchersCompare

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

READMEChangelog (10)Dependencies (3)Versions (35)Used By (0)

MyOrm
=====

[](#myorm)

A Simple ORM for MySQL and MariaDB
----------------------------------

[](#a-simple-orm-for-mysql-and-mariadb)

AlinO\\MyOrm is a lightweight Object-Relational Mapping (ORM) library for PHP 7.4 and above. It simplifies database interactions with MySQL and MariaDB while offering powerful features such as relationship management, lifecycle hooks, AES encryption, and utility methods for modern web applications.

Features
--------

[](#features)

- Automatic Table Detection: Derives table names from class names if not explicitly set.
- Flexible Database Connections: Supports multiple database connections per model.
- Lifecycle Hooks: Provides hooks like `beforeCreate`, `afterCreate`, etc., for custom logic.
- Relationships: Supports `BELONGS_TO`, `HAS_ONE`, `HAS_MANY`, `HAS_MANY_THROUGH` and `BELONGS_TO_MANY`.
- Eloquent-Style Relationships: Define relationships using expressive, chainable methods (`hasOne`, `hasMany`, `belongsTo`, `belongsToMany`).
- AES Encryption: Automatically encrypts/decrypts specified fields on the current model instance.
- JSON Field Casting: Automatically encode and decode attributes to and from JSON.
- Search Indexing: Updates search indexes based on defined fields.
- Utility Methods: `assureUnique`, `list`, `toArray`, etc.
- Efficient Related Data Retrieval: Methods like `getRelatedColumns` and `getRelatedIds` for fetching specific related data without full model hydration.

see how it compares to Laravel's Eloquent ORM in [ORM\_Comparison.md](./ORM_Comparison.md)

Requirements
------------

[](#requirements)

```
PHP 7.4 or higher
Composer
MySQL or MariaDB
AlinO\Db\MysqliDb (included as a dependency)

```

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

[](#installation)

Install the library using Composer. Ensure Composer is installed on your system, then run:

```
composer require alin-o/my-simple-orm
```

This adds the library to your composer.json file and installs it in the vendor directory.

Configuration
-------------

[](#configuration)

Set up the default database connection using the MysqliDb class provided by the library:

```
$mdb = new \AlinO\Db\MysqliDb('localhost', 'username', 'password', 'database_name');
```

Models can override the default connection by defining static::$database:

```
class User extends \AlinO\MyOrm\Model {
    protected static $database = 'custom_db';
}
```

Then, configure the custom connection:

```
\AlinO\MyOrm\Model::setConnection(new \AlinO\Db\MysqliDb('host', 'user', 'pass', 'custom_database_name'), 'custom_db');
```

Defining Models
---------------

[](#defining-models)

Extend the Model class and customize properties as needed:

```
class User extends \AlinO\MyOrm\Model {
    protected static $table = 'users';               // Table name
    protected static $idField = 'id';                // Primary key
    protected static $select = 'id, username, email'; // Fields to select, default *
    protected static $emptyFields = ['status'];      // Fields initialized to 0
    protected static $extraFields = ['temp_data'];   // Non-persisted fields
    protected static $aes_fields = ['email'];        // Fields to encrypt
    protected static $json_fields = ['settings'];    // Fields to cast to/from JSON
    protected static $listSorting = ['username', 'ASC']; // Default sorting for list()
    protected static $relations = [                  // Relationships
        'addresses' => [Model::HAS_MANY, Address::class, 'user_id'],
        'profile' => [Model::HAS_ONE, Profile::class, 'user_id'],
    ];
}
```

If `$table` is not set, it’s automatically derived from the class name (e.g., User becomes user).

Usage
-----

[](#usage)

### Basic CRUD Operations

[](#basic-crud-operations)

#### Creating Records

[](#creating-records)

Create a new record by instantiating a model and saving it:

```
$user = new User();
$user->username = 'johndoe';
$user->email = 'johndoe@example.com';
$user->save();
```

Or use the create method:

```
$user = User::create(['username' => 'janedoe', 'email' => 'janedoe@example.com']);
```

#### Retrieving Records

[](#retrieving-records)

Find a record by ID:

```
$user = User::find(1);
```

Find a record by field:

```
$user = User::find('janedoe@example.com', 'email');
```

Find multiple records by IDs:

```
$users = User::findAll([1, 2, 3]);
```

Find multiple records by field:

```
$user = User::find(['johndoe@example.com', 'janedoe@example.com'], 'email');
```

List all records:

```
$users = User::list(); // Returns an array keyed by ID
```

List specific fields:

```
$usernames = User::list('username'); // Returns username values keyed by ID
```

List specific fields by key:

```
$usernames = User::list('email', 'username'); // Returns email values keyed by username
```

#### Updating Records

[](#updating-records)

Update a record and save:

```
$user->username = 'johnsmith';
$user->save();
```

Or update directly:

```
$user->update(['username' => 'johnsmith']);
```

#### Deleting Records

[](#deleting-records)

Delete a record:

```
$user->delete();
```

### Relationships

[](#relationships)

Define relationships using the $relations array:

#### HAS\_MANY

[](#has_many)

```
class User extends \AlinO\MyOrm\Model {
    protected static $relations = [
        'addresses' => [Model::HAS_MANY, Address::class, 'user_id'],
    ];
}

$addresses = $user->addresses; // Returns an array of Address instances
foreach ($addresses as $address) {
    echo $address->street;
}
```

#### HAS\_ONE

[](#has_one)

```
class User extends \AlinO\MyOrm\Model {
    protected static $relations = [
        'profile' => [Model::HAS_ONE, Profile::class, 'user_id'],
    ];
}

$profile = $user->profile; // Returns a Profile instance or null
echo $profile->bio;
```

#### BELONGS\_TO

[](#belongs_to)

```
class Address extends \AlinO\MyOrm\Model {
    protected static $relations = [
        'user' => [Model::BELONGS_TO, User::class, 'user_id'],
    ];
}

$address = Address::find(1);
$user = $address->user; // Returns the User instance
echo $user->username;
```

#### HAS\_MANY\_THROUGH

[](#has_many_through)

For many-to-many relationships:

```
class User extends \AlinO\MyOrm\Model {
    protected static $relations = [
        'roles' => [Model::HAS_MANY_THROUGH, Role::class, 'role_id', 'user_roles', 'user_id'],
    ];
}

$roles = $user->roles; // Returns an array of Role instances
foreach ($roles as $role) {
    echo $role->name;
}
```

#### BELONGS\_TO\_MANY

[](#belongs_to_many)

For many-to-many relationships (functionally identical to `HAS_MANY_THROUGH`):

```
class Role extends \AlinO\MyOrm\Model {
    protected static $relations = [
        'users' => [Model::BELONGS_TO_MANY, User::class, 'user_id', 'user_roles', 'role_id'],
    ];
}

$users = $role->users; // Returns an array of User instances
foreach ($users as $user) {
    echo $user->username;
}
```

### Eloquent-Style Relationships

[](#eloquent-style-relationships)

In addition to the `$relations` array, you can define relationships as methods on your model for a more expressive and flexible approach. This allows for lazy loading of related models.

#### hasOne

[](#hasone)

Define a one-to-one relationship.

```
class User extends \AlinO\MyOrm\Model {
    public function profile() {
        return $this->hasOne(Profile::class, 'user_id');
    }
}

$profile = $user->profile(); // Returns the Profile instance
```

#### hasMany

[](#hasmany)

Define a one-to-many relationship.

```
class User extends \AlinO\MyOrm\Model {
    public function posts() {
        return $this->hasMany(Post::class, 'user_id');
    }
}

$posts = $user->posts(); // Returns an array of Post instances
```

#### belongsTo

[](#belongsto)

Define the inverse of a one-to-one or one-to-many relationship.

```
class Post extends \AlinO\MyOrm\Model {
    public function user() {
        return $this->belongsTo(User::class, 'user_id');
    }
}

$user = $post->user(); // Returns the User instance
```

#### belongsToMany

[](#belongstomany)

Define a many-to-many relationship.

```
class User extends \AlinO\MyOrm\Model {
    public function roles() {
        return $this->belongsToMany(Role::class, 'user_roles', 'user_id', 'role_id');
    }
}

$roles = $user->roles(); // Returns an array of Role instances
```

#### hasManyThrough

[](#hasmanythrough)

Define a "has-many-through" relationship.

```
class Project extends \AlinO\MyOrm\Model {
    public function deployments() {
        return $this->hasManyThrough(Deployment::class, Environment::class, 'project_id', 'environment_id');
    }
}

$deployments = $project->deployments(); // Returns an array of Deployment instances
```

### Advanced Querying

[](#advanced-querying)

Use the db() method to access the underlying MysqliDb instance for complex queries:

```
$usersData = User::db()
    ->where('active', 1)
    ->orderBy('created_at', 'ASC')
    ->get(User::getTable());
$users = array_map(fn($data) => new User($data), $usersData);
```

Note: the underlying MysqliDb provides a built-in query builder for chaining like where()-&gt;orderBy().

### Serialization

[](#serialization)

Convert a model to an array:

```
$data = $user->toArray(); // Includes database fields
$dataWithExtra = $user->toArray(true); // Includes extra fields
```

Include related data using `with()`:

The `with()` method specifies relations to be included when `toArray()` is called.

- By default (e.g., `with('addresses')`), it includes an array of associative arrays for the related models. Each associative array will contain the primary ID of the related model.
- You can specify which columns from the related models to include using the format `'relationName:column1,column2'` (e.g., `with('addresses:street,city')`). The primary ID of the related model will always be included in addition to your specified columns.

```
// Default: Fetching IDs of related addresses
$user = User::find(1)->with('addresses');
$data = $user->toArray();
// $data['addresses'] will be like:
// [
//   ['id' => 101], // Assuming Address ID is 101 and 'id' is its primary key
//   ['id' => 102]  // Assuming Address ID is 102
// ]

// Specify columns for the related 'addresses'
$user = User::find(1);
$data = $user->with('addresses:street,city')->toArray();
// $data['addresses'] will be like:
// [
//   ['id' => 101, 'street' => '123 Main St', 'city' => 'Anytown'],
//   ['id' => 102, 'street' => '456 Oak Ave', 'city' => 'Otherville']
// ]

// For BELONGS_TO or HAS_ONE relations, the structure is an array containing zero or one associative array:
$address = Address::find(101);
$addressData = $address->with('user:username')->toArray(); // Assuming Address BELONGS_TO User
// $addressData['user'] could be: [['id' => 1, 'username' => 'johndoe']]
// or [] if no related user is found or the foreign key is null.
```

Get a subset of fields:

```
$partial = $user->only('username, email');
```

Convert to JSON:

```
$json = (string) $user; // Uses __toString()
```

### Advanced Relationship Data Retrieval

[](#advanced-relationship-data-retrieval)

For more direct and potentially performant ways to fetch data from related models without full model hydration, you can use the following methods:

#### `getRelatedColumns(string $relationName, array $columns): array`

[](#getrelatedcolumnsstring-relationname-array-columns-array)

This method fetches specific columns from related models.

- **Parameters**:
    - `$relationName`: The name of the relation (e.g., `'posts'`).
    - `$columns`: An array of column names to retrieve from the related model (e.g., `['title', 'slug']`).
- **Returns**: An array of associative arrays. Each inner array represents a related record and contains the requested columns. The primary ID field of the related model is always included in the results, even if not explicitly specified in the `$columns` array.

```
class User extends \AlinO\MyOrm\Model {
    protected static $relations = [
        'posts' => [Model::HAS_MANY, Post::class, 'user_id'],
    ];
}

$user = User::find(1);

// Get 'title' and 'slug' for all posts by this user
$postDetails = $user->getRelatedColumns('posts', ['title', 'slug']);
// $postDetails might look like:
// [
//   ['id' => 10, 'title' => 'My First Post', 'slug' => 'my-first-post'], // 'id' is Post's primary key
//   ['id' => 15, 'title' => 'Another Update', 'slug' => 'another-update']
// ]
```

#### `getRelatedIds(string $relationName): array`

[](#getrelatedidsstring-relationname-array)

This method efficiently retrieves a flat array of primary key IDs for the related models. It utilizes `getRelatedColumns` internally to fetch only the ID column.

```
$user = User::find(1);
$postIds = $user->getRelatedIds('posts'); // Returns, for example: [10, 15]
```

### Lifecycle Hooks

[](#lifecycle-hooks)

Override protected methods to hook into lifecycle events:

```
class User extends \AlinO\MyOrm\Model {
    protected function beforeCreate(): bool {
        if (empty($this->username)) {
            return false; // Prevents creation
        }
        return true;
    }

    protected function afterCreate() {
        // Example: Log creation
    }
}
```

Available hooks: `beforeCreate`, `afterCreate`, `beforeUpdate`, `afterUpdate`, `beforeDelete`, `afterDelete`.

### AES Encryption

[](#aes-encryption)

#### Set AES Key

[](#set-aes-key)

The `@aes_key` session variable is required at the database level for AES encryption and decryption to work. Set it after establishing your database connection:

```
$mdb = new \AlinO\Db\MysqliDb('localhost', 'username', 'password', 'database_name');
$aesKey = getenv('DB_AES'); // Or your preferred way to get the key
if (!empty($aesKey)) {
    $mdb->rawQuery("SET @aes_key = SHA2(?, 512)", [$aesKey]);
}
```

Fields listed in a model's `static $aes_fields` array are automatically encrypted when saved and decrypted when retrieved directly on *that model instance* (e.g., when accessing `$user->email`).

```
class User extends \AlinO\MyOrm\Model {
    protected static $aes_fields = ['email'];
}

$user->email = 'secret@example.com';
$user->save();
echo $user->email; // Outputs: secret@example.com (decrypted)
```

### Search Indexing

[](#search-indexing)

Define fields for search indexing:

```
class Product extends \AlinO\MyOrm\Model {
    protected static $searchField = 'search_index';
    protected static $searchIndex = ['name', 'description'];
}
```

The `search_index` field is updated automatically when the model is saved, containing terms from name and description.

### Utility Methods

[](#utility-methods)

#### assureUnique: Check if a field value is unique.

[](#assureunique-check-if-a-field-value-is-unique)

```
$existingId = User::assureUnique('username', 'johndoe');
if ($existingId) {
    echo "Username taken by ID: $existingId";
}
```

#### of: Scope a query to only include models belonging to a specific owner.

[](#of-scope-a-query-to-only-include-models-belonging-to-a-specific-owner)

```
// Get all posts for a specific user
$posts = Post::of($user)->get();
```

#### load: Load a model instance by class name and ID.

[](#load-load-a-model-instance-by-class-name-and-id)

```
$user = Model::load('User', 1);
// or
$user = Model::load(\App\Models\User::class, 1);
```

#### getChanges: Get an array of the model's changed attributes.

[](#getchanges-get-an-array-of-the-models-changed-attributes)

```
$user->username = 'new-username';
$changes = $user->getChanges(); // ['username' => 'new-username']
```

### Database errors are thrown as DbException:

[](#database-errors-are-thrown-as-dbexception)

```
try {
    $user = User::create(['username' => 'newuser']);
} catch (\AlinO\Db\DbException $e) {
    echo "Error: " . $e->getMessage();
}
```

Testing
-------

[](#testing)

configure database connection in `.env.testing`

```
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=test
DB_USER=test
DB_PASS=secret
DB_AES=aestest

```

then run `phpunit`

Contributing
------------

[](#contributing)

Contributions are welcome! Fork the repository on GitHub and submit a pull request. For bug reports or feature requests, please open an issue. ""

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance84

Actively maintained with recent releases

Popularity18

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 94.1% 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 ~13 days

Recently: every ~3 days

Total

29

Last Release

81d ago

Major Versions

1.6.7 → 2.0.12026-01-16

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/33296641?v=4)[Alin Olteanu](/maintainers/alin-o)[@alin-o](https://github.com/alin-o)

---

Top Contributors

[![carapa-ai](https://avatars.githubusercontent.com/u/263157370?v=4)](https://github.com/carapa-ai "carapa-ai (48 commits)")[![cursoragent](https://avatars.githubusercontent.com/u/199161495?v=4)](https://github.com/cursoragent "cursoragent (2 commits)")[![google-labs-jules[bot]](https://avatars.githubusercontent.com/in/842251?v=4)](https://github.com/google-labs-jules[bot] "google-labs-jules[bot] (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/alin-o-my-simple-orm/health.svg)

```
[![Health](https://phpackages.com/badges/alin-o-my-simple-orm/health.svg)](https://phpackages.com/packages/alin-o-my-simple-orm)
```

###  Alternatives

[doctrine/orm

Object-Relational-Mapper for PHP

10.2k285.3M6.2k](/packages/doctrine-orm)[jdorn/sql-formatter

a PHP SQL highlighting library

3.9k115.1M102](/packages/jdorn-sql-formatter)[illuminate/database

The Illuminate Database package.

2.8k52.4M9.4k](/packages/illuminate-database)[ramsey/uuid-doctrine

Use ramsey/uuid as a Doctrine field type.

90440.3M211](/packages/ramsey-uuid-doctrine)[reliese/laravel

Reliese Components for Laravel Framework code generation.

1.7k3.4M16](/packages/reliese-laravel)[wildside/userstamps

Laravel Userstamps provides an Eloquent trait which automatically maintains `created\_by` and `updated\_by` columns on your model, populated by the currently authenticated user in your application.

7511.7M13](/packages/wildside-userstamps)

PHPackages © 2026

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