granadaorm / granada
Active Record / ORM with eager loading, lazy loading and more
Requires
- php: >=8.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.75
- phpstan/phpstan: ^1.11
- phpunit/phpunit: ^10.0
Suggests
- granadaorm/builder: Automatically build model classes from the database schema
- granadaorm/form: Quick generate html forms from the model structure using css frameworks such as Bootstrap and Bulma
Provides
None
Conflicts
None
Replaces
- surt/granada: *
- dev-master
- 6.0.3
- 6.0.2
- 6.0.1
- 6.0.0
- 5.1.0
- 5.0.0
- 4.1.1
- 4.1.0
- 4.0.10
- 4.0.9
- 4.0.8
- 4.0.7
- 4.0.6
- 4.0.5
- 4.0.4
- 4.0.3
- 4.0.2
- 4.0.1
- 4.0.0
- 3.1.6
- 3.1.5
- 3.1.4
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.0
- 2.10.12
- 2.10.11
- 2.10.10
- 2.10.9
- 2.10.8
- 2.10.7
- 2.10.6
- 2.10.5
- 2.10.4
- 2.10.3
- 2.10.2
- 2.10.1
- 2.10.0
- 2.9.1
- 2.9.0
- 2.8.4
- 2.8.3
- 2.8.2
- 2.8.1
- 2.8.0
- 2.7.0
- 2.6.1
- 2.6.0
- 2.5.0
- 2.4.1
- 2.4.0
- 2.3.0
- 2.2.0
- 2.1.3
- 2.1.2
- 2.1.1
- 2.1.0
- 2.0.1
- 2.0.0
- dev-joshbmarshall-patch-1
This package is auto-updated.
Last update: 2026-11-12 11:07:08 UTC
README
Granada is an Active Record ORM for PHP, based on Idiorm and Paris.
Upgrading from 6.x? Read the upgrading guide. The full documentation is in docs/index.md.
Quick example
use Granada\Model; class User extends Model { public function posts() { return $this->has_many('Post'); } } class Post extends Model {} // find one user $user = User::q()->where('name', 'John')->find_one(); // change a field $user->first_name = 'Doe'; $user->save(); // read the user's posts $posts = $user->posts()->find_many(); foreach ($posts as $post) { echo $post->content; }
Granada grew out of Idiorm and Paris, and their documentation still covers most of the query language. Granada needs PHP 8.4, so use docs/index.md when their documentation differs from Granada's behavior.
Install
Install it with composer:
composer require granadaorm/granada
Then point Granada at your database:
require 'vendor/autoload.php'; use Granada\ORM; ORM::configure('mysql:host=localhost;dbname=my_database'); ORM::configure('username', 'database_user'); ORM::configure('password', 'top_secret');
ORM::configure() takes the connection string, then each setting by name.
Granada works with SQLite, MySQL, PostgreSQL, SQL Server and Firebird.
What Granada adds
Eager loading
with() eager-loads the related models for the whole result in a few queries,
rather than one query per model:
$results = User::q()->with('avatar', 'posts')->find_many();
That runs three queries:
SELECT * FROM user SELECT * FROM avatar WHERE user_id IN (....) SELECT * FROM posts WHERE user_id IN (....)
Each user then has its avatar and posts, so the loop only reads the loaded values:
foreach ($results as $result) { echo $result->avatar->img; foreach ($result->posts as $post) { echo $post->title; } }
Lazy loading
If you leave with() off, a relationship is loaded the first time you read it,
so this loop also works:
$results = User::q()->find_many(); foreach ($results as $result) { echo $result->avatar->img; }
If there is no avatar, the property is null, and reading ->img on it raises
a warning. Check first when a relationship can be empty:
if ($result->avatar) { echo $result->avatar->img; }
Lazy finds
find_many_lazy() is find_many() for large tables. It returns a generator
that yields one model at a time, so a report over thousands of rows does not
retain the whole result set in memory:
foreach (User::q()->where('active', 1)->find_many_lazy() as $id => $user) { echo $user->name; }
Pass a smaller chunk when you need less memory and can run more queries. Read the lazy finds section before you use it on a query with a join, a group, or raw SQL.
Limit the columns an eager load fetches
with_{relation}() eager-loads one relationship and lets you limit or order
what it loads. Called without a callback, it does the same thing as with():
$car = Car::q()->with_manufactor()->find_one(1);
Use the closure to select columns, add conditions, or set an order:
$car = Car::q()->with_manufactor(fn($q) => $q->select('id, name'))->find_one(1); // SELECT `id`, `name` FROM `manufactor` WHERE ... $manufactor = Manufactor::q()->with_cars(fn($q) => $q->select('id, name')->order_by_desc('name'))->find_one(1); // SELECT `id`, `name`, `manufactor_id` FROM `car` WHERE ... ORDER BY `name` DESC
You can use the two forms on one query. Inside the closure, another with_*
call eager-loads a nested relationship:
$car = Car::q()->with_manufactor(fn($q) => $q->select('name'))->with('owner')->find_one(1); $owner = Owner::q()->with_car(fn($q) => $q->select('id, manufactor_id') ->with_manufactor(fn($q) => $q->select('name')) )->find_one(1);
You do not have to select the key that ties the two models together. Granada adds it for you, and selecting it yourself is also fine.
Limit a relationship query to the columns you name: select('id, name'). On a
has_many_through, qualify each column with the table name:
$car = Car::q()->with_parts(fn($q) => $q->select('part.id, part.name'))->find_one(1);
Nested eager loads and arguments
Nested eager loading chains relationship names with a dot: posts.comments
loads each user's posts, then the comments on those posts:
$results = User::q()->with('posts.comments')->find_many(); // SELECT * FROM user // SELECT * FROM post WHERE user_id IN (....) // SELECT * FROM comments WHERE post_id IN (....) foreach ($results as $result) { foreach ($result->posts as $post) { echo $post->title; foreach ($post->comments as $comment) { echo $comment->subject; } } }
If the relationship method takes arguments, with() takes an array keyed by
the relationship name, with the arguments as the value. Add a with key to
eager-load a nested relationship:
$results = User::q()->with(['posts' => ['with' => ['comments']]])->find_many(); $results = User::q()->with(['posts' => ['arg1']])->find_many(); // posts(arg1)
Custom query actions
A custom query action is a real method on the model's query class. The class is
named Query plus the model name, extends \Granada\Query, and any method you
add is available through q():
class QueryCar extends \Granada\Query { public function is_expensive(): static { return $this->where_gt('price', 1000); } } $cars = Car::q()->is_expensive()->find_many();
Other query methods
Some methods take SQL that you write: select_raw(), group_by_raw(),
order_by_raw() and raw_join(). insert() saves a list of rows in one
transaction, pluck() returns one column's value, and find_pairs() returns a
key => value array. delete_many() and update_many() change every row that
the query matches.
Setter methods
A set_{column}() setter receives each value you set for that column and
returns the value that Granada saves:
// In the Model protected function set_title($value) { $this->alias = strtoupper($value); return $value; }
set(), create() and a direct assignment all call it:
// outside of the model $content_instance->set('title', 'A title'); // several fields at once $properties = [ 'title' => 'A title', 'content' => 'Some content', ]; $content_instance->set($properties); // a direct assignment calls it too $content_instance->title = 'A title';
Mass assignment
create() and set() write every key you give them. There is no fillable or
guarded list, and Granada does not filter the keys.
If the array comes from a request, pass only the fields that the form offered before the array reaches the model:
$allowed = ['name', 'email', 'phone']; $enquiry = Enquiry::create(array_intersect_key($data, array_flip($allowed)));
Granada does not remove unknown keys: the save fails with a database error if the column does not exist. Filter the keys yourself, based on what the form offered.
Getters and computed values
A get_{column}() getter transforms a column value before you read it, and a
missing_{name}() method computes a value that the database does not have:
// In the Model // Transform a stored column value. protected function get_path($value) { return strtolower($value); } // Compute a value that no column stores. protected function missing_testing() { return 'whatever'; } ... // outside of the model echo $content_instance->path; // returns the lowercase path value of $content_instance echo $content_instance->testing; // returns 'whatever', from missing_testing()
A method named after the property runs last, and it can return a relationship.
Use your own result set class
Turn on return_result_sets, and a find returns a Granada\ResultSet instead
of an array. A model can name its own subclass, as long as it extends
Granada\ResultSet and is loaded:
// In the Model public static string $resultSetClass = 'TreeResultSet';
// outside of the model echo get_class(Content::q()->find_many()); // TreeResultSet
An eager load returns the same class: a Content model that names
TreeResultSet and has a media relationship returns a TreeResultSet of
Content models, with media as a plain Granada\ResultSet:
Content::q()->with('media')->find_many();
Granada and Paris
Granada began as a fork of Idiorm and Paris and retains their query language and Active Record approach. It adds eager and lazy loading.
Features
- Configuration is one connection string plus its settings.
- The fluent query API comes from Idiorm, and relationships come from Paris.
- The junction writes
attach(),detach()andsync()manage related models through a link table, andsync()updates ahas_many's children. - Eager loading and lazy finds make large results manageable.
- Granada binds values as query parameters, so the database treats them as data.
- Several connections are supported, and you can call model methods on a whole result set.
History
Granada was originally developed by Erik Wiesenthal, up to version 1.5. Further development started by Josh Marshall to release version 2.0.