StaticFactoryMapper
BackStaticFactoryMapper delegates row → object construction to a public static factory method on the target class. Use it in these cases:
- The target class has a
privateconstructor and a named static factory (User::fromRow(...)). - You want to keep the row-to-object logic on the domain class itself, not in many mapper implementations.
- You need custom coercion inside the factory, for example
created_atstring →\DateTimeImmutableor JSONB string → decoded array.
The factory method must have this exact signature:
public static function fromRow(array $row) : self;
The method does not receive the mapping Context. Your factory can need the original Query, the executing Client, or user-supplied data. In that case, implement RowMapper directly. For more information, read the Custom RowMapper section of the ConstructorMapper documentation.
For simple 1:1 constructor-parameter mapping, use ConstructorMapper. For type-driven coercion through flow-php/types, use TypeMapper.
DSL
static_factory_mapper(class-string<T> $class, non-empty-string $method) : StaticFactoryMapper<T>
Returns a RowMapper<T> ready for fetchInto / fetchOneInto / fetchAllInto / Cursor::map().
Basic Usage
<?php
use function Flow\PostgreSql\DSL\{pgsql_client, pgsql_connection, static_factory_mapper};
final readonly class User
{
private function __construct(
public int $id,
public string $name,
public string $email,
public \DateTimeImmutable $createdAt,
) {
}
/**
* @param array<string, mixed> $row
*/
public static function fromRow(array $row) : self
{
return new self(
id: (int) $row['id'],
name: (string) $row['name'],
email: (string) $row['email'],
createdAt: new \DateTimeImmutable((string) $row['created_at']),
);
}
}
$client = pgsql_client(pgsql_connection('host=localhost dbname=mydb'));
$user = $client->fetchOneInto(
static_factory_mapper(User::class, 'fromRow'),
'SELECT id, name, email, created_at FROM users WHERE id = $1',
[1],
);
fetchInto() - First Object or Null
Returns the first row mapped through the factory, or null for no rows:
<?php
$user = $client->fetchInto(
static_factory_mapper(User::class, 'fromRow'),
'SELECT id, name, email, created_at FROM users WHERE email = $1',
['john@example.com'],
);
fetchAllInto() - All Objects
<?php
/** @var User[] $users */
$users = $client->fetchAllInto(
static_factory_mapper(User::class, 'fromRow'),
'SELECT id, name, email, created_at FROM users ORDER BY name',
);
foreach ($users as $user) {
echo $user->name;
}
Streaming via Cursor
<?php
function processUser(object $user): void { /* your code */ }
$cursor = $client->cursor('SELECT id, name, email, created_at FROM large_users');
foreach ($cursor->map(static_factory_mapper(User::class, 'fromRow')) as $user) {
processUser($user);
}
For more information, read Cursors.
Error Handling
StaticFactoryMapper throws Flow\PostgreSql\Client\Exception\MappingException in these cases:
| Condition | Message pattern |
|---|---|
$class does not exist |
Failed to map row to "<class>": Class does not exist |
Factory method does not exist on $class |
Static factory method "<class>::<method>()" does not exist |
Factory method exists but is not declared static |
Factory method "<class>::<method>()" must be declared static |
Factory method is declared but not public |
Factory method "<class>::<method>()" must be declared public |
Factory method throws any \Throwable |
Failed to map row to "<class>": <original message>, with the original exception available via MappingException::getPrevious() |
The constructor validates $class / $method (existence, static, public) immediately. A misconfigured mapper therefore fails at wiring time, not at query time. Exceptions that the factory method throws show on the matching map() call. They are wrapped in a MappingException, and its getPrevious() returns the original throwable.
When to Reach for Something Else
| Need | Use |
|---|---|
| 1:1 column-to-constructor-parameter mapping with no coercion | ConstructorMapper |
Row validation / coercion via flow-php/types |
TypeMapper |
Access to Context (query / parameters / client / catalog) in your mapping logic |
Implement RowMapper directly |
| Strict object hydration of complex graphs | PostgreSQL Valinor Bridge |
Found a typo or an outdated section? Edit this page on GitHub