Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto
12 changes: 11 additions & 1 deletion .github/workflows/coding-style.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
name: Coding Style

on: [push, pull_request]
on:
push:
branches: ["**"]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
nette_cc:
Expand Down
16 changes: 11 additions & 5 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
name: Static Analysis (only informative)
name: Static Analysis

on:
push:
branches:
- main
branches: ["**"]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
phpstan:
Expand All @@ -13,9 +20,8 @@ jobs:
- uses: actions/checkout@v6
- uses: shivammathur/setup-php@v2
with:
php-version: 8.3
php-version: "8.3"
coverage: none

- run: composer install --no-progress --prefer-dist
- run: composer phpstan -- --no-progress
continue-on-error: true # is only informative
72 changes: 51 additions & 21 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,38 @@ name: Tests

on:
push:
branches: ["**"]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
php-extensions: iconv, json, mbstring, xml, gd, intl, tokenizer, mysqli
php-tools: composer:v2

jobs:
tests:
name: PHP ${{ matrix.php }} tests

runs-on: ubuntu-latest

strategy:
fail-fast: false

matrix:
php: ["8.3", "8.4", "8.5"]

services:
mysql:
image: mysql:8.4

env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test

ports:
- 3306:3306

options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1 -uroot -proot"
--health-interval=10s
Expand All @@ -53,24 +56,51 @@ jobs:
- name: Validate composer.json
run: composer validate --strict

- name: Cache Composer dependencies
uses: actions/cache@v6
with:
path: vendor
key: composer-${{ runner.os }}-${{ matrix.php }}-${{ hashFiles('composer.lock') }}
restore-keys: |
composer-${{ runner.os }}-${{ matrix.php }}-

- name: Install dependencies
run: composer install --no-progress --prefer-dist

- name: Import MySQL database
run: |
mysql --host 127.0.0.1 \
--port 3306 \
-uroot \
-proot \
test < tests/db.sql
- name: Import test database
run: mysql --host 127.0.0.1 --port 3306 -uroot -proot test < tests/db.sql

- name: Run tests
run: composer tester

lowest_dependencies:
name: Lowest dependencies
runs-on: ubuntu-latest

services:
mysql:
image: mysql:8.4
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h 127.0.0.1 -uroot -proot"
--health-interval=10s
--health-timeout=5s
--health-retries=10
--health-start-period=10s

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: ${{ env.php-extensions }}
tools: ${{ env.php-tools }}
coverage: none

- name: Install lowest dependencies
run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable

- name: Import test database
run: mysql --host 127.0.0.1 --port 3306 -uroot -proot test < tests/db.sql

- name: Run tests
run: vendor/bin/tester -p php tests -s -C
run: composer tester
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ Use this file as package-specific guidance when working in this repository. Keep
- `.github/`: CI workflows; keep their PHP versions and commands consistent with Composer and the package scripts.
- `composer.json`: package metadata, dependency constraints, autoloading, and available Composer scripts.

## Key Parts
- `Drago\Database\Database` trait reads the table, primary key and entity class from the `Drago\Attr\Table` attribute and needs a `$connection` property.
- `ExtraFluent::record()` and `recordAll()` return the entity class when one is set, otherwise Dibi rows; never pass `null` to `Result::setRowClass()` (Dibi would return arrays).
- `save()` updates when the primary key value is greater than zero, otherwise inserts. `EntityOracle` uses uppercase keys.
- `tests/Database/Database.phpt` needs MySQL (CI provides it and fails there instead of skipping); the other tests run without a database.

## Dependencies

