PHPackages                             timetoogo/penumbra - 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. timetoogo/penumbra

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

timetoogo/penumbra
==================

Domain oriented ORM with PHP integrated query for PHP 5.4+

7684[2 issues](https://github.com/TimeToogo/Penumbra/issues)PHP

Since Jan 28Pushed 11y ago11 watchersCompare

[ Source](https://github.com/TimeToogo/Penumbra)[ Packagist](https://packagist.org/packages/timetoogo/penumbra)[ RSS](/packages/timetoogo-penumbra/feed)WikiDiscussions dev Synced 2mo ago

READMEChangelogDependenciesVersions (5)Used By (0)

Penumbra - Still in development
===============================

[](#penumbra---still-in-development)

Domain oriented ORM, crafted with passion for PHP 5.4+.

Penumbra is not finished, the foundation and architecture is there, but the codebase has almost no unit tests. Since penumbra was originally a learning exercise with the goal of creating an orm that does not influence the domain model, this project has maintained a good code quality, but it is quickly becoming apparent that I cannot single handedly develop and maintain this project, especially with school. Serious contributors are needed, please feel free to clone the repository and look around the code base, if you have any questions, inquiries or want to begin contributing please email me at: .

Please be aware that this is my first large open source project, so any tips/recommendations are welcome.

Another ORM for PHP?!
=====================

[](#another-orm-for-php)

Yes! Penumbra offers features that no other ORM has. Penumbra is 100% data mapper and aims to stay [completely decoupled](#domainmodels)from your domain models while offering a [powerful language integrated query api](#pinq).

Summary of the current state of Penumbra
========================================

[](#summary-of-the-current-state-of-penumbra)

- ORM [functionality is running](https://github.com/TimeToogo/Penumbra/blob/master/Example/One/Example.php) but far from stable.
- Overall, maintains a [good code quality](https://scrutinizer-ci.com/g/TimeToogo/Penumbra).
- Plenty of work is required just [to set up](https://github.com/TimeToogo/Penumbra/tree/master/Example/One).
- Decent size codebase (~15,000 LOC).
- [API](https://github.com/TimeToogo/Penumbra/tree/master/Penumbra/Penumbra/Api) structure is not finialized.
- Apalling [test suite](https://github.com/TimeToogo/Penumbra/tree/master/Tests/Penumbra/Tests).
- Lacking in documentation / code comments.
- Serious contributors [are needed](mailto:elliot@aanet.com.au).

The goals of Penumbra
=====================

[](#the-goals-of-penumbra)

- To provide a maintainable and sensible approach to the complex ORM realm.
- To reward the user with unpolluted and [flexible domain models](#domainmodels).
- To provide a fluent [language integrated query (Similar to C#'s LINQ)](#pinq)
- To [reduce the amount of queries](#queries) to the underlying platform and assist in [eliminating n+1 queries](#queries).

Flexible domain models
==============================================================

[](#flexible-domain-models)

**Penumbra aims to provide as little restriction to domain models as possible**

- Penumbra natively supports: fields, getters/setters and even indexors or innvocation as entity properties.
- Your entities can remain completely unaware of Penumbra: no base class, no annotations and no persistence logic.
- Transparent relationship loading and persisting.
- Seamless identifying and non-identifying relationships between entities:
    - Required child entity - A `User` has a `Profile`
    - Optional child entity - A `User` may have a `CreditCard`
    - [Array](http://php.net/manual/en/language.types.array.php)/[Traversable](http://au1.php.net/manual/en/class.traversable.php) of many child entities - A `User` has multiple `Posts`, *Traversable must be used for lazy loading*
- Embedded Objects
- Value Objects
- Polymorphic Types

PHP integerated query (Pinq)
============================================================

[](#php-integerated-query-pinq)

- Query in terms of the domain, not your database.
- Abstract yourself from the underlying database.
- Maintain full IDE auto-completion.
- Remove the hassle of magic strings and prevent SQL injection.
- Powerful aggregation api, `Count, Maximum, Minimum, Average, Sum, Implode, All, Any`
- Supports used variables for parameterized queries `function ($_) use ($Value) {...`
- Supports dynamic fields, method calls, function calls etc.
- Even supports relationship properties for seamless joins!

**Entity Request (`SELECT`) - Example:**

```
$MiddleAgedUsersRequest = $UserRepository->Request()
        ->Where(function (User $User) {
            return $User->GetAge() > 20 && $User->GetAge() < 50 ;
        })
        ->OrderByDescending(function (User $User) { return $User->GetLastLoginDate(); });

$SomeActiveMiddleAgedUsers = $MiddleAgedUsersRequest->AsArray();
```

Will map to something along the lines of:

```
SELECT Users.* FROM Users
WHERE Users.Age > 20 AND Users.Age Request()
        ->From($MiddleAgedUsersRequest)
        ->Where(function (User $User) { return  $User->IsActive(); })
        ->GroupBy(function (User $User) { return $User->GetAge(); })
        ->Select(function (User $User, IAggregate $Users) {
            return [
                'Age' => $User->GetAge(),
                'Amount' => $Users->Count(),
                'AverageVisits' => $Users->Average(function (User $User) { return $User->GetVisitsPerDay(); })),
            ];
        });
```

Will map to something along the lines of:

```
SELECT Users.Age AS Age, COUNT(*) AS Amount, AVG(Users.VisitsPerDay) AS AverageVisits FROM
    (SELECT Users.* FROM Users
    WHERE Users.Age > 20 AND Users.Age Procedure(
        function (User $User) {
            $User->SetIsActive(false);
            $User->SetInactivationDate(new DateTime());
        })
        ->Where(function (User $User) {
            return $User->GetLastLoginDate() < (new DateTime())->sub(new DateInterval('P2Y'));
        });

$InactiveUserProcedure->Execute();
```

Will map to something along the lines of:

```
UPDATE Users SET
    Users.IsActive = 0,
    Users.InactivationDate = NOW()
WHERE Users.LastLoginDate LoadById(1);
foreach($User->GetPosts() as $Post) {
    $Post->GetAuthor()->GetFullName();
    foreach($Post->GetTags() as $Tag) {
        $Tag->GetName();
    }
}
```

Now here is the number of queries executed for each loading mode (N=Number of Posts);

- `Eager` - **3** (the user | posts joined with the author | all tags), when the user is loaded.
- `Global scope lazy` - For loading a single request this is equivalent to `Request scope lazy` mode below.
- `Request scope lazy` - **4** (the user | posts | all authors | all tags), the posts will be loaded when they are iterated and all the tags will be loaded when it is first is iterated.
- `Parent scope lazy` - **(N \* 2) + 2** - (user | posts | *each post's* author | *each post's* tags).

*NOTE: Relationship loading mode is per-relationship, the above example assumes every relationship will have the same loading mode for simplicity.*

###  Health Score

26

—

LowBetter than 43% of packages

Maintenance19

Infrequent updates — may be unmaintained

Popularity18

Limited adoption so far

Community16

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor2

2 contributors hold 50%+ of commits

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.

### Community

Maintainers

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

---

Top Contributors

[![camspiers](https://avatars.githubusercontent.com/u/51294?v=4)](https://github.com/camspiers "camspiers (1 commits)")[![cordoval](https://avatars.githubusercontent.com/u/328359?v=4)](https://github.com/cordoval "cordoval (1 commits)")[![TimeToogo](https://avatars.githubusercontent.com/u/689898?v=4)](https://github.com/TimeToogo "TimeToogo (1 commits)")

### Embed Badge

![Health badge](/badges/timetoogo-penumbra/health.svg)

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

###  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)[mongodb/mongodb

MongoDB driver library

1.6k64.0M546](/packages/mongodb-mongodb)[ramsey/uuid-doctrine

Use ramsey/uuid as a Doctrine field type.

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

Reliese Components for Laravel Framework code generation.

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

PHPackages © 2026

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