PHPackages                             paigejulianne/picoorm - 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. paigejulianne/picoorm

Abandoned → [paigejulianne/nanoorm](/?search=paigejulianne%2Fnanoorm)Library[Database &amp; ORM](/categories/database)

paigejulianne/picoorm
=====================

PicoORM: a very lightweight ORM with multi-connection support

2.1.1(4mo ago)2116GPL-3.0-or-laterPHPPHP &gt;=8.0

Since Jul 15Pushed 4mo ago1 watchersCompare

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

READMEChangelog (10)Dependencies (1)Versions (16)Used By (0)

PicoORM
=======

[](#picoorm)

A lightweight, secure ORM for PHP 8.0+ that makes database operations simple without the overhead of a full-featured framework.

**Version 2.1.1** | [Changelog](CHANGELOG.md) | [License: GPL-3.0](LICENSE)

by Paige Julianne Sullivan [paigejulianne.com](https://paigejulianne.com) | [GitHub](https://github.com/paigejulianne/picoorm)

---

Features
--------

[](#features)

- **Minimal footprint**: Single-file ORM (~1200 lines)
- **Multiple connections**: Support for multiple named database connections
- **Secure by default**: SQL injection protection via prepared statements and identifier validation
- **Zero dependencies**: Only requires PHP 8.0+ and PDO
- **Auto-save**: Changes are automatically persisted when objects go out of scope
- **Cross-database**: Works with MySQL, PostgreSQL, SQLite, and other PDO-supported databases
- **Transaction support**: Begin, commit, and rollback transactions with ease
- **Dirty checking**: Track which fields have been modified

---

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

[](#installation)

### Via Composer (Recommended)

[](#via-composer-recommended)

```
composer require paigejulianne/picoorm
```

### Manual Installation

[](#manual-installation)

Download `PicoORM.php` and include it in your project:

```
require_once 'PicoORM.php';
```

---

Quick Start
-----------

[](#quick-start)

### 1. Configure Your Database Connection

[](#1-configure-your-database-connection)

Create a `.connections` file in your project root:

```
[default]
DSN=mysql:host=localhost;dbname=myapp;charset=utf8mb4
USER=myuser
PASS=mypassword
```

> **Security Note**: Add `.connections` to your `.gitignore` to prevent committing credentials!

### 2. Create a Model Class

[](#2-create-a-model-class)

Create a class that extends `PicoORM`, named after your database table:

```
use PaigeJulianne\PicoORM;

class Users extends PicoORM
{
    // That's it! PicoORM maps this class to the 'users' table
}
```

### 3. Start Using It

[](#3-start-using-it)

```
// Create a new user
$user = new Users();
$user->name = 'Alice';
$user->email = 'alice@example.com';
$user->save();

echo "Created user with ID: " . $user->getId();

// Load an existing user
$user = new Users(1);
echo $user->name; // Output: Alice

// Update a user
$user->name = 'Alice Smith';
$user->save();

// Delete a user
$user->delete();
```

---

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

[](#configuration)

### Option 1: Using a `.connections` File (Recommended)

[](#option-1-using-a-connections-file-recommended)

The `.connections` file uses an INI-like format with sections for each database connection:

```
# Primary database
[default]
DSN=mysql:host=localhost;dbname=myapp;charset=utf8mb4
USER=app_user
PASS=secure_password

# Analytics database on a separate server
[analytics]
DSN=mysql:host=analytics.example.com;dbname=analytics
USER=analytics_readonly
PASS=analytics_password

# SQLite for local caching
[cache]
DSN=sqlite:/var/cache/myapp/cache.db
USER=
PASS=
```

PicoORM searches for the `.connections` file in these locations (in order):

1. The path specified via `setConnectionsFile()`
2. The parent directory of PicoORM.php
3. The current working directory

#### PDO Options

[](#pdo-options)

You can specify PDO options using the `OPTIONS[constant]` syntax:

```
[production]
DSN=mysql:host=db.example.com;dbname=prod
USER=prod_user
PASS=prod_password
OPTIONS[PDO::ATTR_PERSISTENT]=true
OPTIONS[PDO::ATTR_TIMEOUT]=10
```

### Option 2: Programmatic Configuration

[](#option-2-programmatic-configuration)

Add connections at runtime using `addConnection()`:

```
use PaigeJulianne\PicoORM;

PicoORM::addConnection(
    'default',
    'mysql:host=localhost;dbname=myapp',
    'username',
    'password',
    [PDO::ATTR_PERSISTENT => true]
);
```

### Option 3: Legacy Global Variables (Deprecated)

[](#option-3-legacy-global-variables-deprecated)

For backward compatibility, global variables are still supported:

```
global $PICOORM_DSN, $PICOORM_USER, $PICOORM_PASS, $PICOORM_OPTIONS;

$PICOORM_DSN = 'mysql:host=localhost;dbname=myapp';
$PICOORM_USER = 'username';
$PICOORM_PASS = 'password';
$PICOORM_OPTIONS = [PDO::ATTR_PERSISTENT => true];
```

> **Note**: This method is deprecated. Please migrate to `.connections` file or `addConnection()`.

---

Working with Models
-------------------

[](#working-with-models)

### Defining Models

[](#defining-models)

Create a class extending `PicoORM` for each database table. The class name (lowercased) becomes the table name:

```
use PaigeJulianne\PicoORM;

// Maps to the 'products' table
class Products extends PicoORM {}

// Maps to the 'order_items' table
class OrderItems extends PicoORM {}
```

#### Custom Table Names

[](#custom-table-names)

Override the table name using the `TABLE_OVERRIDE` constant:

```
class Product extends PicoORM
{
    const TABLE_OVERRIDE = 'shop_products';
}
```

#### Specifying a Connection

[](#specifying-a-connection)

For models that use a non-default database connection:

```
class AnalyticsEvent extends PicoORM
{
    const CONNECTION = 'analytics';
}
```

### Creating Records

[](#creating-records)

```
// Method 1: Set properties individually
$product = new Products();
$product->name = 'Widget';
$product->price = 29.99;
$product->category_id = 5;
$product->save();

// Method 2: Set multiple properties at once
$product = new Products();
$product->setMulti([
    'name' => 'Gadget',
    'price' => 49.99,
    'category_id' => 3
]);
$product->save();

// Get the new record's ID
$newId = Products::getLastInsertId();
// or
$newId = $product->getId();
```

### Loading Records

[](#loading-records)

```
// Load by primary key (defaults to 'id' column)
$user = new Users(42);

// Load using a different column
$user = new Users('alice@example.com', 'email');

// Check if the record was found
if ($user->getId() === '-1') {
    echo "User not found";
}
```

### Updating Records

[](#updating-records)

```
$user = new Users(1);
$user->name = 'Updated Name';
$user->email = 'newemail@example.com';
$user->save();

// Or let auto-save handle it when the object is destroyed
function updateUser($id, $name) {
    $user = new Users($id);
    $user->name = $name;
    // save() is called automatically when $user goes out of scope
}
```

### Deleting Records

[](#deleting-records)

```
$user = new Users(1);
$user->delete();
```

### Checking If a Record Exists

[](#checking-if-a-record-exists)

```
// Check by ID
if (Users::exists(42)) {
    echo "User exists";
}

// Check by another column
if (Users::exists('alice@example.com', 'email')) {
    echo "Email is registered";
}
```

### Checking Properties

[](#checking-properties)

```
$user = new Users(1);

if (isset($user->phone)) {
    echo "Phone: " . $user->phone;
} else {
    echo "No phone on file";
}
```

### Refreshing Data

[](#refreshing-data)

Reload the record from the database to get the latest values:

```
$user = new Users(1);
// ... some time passes, data may have changed ...
$user->refreshProperties();
```

---

Querying Multiple Records
-------------------------

[](#querying-multiple-records)

Use `getAllObjects()` to retrieve multiple records with optional filtering:

```
// Get all users
$users = Users::getAllObjects();

// Get users with filters
$activeAdmins = Users::getAllObjects('id', [
    ['status', null, '=', 'active'],
    ['role', null, '=', 'admin']
], 'AND');

// Use OR logic
$featured = Products::getAllObjects('id', [
    ['is_featured', null, '=', 1],
    ['is_bestseller', null, '=', 1]
], 'OR');

// Force array return (even for single results)
$results = Users::getAllObjects('id', [], 'AND', true);
```

### Filter Format

[](#filter-format)

Each filter is an array: `[column, null, operator, value]`

**Supported operators**: `=`, `!=`, ``, ``, `=`, `LIKE`, `NOT LIKE`, `IN`, `NOT IN`, `IS`, `IS NOT`

```
// Find users with Gmail addresses
$gmailUsers = Users::getAllObjects('id', [
    ['email', null, 'LIKE', '%@gmail.com']
]);

// Find products in a price range
$affordableProducts = Products::getAllObjects('id', [
    ['price', null, '>=', 10],
    ['price', null, '=');

// Find a single record
$user = Users::findOneBy('email', 'alice@example.com');

if ($user === null) {
    echo "User not found";
}
```

### First or Create (Upsert)

[](#first-or-create-upsert)

Find a record or create it if it doesn't exist:

```
// Find user by email, or create with additional attributes
$user = Users::firstOrCreate(
    ['email' => 'alice@example.com'],           // Search criteria
    ['name' => 'Alice', 'role' => 'user']       // Values for new record
);

// The returned user either existed or was just created
echo $user->name;
```

### Update or Create

[](#update-or-create)

Find a record and update it, or create if it doesn't exist:

```
// Update existing user's login time, or create new user
$user = Users::updateOrCreate(
    ['email' => 'alice@example.com'],           // Search criteria
    ['last_login' => date('Y-m-d H:i:s')]       // Values to update/set
);
```

---

Counting Records
----------------

[](#counting-records)

```
// Count all records
$totalUsers = Users::count();

// Count with filters
$activeAdmins = Users::count([
    ['role', null, '=', 'admin'],
    ['is_active', null, '=', 1]
], 'AND');

// Count with OR logic
$specialUsers = Users::count([
    ['role', null, '=', 'admin'],
    ['role', null, '=', 'moderator']
], 'OR');
```

---

Plucking Column Values
----------------------

[](#plucking-column-values)

Get an array of values from a single column:

```
// Get all usernames
$usernames = Users::pluck('username');
// Returns: ['alice', 'bob', 'charlie']

// Get emails of active users
$emails = Users::pluck('email', [
    ['is_active', null, '=', 1]
]);
```

---

Data Export
-----------

[](#data-export)

### Converting to Array

[](#converting-to-array)

```
$user = new Users(1);

// Export all properties
$data = $user->toArray();
// Returns: ['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com', ...]

// Export specific columns only
$data = $user->toArray(['name', 'email']);
// Returns: ['name' => 'Alice', 'email' => 'alice@example.com']
```

---

Dirty Checking
--------------

[](#dirty-checking)

Track which properties have been modified:

```
$user = new Users(1);

// Check if any changes have been made
$user->isClean();  // true
$user->isDirty();  // false

// Make a change
$user->name = 'New Name';

$user->isDirty();           // true
$user->isDirty('name');     // true
$user->isDirty('email');    // false

// Get all changed properties
$changes = $user->getDirty();
// Returns: ['name' => 'New Name']

// Get original value before changes
$original = $user->getOriginal('name');  // 'Alice'

// Get all original values
$allOriginal = $user->getOriginal();
```

### Fresh Instance

[](#fresh-instance)

Get a new instance with fresh data from the database (without modifying current instance):

```
$user = new Users(1);
$user->name = 'Modified';

// Get a fresh copy from database
$freshUser = $user->fresh();

echo $user->name;       // 'Modified' (still has local changes)
echo $freshUser->name;  // 'Alice' (fresh from database)
```

---

Atomic Operations
-----------------

[](#atomic-operations)

### Increment and Decrement

[](#increment-and-decrement)

Atomically update numeric columns:

```
$product = new Products(1);

// Increment view count
$product->increment('view_count');

// Increment by a specific amount
$product->increment('stock', 10);

// Decrement
$product->decrement('stock', 5);

// Works with floats too
$product->increment('price', 0.50);
```

---

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

[](#transactions)

### Manual Transaction Control

[](#manual-transaction-control)

```
use PaigeJulianne\PicoORM;

try {
    PicoORM::beginTransaction();

    $user = new Users();
    $user->name = 'Alice';
    $user->save();

    $order = new Orders();
    $order->user_id = $user->getId();
    $order->total = 99.99;
    $order->save();

    PicoORM::commit();
} catch (\Exception $e) {
    PicoORM::rollback();
    throw $e;
}
```

### Transaction Callback

[](#transaction-callback)

A cleaner approach using a callback:

```
$result = PicoORM::transaction(function () {
    $user = new Users();
    $user->name = 'Alice';
    $user->save();

    $order = new Orders();
    $order->user_id = $user->getId();
    $order->total = 99.99;
    $order->save();

    return $order->getId();
});

echo "Created order: $result";
```

If any exception is thrown inside the callback, the transaction is automatically rolled back.

### Transaction Status

[](#transaction-status)

```
// Check if currently in a transaction
if (PicoORM::inTransaction()) {
    // ...
}

// Transactions work with multiple connections
PicoORM::beginTransaction('analytics');
// ... operations on analytics connection ...
PicoORM::commit('analytics');
```

---

Type Validation
---------------

[](#type-validation)

PicoORM automatically validates data types against your database schema, catching type mismatches before they reach the database.

### How It Works

[](#how-it-works)

When you set a property or save a record, PicoORM:

1. Fetches the table schema (cached per request)
2. Validates the value type matches the column type
3. Throws a `TypeError` if validation fails

```
// Assuming 'age' is an INT column in the database
$user = new Users();
$user->name = 'Alice';           // OK - string to VARCHAR
$user->age = 25;                 // OK - int to INT
$user->age = 'twenty-five';      // TypeError: expected integer, got string

// String length validation
// Assuming 'username' is VARCHAR(50)
$user->username = str_repeat('a', 100);  // TypeError: exceeds max length of 50
```

### Disabling Validation

[](#disabling-validation)

Disable validation for a specific model by overriding the constant:

```
class LegacyData extends PicoORM
{
    const VALIDATE_TYPES = false;  // Disable type checking
}
```

### Nullable Columns

[](#nullable-columns)

Null values are only allowed for nullable columns:

```
// If 'email' is defined as NOT NULL
$user->email = null;  // TypeError: Column 'email' does not allow NULL values

// If 'phone' allows NULL
$user->phone = null;  // OK
```

### Type Coercion Rules

[](#type-coercion-rules)

PicoORM allows sensible type coercions:

Database TypeAcceptsINTEGER`int`, `bool`, numeric strings (`"123"`)FLOAT/DECIMAL`float`, `int`, numeric stringsVARCHAR/TEXT`string`, `int`, `float`, `bool` (auto-converted)BOOLEAN`bool`, `0`, `1`, `"0"`, `"1"`### Inspecting Schema

[](#inspecting-schema)

You can inspect the detected schema:

```
$schema = Users::getTableSchema();

print_r($schema);
// [
//     'id' => ['type' => 'int', 'php_type' => 'integer', 'nullable' => false, ...],
//     'name' => ['type' => 'varchar', 'php_type' => 'string', 'max_length' => 255, ...],
//     ...
// ]
```

### Manual Validation

[](#manual-validation)

Validate a value without setting it:

```
$user = new Users(1);

// Returns true/false without throwing
$isValid = $user->validateColumnValue('age', 'invalid', throw: false);

// Or validate all pending changes
$user->validateAllChanges();
```

### Clearing Schema Cache

[](#clearing-schema-cache)

If your schema changes during runtime:

```
// Clear all cached schemas
PicoORM::clearSchemaCache();

// Clear specific table
PicoORM::clearSchemaCache('users');
```

---

Custom Queries
--------------

[](#custom-queries)

For complex queries, use the low-level query methods. Use `_DB_` as a placeholder for the table name:

```
// Fetch a single record
$result = Users::_fetch(
    'SELECT * FROM _DB_ WHERE email = ? AND status = ?',
    ['alice@example.com', 'active']
);

// Fetch multiple records
$results = Users::_fetchAll(
    'SELECT * FROM _DB_ WHERE created_at > ? ORDER BY name',
    ['2024-01-01']
);

// Execute a query (INSERT, UPDATE, DELETE)
Users::_doQuery(
    'UPDATE _DB_ SET last_login = NOW() WHERE id = ?',
    [42]
);
```

---

Multiple Database Connections
-----------------------------

[](#multiple-database-connections)

PicoORM supports connecting to multiple databases simultaneously.

### Define Connections

[](#define-connections)

In your `.connections` file:

```
[default]
DSN=mysql:host=localhost;dbname=main_app
USER=app_user
PASS=app_password

[analytics]
DSN=mysql:host=analytics-server;dbname=analytics
USER=analytics_user
PASS=analytics_password

[legacy]
DSN=mysql:host=old-server;dbname=legacy_data
USER=legacy_user
PASS=legacy_password
```

### Use Connections in Models

[](#use-connections-in-models)

```
// Uses 'default' connection
class Users extends PicoORM {}

// Uses 'analytics' connection
class PageView extends PicoORM
{
    const CONNECTION = 'analytics';
}

// Uses 'legacy' connection
class OldCustomer extends PicoORM
{
    const CONNECTION = 'legacy';
    const TABLE_OVERRIDE = 'tbl_customers';  // Legacy table name
}
```

### Connection Management

[](#connection-management)

```
// List all configured connections
$connections = PicoORM::getConnectionNames();
// Returns: ['default', 'analytics', 'legacy']

// Check if a connection exists
if (PicoORM::hasConnection('analytics')) {
    // Analytics connection is configured
}

// Get the connection used by an instance
$pageView = new PageView();
echo $pageView->getConnection(); // Output: analytics
```

---

Security
--------

[](#security)

PicoORM includes several security measures:

### SQL Injection Protection

[](#sql-injection-protection)

1. **Prepared Statements**: All values are passed through PDO prepared statements
2. **Identifier Validation**: Column names and operators are validated against strict patterns
3. **Operator Whitelist**: Only approved SQL operators are allowed in filters

### Valid Identifiers

[](#valid-identifiers)

Column and table names must:

- Start with a letter (a-z, A-Z) or underscore (\_)
- Contain only letters, numbers, and underscores

```
// Valid
$user->first_name = 'Alice';     // OK
$user->address_line_1 = '123';   // OK

// Invalid - will throw InvalidArgumentException
$user->{'first-name'} = 'Alice'; // Error: hyphens not allowed
```

### Credentials Protection

[](#credentials-protection)

- Store credentials in `.connections` file (not in code)
- Add `.connections` to `.gitignore`
- Use environment-specific connection files in production

---

Error Handling
--------------

[](#error-handling)

PicoORM throws exceptions for error conditions:

```
use PaigeJulianne\PicoORM;

try {
    $user = new Users(1);
    $user->name = 'New Name';
    $user->save();
} catch (\PDOException $e) {
    // Database connection or query error
    error_log("Database error: " . $e->getMessage());
} catch (\InvalidArgumentException $e) {
    // Invalid column name, operator, or filter
    error_log("Invalid argument: " . $e->getMessage());
} catch (\RuntimeException $e) {
    // Connection not configured, statement preparation failed
    error_log("Runtime error: " . $e->getMessage());
}
```

---

Database Compatibility
----------------------

[](#database-compatibility)

PicoORM works with any PDO-supported database:

DatabaseStatusNotesMySQLFullPrimary development targetMariaDBFullCompatible with MySQLPostgreSQLFullTested and supportedSQLiteFullGreat for development/testingSQL ServerPartialMay require adjustments**Note**: Column names are escaped with backticks, which is MySQL/MariaDB syntax. For maximum compatibility, use simple alphanumeric column names.

---

API Reference
-------------

[](#api-reference)

### Static Methods

[](#static-methods)

MethodDescription`setConnectionsFile($path)`Set the path to the connections file`loadConnections()`Manually load connection configuration`addConnection($name, $dsn, $user, $pass, $options)`Add a connection programmatically`getConnectionNames()`Get list of configured connection names`hasConnection($name)`Check if a connection exists`exists($id, $column)`Check if a record exists`getAllObjects($idColumn, $filters, $glue, $forceArray, $limit, $offset, $orderBy, $orderDir)`Retrieve multiple records with pagination`getLastInsertId()`Get the last auto-increment ID`count($filters, $filterGlue)`Count records matching filters`pluck($column, $filters, $filterGlue)`Get array of values from a single column`findBy($column, $value, $idColumn, $operator)`Find all records matching a column value`findOneBy($column, $value, $idColumn)`Find a single record matching a column value`firstOrCreate($attributes, $values, $idColumn)`Find or create a record`updateOrCreate($attributes, $values, $idColumn)`Find and update, or create a record`beginTransaction($connectionName)`Begin a database transaction`commit($connectionName)`Commit the current transaction`rollback($connectionName)`Roll back the current transaction`inTransaction($connectionName)`Check if currently in a transaction`transaction($callback, $connectionName)`Execute callback within a transaction`clearConnectionCache($connectionName)`Clear cached PDO connections`getTableSchema($connectionName)`Get table column definitions`clearSchemaCache($table)`Clear cached schema information`_fetch($sql, $values, $table)`Fetch single record with custom SQL`_fetchAll($sql, $values, $table)`Fetch multiple records with custom SQL`_doQuery($sql, $values, $table)`Execute custom SQL### Instance Methods

[](#instance-methods)

MethodDescription`getId()`Get the record's primary key value`getConnection()`Get the connection name used by this instance`save()`Persist changes to the database`writeChanges()`Alias for save()`delete()`Delete the record from the database`refreshProperties()`Reload data from the database`setMulti($array)`Set multiple properties at once`toArray($columns)`Export record as associative array`isDirty($column)`Check if model has unsaved changes`isClean()`Check if model has no unsaved changes`getDirty()`Get all changed properties and their values`getOriginal($column)`Get original value(s) before modifications`fresh()`Return a fresh instance from the database`increment($column, $amount)`Atomically increment a column value`decrement($column, $amount)`Atomically decrement a column value`validateColumnValue($column, $value, $throw)`Validate a value against column type`validateAllChanges()`Validate all pending changes### Class Constants

[](#class-constants)

ConstantDescription`TABLE_OVERRIDE`Override the default table name`CONNECTION`Specify which database connection to use`VALIDATE_TYPES`Enable/disable type validation (default: true)---

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

[](#contributing)

Contributions are welcome! Please:

1. Fork the repository
2. Create a feature branch
3. Submit a pull request

For bugs and feature requests, use the [GitHub issue tracker](https://github.com/paigejulianne/picoorm/issues).

---

License
-------

[](#license)

PicoORM is released under the [GPL-3.0-or-later](LICENSE) license.

Copyright 2008-present Paige Julianne Sullivan

###  Health Score

40

—

FairBetter than 88% of packages

Maintenance74

Regular maintenance activity

Popularity13

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 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 ~63 days

Recently: every ~129 days

Total

15

Last Release

139d ago

Major Versions

1.7.1-alpha → 2.0.02025-12-24

PHP version history (3 changes)1.0.0PHP &gt;=7.1

1.6.0PHP &gt;=7.4

1.7-alphaPHP &gt;=8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/c7ef9796227c86ce9eb83c0439fa3d5b8ef00d4c729b4ce9e6071e26677a79f5?d=identicon)[paigejulianne](/maintainers/paigejulianne)

---

Top Contributors

[![paigejulianne](https://avatars.githubusercontent.com/u/70969788?v=4)](https://github.com/paigejulianne "paigejulianne (49 commits)")

---

Tags

ormphp-librarydatabaseormmysqlsqlitepostgresqlpdolightweight

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/paigejulianne-picoorm/health.svg)

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

###  Alternatives

[doctrine/dbal

Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.

9.7k578.4M5.6k](/packages/doctrine-dbal)[tommyknocker/pdo-database-class

Framework-agnostic PHP database library with unified API for MySQL, MariaDB, PostgreSQL, SQLite, MSSQL, and Oracle. Query Builder, caching, sharding, window functions, CTEs, JSON, migrations, ActiveRecord, CLI tools, AI-powered analysis. Zero external dependencies.

845.7k](/packages/tommyknocker-pdo-database-class)[cycle/database

DBAL, schema introspection, migration and pagination

64690.9k31](/packages/cycle-database)[ramadan/easy-model

A Laravel package for enjoyably managing database queries.

101.6k](/packages/ramadan-easy-model)[popphp/pop-db

Pop Db Component for Pop PHP Framework

1814.6k11](/packages/popphp-pop-db)

PHPackages © 2026

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