PHPackages                             qkskima/model - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Admin Panels](/categories/admin)
4. /
5. qkskima/model

ActiveTypo3-cms-extension[Admin Panels](/categories/admin)

qkskima/model
=============

A lean but powerful DSL-based model system for TYPO3 with Symfony validation, property access, nested relations, and business rule validation. QkSkima Model is an alternative approach, when you think Extbase is too heavy for the job.

0.0.5(4mo ago)050MITPHPPHP &gt;=8.1

Since Dec 10Pushed 4mo agoCompare

[ Source](https://github.com/QkSkima/Model)[ Packagist](https://packagist.org/packages/qkskima/model)[ Docs](https://www.qkskima.com)[ RSS](/packages/qkskima-model/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (5)Dependencies (3)Versions (6)Used By (0)

QkSkima Model
=============

[](#qkskima-model)

A lean but powerful DSL-based model system for TYPO3 with Symfony validation, property access, nested relations, and business rule validation. QkSkima Model is an alternative approach, when you think Extbase is too heavy for the job.

Features
--------

[](#features)

- **ActiveRecord-style CRUD** operations (create, save, update, destroy)
- **Automatic hydration** from form data with nested relations
- **Symfony validation** with attribute-based constraints
- **Business rule validation** layer for complex domain logic
- **Nested model support** with automatic validation propagation
- **TYPO3 QueryBuilder** integration
- **Comprehensive error handling** with structured error messages
- **TYPO3 specific database operations** like softDelete, hide, show, findAll

Recommendations
---------------

[](#recommendations)

For building RESTful API endpoints that work seamlessly with this model system, we highly recommend using the **`qkskima/api`** composer package.

### Why Use QkSkima Api?

[](#why-use-qkskima-api)

The `qkskima/api` package is specifically designed to complement this model system and provides:

- **Structured API Controllers**: Organize your API endpoints with controllers and actions
- **Automatic CSRF Protection**: Built-in CSRF token validation for secure API calls
- **TYPO3 Frontend Authentication**: Leverage TYPO3's native frontend user authentication system
- **Seamless Model Integration**: Works perfectly with the BaseModel validation and error handling

```
composer require qkskima/api
```

Installation
------------

[](#installation)

```
composer require qkskima/model
```

Architecture
------------

[](#architecture)

### Core Classes

[](#core-classes)

1. **BaseModel** - Abstract base class providing all core functionality
2. **BusinessRuleInterface** - Contract for business rule validators
3. **BusinessRuleResult** - Value object for validation results
4. **TYPO3ModelTrait** - Trait to add TYPO3 specific functionality

Usage
-----

[](#usage)

### Defining a Model

[](#defining-a-model)

```
use QkSkima\Model\BaseModel;
use Symfony\Component\Validator\Constraints as Assert;

class Order extends BaseModel
{
    protected const TABLE = 'tx_sitepackage_orders';

    #[Assert\NotBlank]
    public ?string $orderNumber = null;

    #[Assert\Email]
    public ?string $customerEmail = null;

    #[Assert\Valid]
    public array $orderItems = [];

    protected function getRelations(): array
    {
        return [
            'orderItems' => [
                'class' => OrderItem::class,
                'type' => 'many'
            ]
        ];
    }

    protected function getBusinessRules(): array
    {
        return [
            new OrderDateBusinessRule(),
            new OrderTotalBusinessRule(),
        ];
    }
}
```

### Creating Records

[](#creating-records)

```
// Method 1: Create with validation and save
$order = Order::create($formData);

// Method 2: Create, validate, and save separately
$order = Order::fromArray($formData);
if ($order->validate()) {
    $order->save();
}
```

### Updating Records

[](#updating-records)

```
$order = Order::find(123);
$order->update([
    'status' => 'completed',
    'customerName' => 'Updated Name'
]);
```

### Deleting Records

[](#deleting-records)

```
$order = Order::find(123);
$order->destroy();
```

### Handling Validation Errors

[](#handling-validation-errors)

```
$order = Order::create($formData);

if ($order->hasErrors()) {
    $errors = $order->getErrors();

    // Structure:
    // [
    //     'customerEmail' => ['Invalid email address'],
    //     'orderItems.0.startDate' => ['Start date must be in the future'],
    //     'totalAmount' => ['Total amount does not match']
    // ]
}
```

Nested Relations
----------------

[](#nested-relations)

The system automatically handles nested model hydration and validation:

```
$formData = [
    'orderNumber' => 'ORD-001',
    'customerEmail' => 'test@example.com',
    'orderItems' => [
        [
            'productName' => 'Widget A',
            'quantity' => 2,
            'unitPrice' => 50.00
        ],
        [
            'productName' => 'Widget B',
            'quantity' => 1,
            'unitPrice' => 30.00
        ]
    ]
];

$order = Order::fromArray($formData);
// orderItems are automatically converted to OrderItem instances
// and validated recursively
```

This makes it easy to process the creation of nested relations through a form.

Business Rules
--------------

[](#business-rules)

Business rules provide domain-specific validation logic that runs after syntactic validation:

```
class OrderDateBusinessRule implements BusinessRuleInterface
{
    public function getName(): string
    {
        return 'order_date_validation';
    }

    public function validate(BaseModel $model): BusinessRuleResult
    {
        $result = BusinessRuleResult::success();

        if (!$model instanceof Order) {
            return $result;
        }

        $orderDateTime = new \DateTime($model->orderDate);
        $now = new \DateTime();

        if ($orderDateTime > $now) {
            $result->addViolation(
                'orderDate',
                'Order date cannot be in the future'
            );
        }

        return $result;
    }
}
```

Business rules can:

- Access database via TYPO3 QueryBuilder
- Call external services
- Perform complex calculations
- Add violations to specific fields (including nested fields)

### Adding Business Rule Violations to Nested Fields

[](#adding-business-rule-violations-to-nested-fields)

```
public function validate(BaseModel $model): BusinessRuleResult
{
    $result = BusinessRuleResult::success();

    // Validate nested order items
    foreach ($model->orderItems as $index => $item) {
        if ($item->startDate < new \DateTime()) {
            $result->addViolation(
                "orderItems.{$index}.startDate",
                'Start date must be in the future'
            );
        }
    }

    return $result;
}
```

Validation Flow
---------------

[](#validation-flow)

1. **Syntactic Validation** - Symfony constraints on properties
2. **Nested Validation** - Recursive validation of related models
3. **Business Rule Validation** - Custom domain logic (only if syntactic validation passes)

Database Integration
--------------------

[](#database-integration)

The system uses TYPO3's QueryBuilder for all database operations:

- `save()` - Inserts new records or updates existing ones
- `destroy()` - Deletes records and cascades to relations
- Automatic timestamp handling (`crdate`, `tstamp`)
- Support for custom queries via `getQueryBuilder()`

### Custom Finder Methods

[](#custom-finder-methods)

```
public static function find(int $uid): ?self
{
    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
        ->getQueryBuilderForTable(self::TABLE);

    $row = $queryBuilder
        ->select('*')
        ->from(self::TABLE)
        ->where(
            $queryBuilder->expr()->eq('uid', $uid)
        )
        ->executeQuery()
        ->fetchAssociative();

    return $row ? self::fromArray($row) : null;
}
```

Error Structure
---------------

[](#error-structure)

Errors are returned as a flat array with dot-notation for nested fields:

```
[
    'customerEmail' => ['Invalid email address'],
    'totalAmount' => ['Must be positive', 'Does not match total'],
    'orderItems.0.quantity' => ['Must be positive'],
    'orderItems.1.startDate' => ['Start date must be in the future']
]
```

This structure makes it easy to:

- Display errors in forms
- Map errors to specific input fields
- Handle nested validation feedback

Best Practices
--------------

[](#best-practices)

1. **Always validate before save**: The `create()` method does this automatically
2. **Use business rules for domain logic**: Keep Symfony constraints for syntax/format
3. **Define relations explicitly**: Use `getRelations()` for nested models
4. **Handle errors gracefully**: Check `hasErrors()` after operations
5. **Override saveRelations/destroyRelations**: For custom cascade behavior

### Custom Business Rules with Database Access

[](#custom-business-rules-with-database-access)

```
class UniqueOrderNumberRule implements BusinessRuleInterface
{
    public function validate(BaseModel $model): BusinessRuleResult
    {
        $result = BusinessRuleResult::success();

        if (!$model instanceof Order) {
            return $result;
        }

        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
            ->getQueryBuilderForTable('tx_sitepackage_orders');

        $count = $queryBuilder
            ->count('uid')
            ->from('tx_sitepackage_orders')
            ->where(
                $queryBuilder->expr()->eq(
                    'order_number',
                    $queryBuilder->createNamedParameter($model->orderNumber)
                ),
                $queryBuilder->expr()->neq(
                    'uid',
                    $queryBuilder->createNamedParameter($model->uid ?? 0)
                )
            )
            ->executeQuery()
            ->fetchOne();

        if ($count > 0) {
            $result->addViolation(
                'orderNumber',
                'This order number already exists'
            );
        }

        return $result;
    }
}
```

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance75

Regular maintenance activity

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity37

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~6 days

Total

5

Last Release

136d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/5fd0da54b69c15d4964484de604f904d9cd43f55a214a1d4bdc3e9f92cad4c37?d=identicon)[atkins](/maintainers/atkins)

---

Top Contributors

[![colinatkins](https://avatars.githubusercontent.com/u/3305205?v=4)](https://github.com/colinatkins "colinatkins (6 commits)")

---

Tags

phpvalidationmodelattributescrudextbaselightweightrepositoryddddomain modeltypo3guardbusiness-rules

### Embed Badge

![Health badge](/badges/qkskima-model/health.svg)

```
[![Health](https://phpackages.com/badges/qkskima-model/health.svg)](https://phpackages.com/packages/qkskima-model)
```

###  Alternatives

[jamierumbelow/codeigniter-base-model

CodeIgniter base CRUD model to remove repetition and increase productivity

1.0k2.6k](/packages/jamierumbelow-codeigniter-base-model)[sandstorm/crudforms

Create easy CRUD forms for Domain Models in Flow and Neos CMS

2220.0k](/packages/sandstorm-crudforms)[inani/nova-resource-maker

A Laravel Nova package to help you generate resources fields

2513.7k](/packages/inani-nova-resource-maker)[2lenet/crudit-bundle

The easy like Crud'it Bundle.

1714.8k8](/packages/2lenet-crudit-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
