Download the PHP package phpibe/sqlc-php without Composer

On this page you can find all versions of the php package phpibe/sqlc-php. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package sqlc-php

sqlc-php | https://phpibe.github.io/sqlc-php

A PHP code generator inspired by sqlc for Go. It reads your SQL schema and annotated query files, and generates fully-typed PHP 8.4 classes that use PDO under the hood — no ORM, no magic, just plain objects derived directly from your database.


How it works

  1. Parse — reads CREATE TABLE statements and builds a schema catalog.
  2. Analyze — resolves every query's parameters and result columns against the catalog.
  3. Generate — emits one readonly DTO per table, PHP backed enums for ENUM columns, one query class per @group, and optionally a matching interface per query class.

Requirements


Installation

Then run the CLI from your project root:


Configuration — sqlc.yaml

Minimal single-target config

generate_interfaces defaults to true — interfaces are generated unless explicitly set to false:

Multiple output targets

targets accepts any number of entries — each produces a separate generation pass using the same parsed schema:

Each target inherits the global engine, language, and type_overrides. A target can override any of them locally.

Multiple schema files

schema accepts both a scalar string (single file) and a YAML list. All files are parsed and merged into a single catalog:

Per-target queries

Each target has its own queries list. queries accepts a scalar or a list:

The CLI prints a per-file count alongside the total:

Inflection language

sqlc-php uses doctrine/inflector to singularise table names when inferring class names. Set globally or per-target:

With language: spanish, tables like usuarios, pedidos, categorias produce Usuario, Pedido, Categoria without needing @group on every query.

Table name english (doctrine) spanish
analyses Analysis
matrices Matrix
usuarios Usuarios Usuario
pedidos Pedido Pedido
users User

The @group annotation always takes precedence over inferred names.

virtual_tables — views and external tables

virtual_tables: declares tables that exist in the database but have no CREATE TABLE in the schema files — views, materialized views, or tables from other schemas.

Nullability convention — all columns are NOT NULL by default. Specify nullable: true only for columns that can be null. This is the inverse of schema parsing where NOT NULL must be explicit.

Virtual tables are registered in the SchemaCatalog for column type resolution. Queries against them work exactly like queries against real tables. The only difference: no Model class is generated for virtual tables.

Generates UserSummaryQuery.php with correct column types, but no UserSummary.php model.

includes — splitting the config

includes: loads additional YAML fragments and merges their list fields (virtual_tables:, type_overrides:, targets:) into the main config. Scalar fields (engine:, language:) in include files are silently ignored.

All virtual_tables: entries from all includes are accumulated. Multiple include files can each declare their own virtual_tables: — they are all merged before processing.

Field Behaviour
virtual_tables: Accumulated — all entries from all includes + main file
type_overrides: Accumulated — includes first, main file appended last
targets: Accumulated — includes first, main file appended last
engine:, language: Ignored in includes — main file always controls scalars

Type override precedence

Priority Rule Description
1 column Exact table.column match — wins over everything
2 db_type Matches any column whose SQL type matches
3 Default Built-in SQL → PHP type mapping

Nullable override

Any type_override entry accepts an optional nullable field:

When nullable is omitted, nullability is inherited from the schema.

Default SQL → PHP type mapping

SQL type PHP type Notes
INT, BIGINT, SMALLINT, TINYINT int
DECIMAL, FLOAT, DOUBLE float
VARCHAR, CHAR, TEXT string
DATE, DATETIME, TIMESTAMP \DateTimeImmutable fromRow uses new \DateTimeImmutable(...)
TIME string no standard PHP time-interval type
JSON array hydrated via json_decode in fromRow
ENUM(...) EnumClass generates a PHP 8.1 backed enum file
BOOLEAN bool

Annotating queries

Every query must have at minimum a @name and a @returns annotation, written as SQL comments:

Return type semantics

