ascetic-soft/rowcast-bundle
Symfony bundle integrating ascetic-soft/rowcast connection and DataMapper, with optional rowcast-schema migrations/services and console commands. Supports SQL profiler integration, configurable DSN/auth/options, transactions, schema paths, and query profiling thresholds.
Install the Bundle:
composer require ascetic-soft/rowcast-bundle
Ensure RowcastBundle is auto-registered in config/bundles.php or add it manually.
Configure the Bundle:
Create config/packages/rowcast.yaml:
rowcast:
connection:
dsn: '%env(DATABASE_DSN)%'
username: '%env(DATABASE_USER)%'
password: '%env(DATABASE_PASSWORD)%'
First Use Case:
Inject DataMapper into a service/repository and use it to fetch data:
use AsceticSoft\Rowcast\DataMapper;
final readonly class UserRepository {
public function __construct(private DataMapper $mapper) {}
public function findById(int $id): ?array {
return $this->mapper->fetchOne('SELECT * FROM users WHERE id = :id', ['id' => $id]);
}
}
Verify Connection:
Use the rowcast:query console command to test connectivity:
bin/console rowcast:query "SELECT 1"
DataMapper to abstract database operations.
final readonly class PostRepository {
public function __construct(private DataMapper $mapper) {}
public function findAllPublished(): array {
return $this->mapper->fetchAll('SELECT * FROM posts WHERE published_at IS NOT NULL');
}
}
DataMapper with custom hydrators for complex objects.ascetic-soft/rowcast-schema):
#[Rowcast\Table(name: 'users')]
class User {
#[Rowcast\Column(type: 'integer', primary: true)]
public int $id;
#[Rowcast\Column(type: 'string', length: 255)]
public string $email;
}
Configure in rowcast.yaml:
rowcast:
schema:
path: '%kernel.project_dir%/src/Entity'
bin/console rowcast:diff --dry-run # Preview changes
bin/console rowcast:make # Generate migration files
bin/console rowcast:migrate # Apply migrations
nest_transactions: true) for granular control:
rowcast:
connection:
nest_transactions: true
Connection directly:
$this->connection->beginTransaction();
try {
$this->mapper->execute('INSERT INTO users (...) VALUES (...)');
$this->connection->commit();
} catch (\Exception $e) {
$this->connection->rollBack();
throw $e;
}
$users = $this->mapper->fetchAll(
'SELECT * FROM users WHERE status = :status',
['status' => 'active']
);
$stmt = $this->connection->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => 1]);
$user = $stmt->fetch();
Connection or DataMapper into controllers/services:
use AsceticSoft\Rowcast\DataMapper;
final readonly class UserController {
public function __construct(private DataMapper $mapper) {}
public function index(): Response {
$users = $this->mapper->fetchAll('SELECT * FROM users');
return $this->render('users/index.html.twig', ['users' => $users]);
}
}
Connection events (e.g., beforeQuery, afterQuery) for logging/auditing:
$connection->addListener(new class implements ConnectionListenerInterface {
public function beforeQuery(QueryEvent $event): void {
// Log query details
}
});
HydratorInterface to transform raw rows into domain objects:
use AsceticSoft\Rowcast\Hydrator\HydratorInterface;
final class UserHydrator implements HydratorInterface {
public function hydrate(array $row): User {
return new User(
id: $row['id'],
email: $row['email'],
createdAt: new \DateTimeImmutable($row['created_at'])
);
}
}
DataMapper:
$mapper->setHydrator(User::class, new UserHydrator());
SchemaParserInterface for non-standard schema formats (e.g., JSON).PlatformInterface for database-specific behaviors (e.g., PostgreSQL JSONB support).dev environment (rowcast.yaml):
rowcast:
profiler:
enabled: true
RowcastDataCollector.$connection = new Connection(
dsn: 'sqlite::memory:',
username: '',
password: ''
);
Connection or DataMapper in unit tests:
$mockConnection = $this->createMock(ConnectionInterface::class);
$mockConnection->method('fetchAll')->willReturn([...]);
path pointing to a directory in rowcast.schema.path, ensure ascetic-soft/rowcast-schema is installed and AttributeSchemaParser is available. Otherwise, fall back to file-based parsing.migration_table (default: _rowcast_migrations) doesn’t conflict with existing tables. Prefix with your app name if needed (e.g., myapp_rowcast_migrations).mysql://user:pass@localhost/db or pgsql://user:pass@localhost/db).$this->connection->execute('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
profiler.enabled: false in production to avoid performance impact.max_queries to prevent memory issues in long-running requests:
rowcast:
profiler:
max_queries: 100
composer dump-autoload if adding new classes.User referencing Post which references User).rowcast:query, use --param for each parameter to avoid ambiguity:
bin/console rowcast:query "SELECT * FROM users WHERE id = :id" --param id=1
jq or less.rowcast:query to verify connectivity:
bin/console rowcast:query "SHOW TABLES"
APP_DEBUG=1) to see connection errors.rowcast:diff --dry-run before applying migrations.rowcast:status to check migration status:
bin/console rowcast:status
How can I help you explore Laravel packages today?