❓ FAQ

Entities

How does Hector ORM map an entity class to a database table?

By default, the class name (without namespace) is converted to snake_case. For example, UserProfile maps to the user_profile table.

You can override this with the #[Orm\Table] attribute:

use Hector\Orm\Attributes as Orm;

#[Orm\Table('my_custom_table')]
class User extends Entity {}

See Entities — How entities map to tables for details.

What is the difference between Magic Entity and Classic Entity?

Magic Entity (MagicEntity) uses PHP’s __get/__set magic methods — properties are dynamic. Minimal boilerplate, but no IDE autocompletion or static analysis.

Classic Entity (Entity) uses explicitly declared PHP properties. Better IDE support and easier to maintain.

Both support the same features (relationships, save, delete, etc.). The choice depends on your preference for explicitness vs. brevity.

See Entities for a full comparison.

My entity is not saved to the database — what could be wrong?

Common causes:

  1. You forgot to call save() — Creating an entity and setting properties does not persist it automatically.
  2. The ORM is not booted — Make sure OrmFactory::orm(...) has been called before any entity operation.
  3. The table name does not match — Check that the snake_case version of your class name matches a table in the configured schema, or use #[Orm\Table('...')] explicitly.
  4. A before-save event stopped propagation — If you use a PSR-14 event dispatcher, check that no listener calls stopPropagation() on EntityBeforeSaveEvent. See Events.
What is the difference between save() and Orm::get()->persist()?

Entity::save() persists the entity immediately (one database query per call).

Orm::get()->save($entity) (without persist: true) marks the entity for saving but does not execute the query. You must call Orm::get()->persist() to flush all pending operations in a single transaction.

See Entities — Deferred persistence for details.


Queries

How do I debug the SQL queries generated by Hector ORM?

Enable the query logger on your Connection:

use Hector\Connection\Connection;
use Hector\Connection\Log\Logger;

$logger = new Logger();
$connection = new Connection(
    dsn: 'mysql:host=localhost;dbname=mydb',
    username: 'root',
    password: 'secret',
    logger: $logger,
);

// ... run your queries ...

foreach ($logger->getLogs() as $log) {
    echo $log->getStatement() . "\n";
    print_r($log->getParameters());
    echo $log->getDuration() . "ms\n";
}

See Connection — Query logging.

How do I avoid N+1 queries with relationships?

Use eager loading with with() on the query builder:

$users = User::query()
    ->with(['posts', 'profile'])
    ->all();

This loads the related entities in a single batch query instead of one query per entity.

For entities that are already loaded, use load():

$user = User::find(1);
$user->load(['posts']);

See Builder — Eager loading and Entities — Loading relations on-demand.


Schema & cache

Schema introspection is slow — how do I speed it up?

Use a PSR-16 cache implementation to persist schema metadata across requests:

$orm = OrmFactory::orm(
    options: ['schemas' => ['my_database']],
    connection: $connection,
    cache: $psr16Cache,
);

The schema will be introspected once, cached, and reused on subsequent requests.

See Cache.

When should I clear the schema cache?

Clear the cache whenever your database schema changes — typically after running migrations or manually altering tables.

$cache->clear();
// or delete the specific key:
$cache->delete('HECTOR_ORM');

Integrate this into your deployment pipeline, right after database migrations.


Migrations

Can I roll back a migration?

Only if the migration implements ReversibleMigrationInterface and provides a down() method. The runner will throw a MigrationException if you try to revert a non-reversible migration.

$runner->down(steps: 1); // Revert the last migration

See Migration.

In what order are migrations executed?

Migrations are executed in the order provided by the migration provider:

  • DirectoryProvider: files are sorted alphabetically by filename. Use a timestamp prefix (e.g. 20260101000000_CreateUsers.php) to control the order.
  • Psr4Provider: classes are sorted alphabetically by FQCN.
  • ArrayProvider: order of insertion.

See Migration — Providers.

Last updated: Wed, 18 Mar 2026 16:03