Annotation PHP return type Behaviour
:many ModelClass[] Returns an array; empty array if no rows
:many-paginated ModelClass[] Like :many but auto-injects LIMIT/OFFSET params
:one ModelClass Returns the object; throws RuntimeException if no row found
:opt ModelClass\|null Returns the object or null if no row found
:exec void Executes the statement (INSERT, UPDATE, DELETE)
:batch int Executes the same INSERT/UPDATE N times in a transaction; returns row count
:transaction void Runs multiple @calls methods sequentially in one transaction

Query examples

SELECT * — returns the table model

Generated method:


SELECT * with WHERE — :one throws, :opt returns null

Generated methods:


SELECT specific columns

When columns come from a single table, the return type is still the table model:


JOIN — generates a result DTO

When columns come from multiple tables, a dedicated *Row DTO is generated:

Generated files:


Aggregate and expression columns

sqlc-php infers types from SQL functions. Aliases are generated automatically when none is provided (mirroring sqlc/Go behaviour):

Generated DTO:

Expression type inference table

SQL expression PHP type Auto-alias (no AS)
COUNT(*) int count
SUM(int_col) ?int sumIntCol
SUM(decimal_col) ?float sumDecimalCol
AVG(col) ?float avgCol
MIN(col) ?{type of col} minCol
MAX(col) ?{type of col} maxCol
COALESCE(col, x) {type of col} (not nullable) coalesceCol
IFNULL(col, x) {type of col} (not nullable) ifnullCol
NULLIF(col, x) ?{type of col} nullifCol
CONCAT(...) ?string concat
CAST(x AS INT) int castX
UPPER/LOWER/TRIM(col) string upper / lower / trim
LENGTH(col) int length
CASE WHEN ... ?string case
Unknown expression mixed col_1, col_2

:many-paginated — automatic pagination

Using :many-paginated instructs sqlc-php to automatically append LIMIT :limit OFFSET :offset to the SQL and add those two parameters to the generated method with sensible defaults.

Generated method:

The SQL stored in the class becomes:

Any user-defined parameters appear first in the signature; $limit and $offset are always last:


IN() clauses — array parameters

Parameters inside IN() clauses are automatically detected and handled with dynamic placeholder expansion at runtime. No manual SQL building required.

Generated method:

The element type in the docblock (int[]) is inferred from the column type, just like any other parameter.

Mixed IN and regular parameters

Regular params are bound with bindValue(); IN-list params are expanded positionally and passed to execute(array). The two mechanisms are combined transparently.

Multiple IN clauses

Each IN-list param gets its own placeholder variable ($__ph_ids, $__ph_roleIds) and its values are spread into execute() in order.

NOT IN

NOT IN (:param) works exactly like IN (:param):


:batch — bulk operations in a transaction

Executes the same INSERT or UPDATE query N times inside a single PDO transaction. Rolls back and re-throws on any failure.

The statement is prepared once and reused for every row. An empty $rows array returns 0 without opening a transaction.


:transaction — multi-method transactions

Groups multiple :exec methods from the same Query class into a single transaction via @calls. Requires @group since there is no SQL to infer the group from.

If the :transaction method has @param declarations, they are forwarded to all callee methods.


Prepared statement caching

Opt-in per target. Caches PDOStatement objects to avoid re-preparing the same SQL on every call — especially useful in loops.

With caching enabled, the generated class includes private array $stmts = [] and every method uses:


UPDATE / DELETE — :exec


MySQL ENUM → PHP backed enum

When a column is defined as ENUM(...), sqlc-php generates a PHP 8.1 backed enum file and uses it as the property type in the DTO. The fromRow method uses ::from() or ::tryFrom() depending on nullability.

Generated enum:

Generated DTO property and cast:

Nullable ENUM columns use ::tryFrom():

Enum naming convention: {SingularTable}{PascalColumn} — e.g. orders.statusOrderStatus, users.roleUserRole. Hyphenated values are converted to PascalCase: in-progresscase InProgress = 'in-progress'.