Composer constraints describe the package contract. Before changing integration behavior, inspect the source and the actual call sites in this package; do not infer runtime usage solely from dependency declarations.
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"require-dev": {
"nette/bootstrap": "^3.2",
"nette/tester": "^2.5",
"tracy/tracy": "^2.7",
"tracy/tracy": "^2.10",
"phpstan/phpstan-nette": "^2.0"
},
"autoload": {
Expand All @@ -29,6 +29,6 @@
},
"scripts": {
"phpstan": "phpstan analyse",
"tester": "tester tests -s"
"tester": "tester tests -s -C"
}
}
5 changes: 5 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ parameters:
paths:
- src
- tests
excludePaths:
analyseAndScan:
- tests/tmp (?)
- tests/*/temp/* (?)
- tests/*/*/temp/* (?)
109 changes: 57 additions & 52 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,108 +26,113 @@ composer require drago-ex/database

## Basic Model Example

A model uses the `Database` trait and describes its table with the `Table` attribute
(table name and primary key). The trait reads the connection from the `$connection` property:

```php
#[Table('table_name', 'primary_key')]
use Dibi\Connection;
use Drago\Attr\Table;
use Drago\Database\Database;

#[Table(table_name, id)]
class Model
{
use Database;
use Database;

public function __construct(
protected Connection $connection,
) {
}
}
```

## Common Queries

Reading records from a table:
```php
$this->model->read('*');
```
The query methods return a query builder (`ExtraFluent`). Fetch the result with `record()` or
`recordAll()`, or run a write query with `execute()`:

Find records by column name:
```php
$this->model->find('column', 'value');
```
// All records
$rows = $this->model->read(*)->recordAll();

Get a record by ID:
```php
$this->model->get(1);
```
// Records by column value
$rows = $this->model->find(column, value)->recordAll();

Delete a record by column name:
```php
$this->model->delete('column', 'value');
// One record by the primary key, or null
$row = $this->model->get(1)->record();

// Delete records by column value
$this->model->delete(column, value)->execute();
```

Save records as an array (update if `id` is provided):
Without an entity class, records are returned as `Dibi\Row` objects.

Save a record as an array. When the array contains the primary key with a value greater than zero,
the record is updated, otherwise a new record is inserted:
```php
$this->model->save(['column' => 'value']);
$this->model->save(['id' => 1, 'column' => 'value']);
```

## Using Entities

```php
class SampleEntity extends Drago\Database\Entity
use Drago\Database\Entity;

class SampleEntity extends Entity
{
public const Table = 'name';
public const PrimaryKey = 'id';
public const string Table = name;
public const string PrimaryKey = id;

public ?int $id = null;
public string $sample;
}
```

Use the entity in a model:
Use the entity in a model, so records are returned as `SampleEntity` objects:
```php
#[Table(SampleEntity::Table, SampleEntity::PrimaryKey, entity: SampleEntity::class)]
class Model
{
/** @use Database<SampleEntity> */
use Database;
}
```
use Database;

Fetch records as objects:
```php
$row = $this->model->find('id', 1)->record();
public function __construct(
protected Connection $connection,
) {
}
}

// Accessing properties
echo $row->id;
$row = $this->model->get(1)->record();
echo $row->sample;

$rows = $this->model->read(*)->recordAll();
```

## Save Entity Records

To save entity data (update record if `id` is present):
Entities are saved the same way as arrays (updated when the primary key is set):
```php
$entity = new SampleEntity;
$entity->id = 1;
$entity->sample = 'sample';
$entity->sample = sample;

$this->save($entity);
$this->model->save($entity);
```

## Advanced Features
## Transactions

### Entity Class for Database Mapping

You can use a custom entity class with database mapping:
```php
#[Table(SampleEntity::Table, SampleEntity::PrimaryKey, entity: SampleEntity::class)]
class Model
{
/** @use Database<SampleEntity> */
use Database;
$this->model->beginTransaction();
try {
$this->model->save($entity);
$this->model->commit();
} catch (Throwable $e) {
$this->model->rollBack();
throw $e;
}

// Fetch records directly as objects
$row = $this->model->find('id', 1)->record();

// Access the object's properties
echo $row->id;
echo $row->sample;

// Fetch all records
$allRecords = $this->model->read('*')->recordAll();
```

### Entity Generation
## Entity Generation

For automatic entity generation, consider using the Drago Generator tool: [https://github.com/drago-ex/generator](https://github.com/drago-ex/generator)
Loading
Loading