https://github.com/jahidulpabelislam/orm
Simple & lightweight ORM for PHP.
https://github.com/jahidulpabelislam/orm
database entity model orm php
Last synced: 5 months ago
JSON representation
Simple & lightweight ORM for PHP.
- Host: GitHub
- URL: https://github.com/jahidulpabelislam/orm
- Owner: jahidulpabelislam
- License: gpl-3.0
- Created: 2020-01-22T21:40:36.000Z (over 6 years ago)
- Default Branch: 2.x
- Last Pushed: 2026-02-15T19:44:01.000Z (5 months ago)
- Last Synced: 2026-02-15T19:51:09.289Z (5 months ago)
- Topics: database, entity, model, orm, php
- Language: PHP
- Homepage:
- Size: 186 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# ORM
[](https://www.codefactor.io/repository/github/jahidulpabelislam/orm)
[](https://packagist.org/packages/jpi/orm)
[](https://packagist.org/packages/jpi/orm)
[](https://packagist.org/packages/jpi/orm)
[](https://packagist.org/packages/jpi/orm)

A super simple & lightweight ORM following the active record pattern.
This has been kept very simple stupid (KISS), with minimal validation (PHP type errors only) to reduce complexity in the library and maximize performance for consumer developers. Therefore, please make sure to add your own validation if using user inputs in any database queries.
## Features
- Easy to configure for your project's entities
- Supporting scalar types, array, and date/time and relationships `Many-to-One`, `One-to-Many` and `One-to-One`
- Fluent, chainable API for CRUD operations, using integrated query builder from [jpi/query](https://packagist.org/packages/jpi/query)
## Dependencies
- PHP 8.0+
- Composer
- PHP PDO
- MySQL 5+
- [jpi/database](https://packagist.org/packages/jpi/database) v2
- [jpi/query](https://packagist.org/packages/jpi/query) v2
## Installation
Use [Composer](https://getcomposer.org/)
```bash
$ composer require jpi/orm
```
### Properties and Methods for Setup
You will need to extend `\JPI\ORM\Entity` and then define the following:
#### `getDatabase(): \JPI\Database`
This method must be implemented to provide the database connection for this entity. `\JPI\Database` is just an extension of `PDO` - you can find out more [here](https://packagist.org/packages/jpi/database).
#### `$table: string`
The database table name for this entity.
#### `$dataMapping: array`
This array defines the structure of your entity and maps to your database columns. Each key is a column name and the value is an array with:
- `type` (required): One of: `string`, `int`, `float`, `array`, `date`, `date_time`, `belongs_to`, `has_one`, `has_many`
- `default_value`
- `separator`: The separator for `array` type columns when stored as delimited strings (defaults to `","`)
- `entity`: The related entity class name (required for relationship types)
- `column`
- The database column behind this for `belongs_to` type (defaults to `{key}_id`)
- The key in related entity for `has_many` & `has_one` types that links back to this
- `cascade_delete`: Whether to delete related entities when this entity is deleted (for `has_one` and `has_many` types)
- `cascade_clone`: Whether to clone related entities when this entity is cloned via `clone` - essentially creates new records with same data, else it sets to null/empty (for `has_one` and `has_many` types)
```php
protected static array $dataMapping = [
"name" => [
"type" => "string",
],
"sku" => [
"type" => "string",
],
"price" => [
"type" => "float",
"default_value" => 0.00,
],
"categories" => [
"type" => "array",
"separator" => ",", // Optional, defaults to ","
],
"created_at" => [
"type" => "date_time",
],
];
```
#### `$columnPrefix: string` - optional
Some database designers like to prefix their table columns. For example, the `products` table might have columns like `product_id` & `product_name` instead of `id` & `name`. Set this property to add that prefix automatically.
Note: the first underscore is required.
#### `$defaultOrderByColumn: string` - optional
The default column to order results by when selecting records and haven't specified an order. Default is `id`.
#### `$defaultOrderByASC: bool` - optional
Whether the default ordering should be ascending. Default is `true`.
### Complete Example
```php
...
class Product extends \JPI\ORM\Entity {
protected static string $table = "products";
protected static array $dataMapping = [
"name" => [
"type" => "string",
],
"sku" => [
"type" => "string",
],
"price" => [
"type" => "float",
],
"stock" => [
"type" => "int",
],
"created_at" => [
"type" => "date_time",
],
];
public static function getDatabase(): \JPI\Database {
return new \JPI\Database("mysql:host=localhost;dbname=shop", "username", "password");
}
...
}
```
## Relationships
The ORM supports three types of relationships:
#### `belongs_to` - Many-to-One
```php
class Order extends Entity {
...
protected static array $dataMapping = [
...
"customer" => [
"type" => "belongs_to",
"entity" => Customer::class,
"column" => "customer", // Defaults to "customer_id" if not specified
],
...
];
...
}
$order = Order::getById(1);
$customer = $order->customer; // Lazy loads the Customer entity
```
#### `has_many` - One-to-Many
```php
class Order extends Entity {
...
protected static array $dataMapping = [
...
"items" => [
"type" => "has_many",
"entity" => OrderItem::class,
"column" => "order", // The key in OrderItem that links back
"cascade_delete" => true,
],
...
];
...
}
$order = Order::getById(1);
$items = $order->items; // Lazy loads a Collection of OrderItem entities
```
#### `has_one` - One-to-One
```php
class Order extends Entity {
...
protected static array $dataMapping = [
...
"payment" => [
"type" => "has_one",
"entity" => Payment::class,
"column" => "order",
"cascade_delete" => true,
],
...
];
...
}
$order = Order::getById(1);
$payment = $order->payment; // Lazy loads the Payment entity
```
## Usage
### Retrieving Entities
**`getById(int $id): ?static`** - Get an entity by its ID.
**`newQuery(): QueryBuilder`** - Get a query builder instance for advanced queries. See [Query Builder](#query-builder) section for examples.
### Accessing Entity Data
You can get and set entity values using simple property access, these are the keys from `$dataMapping`. When setting the value must be value for the type defined or null.
### Creating and Saving Entities
**`factory(?array $data = null): static`** - Create a new entity instance, and optionally set initial data in one call.
**`insert(array $data): static`** - Create and save an entity in one call.
**`save(): bool`** - Save (insert or update) the entity to the database.
### Deleting Entities
**`delete(): bool`** - Delete the entity from the database.
### Utility Methods
**`isLoaded(): bool`** - Check if the entity has been loaded from or saved to the database.
**`isDeleted(): bool`** - Check if the entity has been deleted.
**`toArray(): array`** - Convert the entity to an array.
**`reload(): void`** - Reload the entity from the database.
## Query Builder
The query builder (accessed via `newQuery()`) provides a fluent interface for building database queries. It uses `\JPI\Database\Query\Builder` from [jpi/query](https://packagist.org/packages/jpi/query), see documentation there for full details on available query methods.
### Eager Loading
To avoid N+1 query problems, you can eager load relationships using the `with()` method:
```php
// Eager load a single relationship
$orders = Order::newQuery()->with("customer")->select();
// Eager load multiple relationships
$orders = Order::newQuery()->with("customer", "items")->select();
// Eager load nested relationships
$orders = Order::newQuery()->with("customer.address")->select();
// Combine with other query methods
$orders = Order::newQuery()
->where("status", "=", "completed")
->orderBy("created_at", false)
->with("customer", "items")
->select();
```
Without eager loading, accessing relationships causes a separate query for each entity (N+1 problem). With eager loading, all related entities are fetched in a single optimized query.
### Lazy Eager Loading
If you already have a collection of entities and later need to load relationships, use the `load()` method:
```php
// Get orders without relationships
$orders = Order::newQuery()->where("status", "=", "completed")->select();
// Later, load relationships on the collection
$orders->load("customer");
// Or load multiple relationships
$orders->load("customer", "items");
// Nested relationships work too
$orders->load("customer.address");
```
This is useful when you receive a collection from another part of your application and need to load relationships efficiently. Like `with()`, it prevents N+1 query problems by batching relationship queries instead of loading them one at a time.
## Support
If you found this library interesting or useful please spread the word about this library: share on your socials, star on GitHub, etc.
If you find any issues or have any feature requests, you can open a [issue](https://github.com/jahidulpabelislam/orm/issues) or email [me @ jahidulpabelislam.com](mailto:me@jahidulpabelislam.com) :smirk:.
## Authors
- [Jahidul Pabel Islam](https://jahidulpabelislam.com/) [](mailto:me@jahidulpabelislam.com)
## Licence
This module is licensed under the General Public Licence - see the [licence](LICENSE.md) file for details.