JSON column → typed array

JSON columns map to array in PHP and are automatically hydrated via json_decode in the generated fromRow:

For NOT NULL JSON columns, the fallback is ?? [] to guarantee a non-null array is always returned.


@deprecated — mark a method as deprecated

Adding @deprecated to a query causes the generated method to include a @deprecated PHPDoc tag. This is useful when migrating queries without breaking existing code.

Generated method:

The reason is optional — -- @deprecated without a message emits @deprecated alone.


@nillable — force a result column to be nullable

@nillable columnAlias forces a specific column in the result set to be ?type in the generated DTO or return type, regardless of how the column is declared in the schema.

This is useful in two scenarios:

LEFT JOIN — column may be NULL at runtime even though NOT NULL in schema:

Generated DTO (multi-table → custom DTO):

*Direct model queries (`SELECT `) — forces a dedicated DTO instead of reusing the table model:**

When @nillable is used on a query that would normally return the table model directly (single-table SELECT *), sqlc-php generates a dedicated *Row DTO so the nullability can be applied without mutating the base model class:

This generates GetUserProfileRow with public ?string $email instead of reusing User where email is NOT NULL.

Multiple @nillable annotations can be stacked. The annotation targets the output alias (the name after AS), or the column name when no alias is used.


@embed — nested objects for JOIN results

@embed ClassName prefix_ groups all result columns whose alias starts with prefix_ into a nested readonly value object instead of flattening them into the parent DTO.

Generated files:

Role.php — standalone readonly value object with stripped property names:

GetUserWithRoleRow.php — parent DTO with the nested Role object as a property:

Usage:

Multiple @embed groups on one query

Generates Role.php, Address.php, and GetUserFullRow.php with:

Naming convention

The DTO property name is derived from the prefix by stripping the trailing underscore:

The prefix can be written with or without trailing underscore in the annotation: @embed Role role_ and @embed Role role both produce the same result.


Optional parameters

Marking a parameter as @optional instructs sqlc-php to rewrite the SQL condition at generation time. When null is passed at runtime the filter is skipped entirely; when a value is passed it filters normally. No if statements or query builders required.

sqlc-php rewrites each optional condition before emitting any PHP:

Generated method:

Calling the method:

Mixing required and optional parameters

Required parameters always appear first in the signature; optional parameters follow with = null.

Supported operators

Operator Rewritten form
= (:param IS NULL OR col = :param)
<> (:param IS NULL OR col <> :param)
!= (:param IS NULL OR col != :param)
> (:param IS NULL OR col > :param)
< (:param IS NULL OR col < :param)
>= (:param IS NULL OR col >= :param)
<= (:param IS NULL OR col <= :param)
LIKE (:param IS NULL OR col LIKE :param)
ILIKE (:param IS NULL OR col ILIKE :param)

Parameter name validation

If a name declared in @optional does not match any :param token in the SQL, generation stops immediately with a fatal error:

Limitations

@optional is only safe on queries with a plain WHERE clause over a single table. The following shapes produce a fatal error at generation time:

For these cases, use PHP-side conditional query building instead.


Parameter type resolution

Named parameters (:paramName) are automatically typed by matching them to schema columns. Resolution order:

  1. @param annotation — explicit override: -- @param userId users.id
  2. Qualified referenceWHERE table.col = :param
  3. SET clauseSET col = :param
  4. camelCase → snake_case:updatedAt → looks up updated_at in the schema
  5. Fallbackmixed / PDO::PARAM_STR

Generated file structure

Model class example (User.php)

Query class example (UserQuery.php)

When generate_interfaces: true, the class declares implements UserQueryInterface:

Interface example (UserQueryInterface.php)


Usage in your application


Usage with Laravel

The recommended pattern is to wrap the generated query class inside a repository class, bind it in a Service Provider using the generated interface, and inject it into controllers or services via the constructor.

1. Create a repository

2. Register the binding in a Service Provider

If your application uses multiple database connections, pass the connection name explicitly:

3. Inject the repository into a controller

4. Inject into a service or job

5. Testing with the interface

Because the repository depends on UserQueryInterface, you can swap in a mock without touching the database:


CLI flags

--verify — CI check

Generates all files in memory and compares them against the existing output. Writes nothing. Exits 1 if anything is missing or out of date.

--dry-run — preview without writing

Prints the full content of every file that would be generated to stdout. Writes nothing to disk.

--diff — show what would change

Compares generated content against existing files and prints a colored unified diff. Exits 0 when nothing would change, 1 when there are differences. Writes nothing.


Running the tests

The test suite covers:

Suite File What it tests
Schema Parser tests/Parser/SchemaParserTest.php CREATE TABLE, ENUM, nullable, AUTO_INCREMENT, DEFAULT
Query Parser tests/Parser/QueryParserTest.php All annotations incl. @deprecated, @nillable, blank lines
Type Mapper tests/TypeMapper/MySQLTypeMapperTest.php Default mappings, nullable override, PDO constants
JSON Type tests/TypeMapper/JsonTypeTest.php JSON → array, json_decode casts
Config tests/Config/ConfigTest.php YAML parsing, scalar/list schema and queries, generate_interfaces
New Features v1.3 tests/Config/NewFeaturesTest.php Multiple schemas, nullable override, @deprecated, @nillable
New Features v1.4 tests/NewFeaturesV14Test.php :many-paginated, @nillable on direct models, targets, --dry-run, --diff
Embed tests/EmbedTest.php @embed annotation, EmbedDefinition, EmbedGenerator, nested DTO generation
Inflector tests/InflectorServiceTest.php InflectorService, all 6 languages, Config language field, group inference
Bug Fixes tests/BugFixTest.php Regression tests for v1.5.2 critical and medium bug fixes
IN() Params tests/InListParamTest.php IN/NOT IN detection, type inference, signature, placeholder expansion
TypeMapper Factory tests/TypeMapper/TypeMapperFactoryTest.php Interface contract, factory engine resolution, unsupported engine errors
Param Resolver tests/Resolver/ParamResolverTest.php WHERE/SET/UPDATE param resolution, camelCase→snake
Expression Resolver tests/Resolver/ExpressionTypeResolverTest.php All aggregate and scalar functions
Analyzer tests/Analyzer/QueryAnalyzerTest.php Full pipeline: model detection, JOINs, aggregates
SQL Rewriter tests/Rewriter/SqlRewriterTest.php All operators, unsafe construct guards
Optional Params tests/Analyzer/OptionalParamTest.php @optional end-to-end
Enum Generator tests/Generator/EnumGeneratorTest.php ENUM parsing, backed enum generation, fromRow casts
Interface Generator tests/Generator/InterfaceGeneratorTest.php Interface code, method signatures, implements clause
Generator tests/Generator/GeneratorTest.php Code structure, docblock indentation, PDO bindings
Verify Flag tests/VerifyFlagTest.php --verify exit codes, no file writes

Project structure


Changelog

[2.9.3] — scoped_dtos path includes @class group

Iterates the scoped_dtos: true feature. The subdirectory now includes the @class group as a parent:

This matches the exact structure from the feature request:

Generates:

Also enables two groups with the same method name to coexist:

[2.9.2] — scoped_dtos & embed collision detection

Embed collision detection: when two queries use @embed with the same class name but different columns, generation now aborts with a clear error instead of silently overwriting:

scoped_dtos: true: each query's DTOs and embeds get a dedicated subdirectory named after the method. Collisions become structurally impossible:

Backward compatible — scoped_dtos: false by default. 16 new tests.

[2.9.1] — table.* with @embed bugfix

Bug fix: using reserve_billing.* alongside @embed columns with __ prefixes now generates the correct return type.

Root cause: detectDirectModel counted reserve__id (from reserve table) as proof of multiple tables, discarding the @dto annotation and falling back to GetDetailsRow.

Fix: columns with __ in their alias are excluded from the single-table check — they are embedded object fields by design. @embed still forces DTO mode (the plain model doesn't have nested properties), but the @dto class name is correctly used as the return type.

13 new tests in tests/TableWildcardEmbedTest.php.

[2.9.0] — OR groups in Criteria & UNION queries

Criteria::orGroup() — adds OR conditions to @searchable criteria. Fully immutable, backward compatible.

UNION / UNION ALL — natively supported. Types resolved from first SELECT. @searchable, @partial, @returning rejected on UNION with clear errors.

35 new tests in tests/OrGroupUnionTest.php.

[2.8.5] — Technical debt refactor

Three structural improvements with no user-facing behavior changes — same SQL output, same generated code, better internals.

Fix A — renderPaginateCore(): the duplicated COUNT+SELECT body (∼50 lines) that appeared in both renderPaginateMethod and renderSearchablePaginateMethod is now a single shared method. Both entry points pass their specific SQL expressions and binding blocks as parameters.

Fix B — InterfaceGenerator strategy dispatch: the monolithic renderMethodSignature() with 7 if/elseif branches is replaced by a match() dispatch table routing to one dedicated renderer per return-type family. Adding a new return type now means adding one method — the router never changes.

Fix C — renderBindings(string $stmtVar = '$stmt'): root cause of the $stmt undefined bug in :paginated. renderBindings() now accepts the PDO statement variable name explicitly. The :paginated methods call renderBindings($query, '$__countStmt') and renderBindings($query, '$__stmt') directly — the str_replace('$stmt->', ...) workaround is gone.

23 new tests in tests/TechDebtRefactorTest.php.

[2.8.0] — :paginated & @returning

:paginated — new return type (alongside :many, :one, etc.) that returns a PaginatedResult with items + metadata in one call:

@returning — INSERT that fetches and returns the created row:

Other changes:

[2.7.7] — toDebugBindings() for Debugbar integration

The bug: passing $q->bindings() directly to Debugbar showed [,1] because bindings() returns [value, PDO_TYPE] tuples — Debugbar serialized the inner array as a string.

Fix — Option A (recommended): toDebugSql() + empty bindings:

Fix — Option B: toString() + toDebugBindings():

[2.7.6] — query execution timing (durationMs)

[2.7.5] — PSR-3 logger & afterQuery hook

[2.7.4] — lastQuery() inspection

[2.7.3] — out: map form (per-type directories)

[2.7.1] — @partial (PATCH/UPDATE)

[2.7.0] — @searchable dynamic filters

[2.6.2] — symfony/yaml migration

[2.6.0] — --generate-schema

[2.5.3] — @class annotation and class_suffix

Generated: UserRepository.php with class UserRepository (and UserRepositoryInterface if generate_interfaces: true).

[2.5.2] — Optional pagination limit

[2.5.0] — @counted pagination

[2.4.0] — Watch mode

[2.3.0]

[2.2.0]

[2.1.1] — Bug fixes and DateTimeImmutable

Bug fixes

DateTimeImmutable mapping

[2.1.0] — virtual_tables and includes

[2.0.0] — Unified configuration

Migration from v1:

[1.6.0] — PostgreSQL groundwork

[1.5.3]

[1.5.2] — Bug fixes

Critical

Medium

Tests — 28 new regression tests in tests/BugFixTest.php.

[1.5.1]

[1.5.0]

[1.4.0]

[1.3.0]

[1.2.0]

[1.1.0]

[1.0.0]


All versions of sqlc-php with dependencies

PHP Build Version
Package Version
Requires php Version >=8.3
doctrine/inflector Version ^2.0
symfony/yaml Version ^6.0 || ^7.0 || ^8.0
psr/log Version ^1.0 || ^2.0 || ^3.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package phpibe/sqlc-php contains the following files

Loading the files please wait ...