Framework DotApp Updated: 2025-05-04


About the Author

My name is Štefan Miščík and I am a senior fullstack web developer at Dotsystems s.r.o. (WEB)

Why Did I Develop DotApp?

1. I wanted to simplify my work.

Most frameworks forced me to take a truck even for a spaghetti shopping trip. That’s why I created DotApp – it’s scalable and can be a shopping bag, a cart, or a truck, fully automatically depending on the size of the load. DotApp is a framework designed to simplify web application development – fast, efficient, and without unnecessary clutter. It offers capabilities that make it ideal for projects of all sizes.

2. I wanted to test my skills.

It was 2014, and I wanted to see how much I really knew. Using existing frameworks is convenient, but I was drawn to the challenge of creating something of my own. I didn’t want to settle for an average solution; I aimed to build a framework that could hold its own even in demanding conditions. For me, DotApp was an opportunity to push my skills to the next level and prove what I’m capable of.


What's New ✨

DotApp continues to evolve with exciting new features to make development even more intuitive and elegant. Below are the latest additions:

New Facades

Facades (Router, Route, DB, Request) have been introduced to provide a cleaner and more elegant syntax for interacting with core components. Instead of using $dotApp->component->method, you can now use Component::method for improved readability. The original syntax via the $dotApp object remains fully functional, ensuring complete backward compatibility.

Router and Route Facades

Added Router and Route facades, which are identical aliases for $dotApp->router and $dotApp->middleware(). Choose Router:: or Route:: based on your preferred syntax for cleaner route and middleware definitions without needing the $dotApp object.


Router::get('/', fn() => 'Hello World');
Route::get('/', fn() => 'Hello World');
Router::post('/submit', fn() => 'Submitted');
Route::post('/submit', fn() => 'Submitted');
Router::get('/', 'page@main'); // Call a controller method
Route::get('/', 'page@main'); // Call a controller method
                    
DB Facade

Added DB facade, an alias for $dotApp->db. Use DB:: instead of $dotApp->db-> for a cleaner syntax when performing database operations like queries, schema management, or transactions.


DB::driver('pdo', $dotApp);
DB::q(function ($qb) {
    $qb->select('*')->from('users')->where('id', '=', 1);
})->first();
DB::schema(function ($schema) {
    $schema->createTable('posts', function ($table) {
        $table->id();
        $table->string('title');
    });
});
                    
Request Facade

Added Request facade, an alias for $dotApp->request. Use Request:: instead of $dotApp->request-> for a simpler syntax when accessing request data, such as paths, methods, or form submissions.


Request::getPath(); // Get the current request path
Request::getMethod(); // Get the HTTP method (e.g., GET, POST)
Request::data(); // Access request data (e.g., POST or JSON payload)
Request::form('myForm', fn($request, $name) => 'Form submitted!', fn($request, $name) => 'Invalid form');
                    


Important Note: Accessing DotApp Instance

The $dotApp variable, used throughout the examples in this documentation, represents the core instance of the DotApp framework. It is automatically available in index.php via the configuration in config.php. Anywhere else in your code, you can easily access the DotApp instance using:


use \Dotsystems\App\DotApp;
$dotApp = DotApp::DotApp();
            

This ensures you can call methods like $dotApp->router, $dotApp->module, or others from any part of your application. All examples in this documentation assume $dotApp is available, either automatically or through this simple instantiation.



Key Features of DotApp

Simplicity Without Compromise

DotApp combines intuitive design with high performance. You don’t need complex setups or excessive configurations – just define routes and modules, and everything else manages itself. Routes are processed only where needed, and no extra steps are required to maintain performance – it’s all automatic and efficient.


// Example of simplicity when working with the DotApp framework
namespace Dotsystems\App\Modules\DotAppDoc\Controllers;

class TestController1 {
    public static function testMiddlewareFn($request) {
        return "Hello " . $request->body(); // Adds text at the beginning
    }
    
    public static function mainFn($request) {
        return $request->body() . "World"; // Adds text at the end
    }
}

// Simple controller call
$dotApp->router
    ->get("/home", "dotappdoc:TestController1@mainFn")
    ->before("dotappdoc:TestController1@testMiddlewareFn");

// Result for /home: "Hello World"
            
Focus on Low Resource Consumption

DotApp keeps memory demands to a minimum – instead of loading massive route structures and configurations, it processes only what’s currently needed. This means faster startup and great performance even on weaker servers.

Fast Route Processing

DotApp intelligently filters only relevant modules and their routes, eliminating unnecessary searches. The result is swift loading even with thousands of routes.

Example

Demonstration of routing speed: Before displaying this page, 1000 unique static and 1000 unique dynamic, deliberately unorganized routes were automatically added to the router at random. The goal was to showcase fast loading despite 2000 extra unnecessary routes. None of them match the current URL, ensuring that all must go through the router’s matching process.


for ($i = 0; $i < 1000; $i++) {
    $this->dotApp->router->any($this->getData("base_url") . "_routa" . $i, function () use ($dotApp, $i) {
        $dotApp->unprotect($_POST);
        echo "This is route: " . $this->getData("base_url") . "_routa" . $i;
    }, true);
}

for ($i = 0; $i < 1000; $i++) {
    $this->dotApp->router->any($this->getData("base_url") . "_routa" . $i . "(?:/{language})?", function () use ($dotApp, $i) {
        $dotApp->unprotect($_POST);
        echo "This is route: " . $this->getData("base_url") . "_routa" . $i . "(?:/{language})?";
    });
}

// Try it out: /documentation/_routa7
            
$this->dotApp->router->get($this->getData("base_url") . "intro", function () use ($dotApp) {
    /*
        For info:
        $this->moduleName - variable automatically available in every initialized module
        $this->getData("base_url") - when registering module listeners, the documentation module sets the base_url of the module (/documentation/)
            $dotApp->on("dotapp.module_dotappdoc.init.start", function ($moduleObj) {
                $moduleObj->setData("base_url", "/documentation/");
            });
        Explained in the module documentation.
    */
    $viewVars['seo']['description'] = "Description";
    $viewVars['seo']['keywords'] = "Keywords";
    $viewVars['seo']['title'] = "Documentation for the PHP framework DotApp";
    return $dotApp->router->renderer->module($this->moduleName)->setView("index")->setViewVar("variables", $viewVars)->renderView();
});
                            


Displaying the page, including route creation, routing, and code generation using the templating system, took:

Modular Efficiency with Bidirectional Connectivity

DotApp processes only the routes of the active module, saving resources. Modules are interconnectable in both directions – they can activate their dependencies (e.g., module 3 loads module 1 via $dotApp->module("Module1")->load()) or be triggered by a parent module (e.g., module 1 activates modules 2 and 3 via listeners). This flexibility allows dynamic module combinations based on project needs.

Cascading Module Loading

If a module depends on another (e.g., BBB needs XXX), DotApp automatically loads XXX before completing BBB. This ensures reliability – no errors due to missing dependencies – and keeps the system lightweight by loading only what’s necessary.

Dynamic Dependency Management via Triggers and Listeners

Each module has triggers like init.start, loading, loaded, and more, which listeners respond to. For example, the dotapp.module.Module1.loading listener can trigger the loading of module 2 if module 1 is active. The load() function ensures a module is loaded only once, whether cascading (top-down) or bidirectional (bottom-up).

Note: Trigger names are case-insensitive, so dotapp.module.Module1.loading and Dotapp.Module.Module1.Loading are equivalent, but we recommend using the format dotapp.module.ClassName.eventName for consistency.


// Listener for cascading loading
$dotApp->on("dotapp.module.Module1.loading", function () use ($dotApp) {
    $dotApp->module("Module2")->load(); // Loads Module2 only if needed
});
            
Automatic Dependency Resolution and DI

Modules and their dependencies load automatically – just define the logic in initializeCondition() or listeners. Dependency Injection (DI) is simple and efficient – services are registered (e.g., singleton), and DotApp delivers them where needed without unnecessary overhead.


// Registering a singleton in a module
$this->singleton(DotApp::class, function () { return $this; });

// Using DI in a controller
namespace Dotsystems\App\Modules\DotAppDoc\Controllers;

use Dotsystems\App\DotApp;

class TestController2 {
    public static function injectionTest(DotApp $dotApp) {
        echo "Automatically injected DotApp";
    }
}
            
First Callback Wins

For each URL, only the first matching callback is retained – subsequent registration attempts are ignored, boosting performance and preventing conflicts.


$dotApp->router->get('/documentation/test1', "dotappdoc:className@fnName");
$dotApp->router->get('/documentation/test1', function () { return "Ignored"; });
// Only the first definition is used
            
Scalability for Small and Large Projects

DotApp is ideal for small sites and complex applications alike – it maintains low demands and high speed regardless of project scope. Large modules can be split into smaller parts that load recursively as needed.

No Unnecessary Overhead

DotApp focuses on the essentials – fast routing, minimal resource usage, and ease of use. It doesn’t burden you with features you don’t need.

DotApp Bridge

Live connection – a bridge between frontend and backend. Just use simple code:

<button {{ dotbridge:on(click)="dotcms.newsletter(newsletter.email)" }}>Subscribe</button>

and on the PHP side:


$dotApp->bridge->fn("dotcms.newsletter", function ($data) { /* Logic */ });
            

The button is automatically linked to the PHP function, with rich possibilities to be introduced in the documentation.

Example of generated code:


<button  dotbridge-key="bH7KL7nZ8LKREiYofUU5R6aWfOFs-E-" dotbridge-id="Aak8UmdBkLUGOal37v3H8PqJiH1U0Bl4HR9Rq0E0agaZ45K2025051731083108310807070831d74f87b083d52717ffde662ddaa753ca" dotbridge-event="click" dotbridge-data="2ftP3yEzEZIoyY7zjGJm7zk5RzFZV2JaK09ldnBWV3JvdHFjdHExeDhackg5T0IwaTRXZmlxTWYzdU09" dotbridge-data-id="Pq1wtYoY0DKqOQA2dUxgGmQ1dzI0elE5SlBQak5sZE8zMUlvdk5RMWZuMjBlQjFPNHRxWEF2V0p4MGxkMCttVTVobnRhWExHdFhRUklhWGs=" dotbridge-function="dotcms.newsletter" dotbridge-inputs="newsletter.email">Subscribe</button>
            
HTML

DotApp is tailor-made for developers who want an efficient tool without fluff. It offers speed, low demands, and simplicity that makes work easier. It’s a framework that proves less can be more – with results that speak for themselves.

Try DotApp and see for yourself!

Installation

The DotApp application is not installed in the standard way via composer install because it is not a library but a complete application with its own architecture. Instead, follow these steps:

1. Install the application using Git:

git clone https://github.com/dotsystems-sk/dotapp.git ./

This command clones the repository into the current directory.

Alternatively, you can download the ZIP file:

Download dotApp

Unzip it into the directory where you need to place the application.

2. Setup and Usage:

After cloning or unzipping the application, configure it according to your needs (e.g., database configuration, environment settings, etc.).

3. Using Composer:

Although the application itself is not installable via Composer, once installed, you can use Composer within the application directory to add additional dependencies required for your project. Simply run:

composer require

Directory Structure

dotapp
├───app
│   ├───custom.classes
│   │   └───phpmailer
│   ├───modules
│   ├───parts
│   │   ├───controllers
│   │   ├───models
│   │   └───views
│   ├───runtime
│   │   ├───cache
│   │   ├───generator
│   │   └───routercache
│   └───vendor
│       └───composer
└───assets

Post-Installation Setup

This section describes the minimal setup required after installing the DotApp framework. If you don’t need to work with databases or prefer your own solution over the built-in library, simply follow these steps:

1. Define the __ROOTDIR__ constant in index.php in the root directory (without a trailing slash)

define('__ROOTDIR__', "/var/www/html");

Ensure that __ROOTDIR__ matches the actual server location, otherwise the framework may not function correctly.


2. Set a secure encryption key in the file ./app/config.php

new \Dotsystems\App\DotApp(md5("YourSuperSecretKey"));

When setting encKey, use a unique and sufficiently long string (at least 32 characters are recommended).


3. (Optional) If you want to use the built-in driver for database operations, uncomment and fill out the sample section in the file ./App/config.php

Note: You can safely remove the if (!__MAINTENANCE__) condition.

if (!__MAINTENANCE__) {
    /* Database setup */
    $dotApp->db->driver("mysqli");
    // Example usage: $dotApp->db->add("name_to_call_the_database", "server 127.0.0.1", "username", "password", "database_name_e.g._db_w4778", "connection_encoding UTF8");
    // Define parameters for the first database
    $dotApp->db->add("sourceDB", "127.0.0.1", "username", "password", "ws_477854", "UTF8");
    // Define parameters for the second database
    $dotApp->db->add("targetDB", "127.0.0.1", "username2", "password2", "ar_994475", "UTF8");
    
    // Loading modules is mandatory for the framework to function properly. You can place it inside or outside the if block based on your needs.
    $dotApp->loadModules();
}
            

Running the Framework

Everything is now ready. The DotApp framework ensures a secure and fast operation of your application. Make sure all settings and custom code are added before calling the $dotApp->run(); function (or its alias $dotApp->davajhet();).


// Everything ready? Start the framework! - As seen in the index.php file
$dotApp->davajhet();
// or in English:
$dotApp->run();

/*
    Note: $dotApp->davajhet() is an alias for $dotApp->run().
    I come from eastern Slovakia, so I added a bit of our "davaj het!" (let's go!)
*/
            

Adding the First Route

Blank page and ERROR 404? If you followed the instructions and see a blank page with a 404 status code after starting, don’t worry. It’s logical. The router is empty, so it couldn’t find a route for the / address in your browser and correctly displayed a 404. Your framework has no modules or routes yet, so it’s doing what it’s supposed to. Let’s add the first route. Before the $dotApp->run(); / $dotApp->davajhet(); code, add the first route.


// Adding the first route for the homepage
$dotApp->router->get("/", function () {
    return "Hello World!";
});

// Starting the framework
$dotApp->run();
        

After running, we see a page with the content:

Hello World!

DotApp Router

The Router is a key component of the DotApp Framework, managing the routing of HTTP requests within the application. It allows you to define how requests (e.g., GET, POST) are mapped to specific callback functions, controllers, or middleware. The Router is designed to handle both static and dynamic routes, support hooks (before and after), and provide flexibility in building web applications.

1.1 What is the Router?

The Router in the DotApp Framework is a class Dotsystems\App\Parts\Router that processes incoming HTTP requests and directs them to the appropriate handlers. It works in conjunction with the Request object, which contains information about the request (path, method, variables). Its primary role is to simplify route definition and ensure the correct code is executed for a given URL and HTTP method.

The Router is integrated directly into the framework's core, so you don’t need to install or configure it separately – simply use it via $dotApp->router.

1.2 Key Features

The Router offers a wide range of features that make application development easier:

  • HTTP Method Support: Define routes for GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD, TRACE, and the universal ANY method.
  • Dynamic Routes: Use variables (e.g., {id}) and regular expressions to capture parts of the URL.
  • Middleware: Support for before and after hooks to execute logic before and after the main handler.
  • Request Object: Passes request information to callbacks and controllers.
  • Flexibility: Ability to use anonymous functions, controllers, or middleware via strings (e.g., "module:controller@function").
  • Chaining: Method chaining for cleaner code.

1.3 Basic Routing Principles

The Router compares the current URL (obtained from request->getPath()) and HTTP method (from request->getMethod()) with defined routes. If a match is found:

  1. Any before hooks are executed.
  2. The main logic (callback, controller, or middleware) is performed.
  3. Any after hooks are executed.

Routes can be:

  • Static: Exact URL match (e.g., /home).
  • Dynamic: Contain variables or wildcards (e.g., /user/{id}).
  • First Match Wins: The first matching route is used; subsequent matches are ignored.

The Router resolves requests using the resolve() method, which is typically called automatically within the DotApp lifecycle.

Example of a Basic Route:

$dotApp->router->get('/home', function ($request) {
    return "Welcome to the homepage!";
});
        

When accessing the URL http://example.com/home, the text "Welcome to the homepage!" is displayed.

2. Getting Started

This chapter guides you through the basics of working with the Router in the DotApp Framework – from initialization to defining your first route.

2.1 Router Initialization

The Router is automatically initialized as part of the $dotApp instance when the application is created in the DotApp Framework. There’s no need to manually create or configure it – simply access it via $dotApp->router. During initialization, default values are set, such as the controller directory (__ROOTDIR__/App/Parts/Controllers/) and empty arrays for routes and hooks.

Technical Details:

  • The Router is an instance of the Dotsystems\App\Parts\Router class.
  • During construction, it receives $dotAppObj (an instance of the main DotApp class), giving it access to the Request object and other framework services.

2.2 Accessing the Router in DotApp

You access the Router through the global $dotApp instance, which is available anywhere in the application after its bootstrap. The Request object is automatically available via $dotApp->router->request and carries information about the current request (path, method, variables).

Example of Access:

// Check the current path
echo $dotApp->router->request->getPath(); // E.g., "/home"

// Check the HTTP method
echo $dotApp->router->request->getMethod(); // E.g., "get"
        

2.3 Defining Your First Route

The simplest way to start with the Router is to define a basic route using one of the HTTP methods (e.g., get()). A route can be linked to an anonymous function (callback), a controller, or middleware.

Example of a First Route with a Callback:

$dotApp->router->get('/home', function ($request) {
    return "Welcome to the homepage!";
});
        

Explanation:

  • /home: Static URL path.
  • function($request): Callback that accepts the Request object and returns a response.
  • When calling http://example.com/home, the text "Welcome to the homepage!" is displayed.
Example with a Controller:

Suppose you have a controller HomeController in __ROOTDIR__/App/Parts/Controllers/HomeController.php with a function index:


// HomeController.php
namespace Dotsystems\App\Parts\Controllers;

use Dotsystems\App\DotApp;

class HomeController {
    public static function index($request) {
        return "This is the homepage from the controller!";
    }
}

// Route definition
$dotApp->router->get('/home', 'HomeController@index');
        

Explanation:

  • 'HomeController@index': Reference to the static index method in the HomeController class.
  • The Router automatically loads and calls this method with the Request object.
Running the Routing:

The Router processes requests after calling $dotApp->router->resolve(), which is typically in the main application script (e.g., index.php). If the framework is properly set up, this call may be automatic.


// index.php
require_once 'vendor/autoload.php';
$dotApp = new Dotsystems\App\DotApp();
$dotApp->router->get('/home', function ($request) {
    return "Welcome!";
});
$dotApp->router->resolve();
        

3. Defining Routes

This chapter explains how to define routes in the Router of the DotApp Framework. The Router supports various definition methods – from basic HTTP methods to dynamic routes with variables and working with controllers.

3.1 Basic HTTP Methods (GET, POST, etc.)

The Router provides methods for all standard HTTP methods: get(), post(), put(), delete(), patch(), options(), head(), and trace(). Each method defines a route for a specific HTTP request.

Example of a GET Route:

$dotApp->router->get('/about', function ($request) {
    return "This is the About Us page!";
});
        
Example of a POST Route:

$dotApp->router->post('/submit', function ($request) {
    return "The form has been submitted!";
});
        

Note: Each method takes the URL path as the first parameter and a callback (or controller reference) as the second parameter. The callback always receives the $request object.

3.2 The match() Method for Multiple Methods and URLs

The match() method allows defining a route for multiple HTTP methods at once or for an array of URLs. It’s a more flexible approach compared to standalone methods like get() or post().

Example with Multiple Methods:

$dotApp->router->match(['get', 'post'], '/contact', function ($request) {
    return "This is the contact page!";
});
        

This route works for both GET and POST requests to /contact.

Example with Multiple URLs:

$dotApp->router->match(['get'], ['/home', '/index'], function ($request) {
    return "Welcome to the homepage!";
});
        

The route captures requests to both /home and /index.

3.3 Static vs. Dynamic Routes

The Router distinguishes between static and dynamic routes:

  • Static Routes: Exact URL match (e.g., /home).
  • Dynamic Routes: Contain variables or wildcards (e.g., /user/{id}).
Example of a Static Route:

$dotApp->router->get('/profile', function ($request) {
    return "This is a static profile!";
});
        
Example of a Dynamic Route:

$dotApp->router->get('/user/{id}', function ($request) {
    return "User profile with ID: " . $request->matchData()['id'];
});
        

For the URL /user/123, it displays "User profile with ID: 123".

3.4 Using Variables in Routes

Dynamic routes can include variables marked with curly braces (e.g., {id}). These variables are automatically extracted and available via $request->matchData().

Basic Usage:

$dotApp->router->get('/article/{slug}', function ($request) {
    return "Article: " . $request->matchData()['slug'];
});
        

For /article/how-to-cook, it displays "Article: how-to-cook".

Typed Variables:

The Router also supports variable typing:

  • {param:s}: String (no slashes).
  • {param:i}: Integer.
  • {param:l}: Letters only.
  • {param?}: Optional parameter.
  • {param*}: Wildcard (captures everything).

$dotApp->router->get('/user/{id:i}', function ($request) {
    return "User ID: " . $request->matchData()['id'];
});
        

Works only for numbers, e.g., /user/123, but not /user/abc.

3.5 Working with Controllers and Middleware

In addition to anonymous functions, you can map routes to controllers or middleware using a string in the format "module:controller@function" or "module:\\middleware\\middleware1@function1".

Example with a Controller:

// app/parts/controllers/UserController.php
namespace Dotsystems\App\Parts\Controllers;

use Dotsystems\App\DotApp;

class UserController {
    public static function show($request) {
        return "Displaying the user!";
    }
}

// Route definition
$dotApp->router->get('/user', 'UserController@show');
        
Example with Middleware:

// app/parts/middleware/AuthMiddleware.php
namespace Dotsystems\App\Parts\Middleware;

use Dotsystems\App\DotApp;

class AuthMiddleware {
    public static function check($request) {
        return "Authentication check!";
    }
}

// Route definition
$dotApp->router->get('/secure', 'parts:\\Middleware\\AuthMiddleware@check');
        

Note: Functions must be defined as public static and accept $request as a parameter.

4. Working with the Request Object

The Request object is an integral part of the Router in the DotApp Framework. It carries information about the current HTTP request and is automatically passed to callbacks, controllers, and middleware. This chapter explains how it works and how to use it effectively.

4.1 What is Request?

The Request is an instance of the Dotsystems\App\Parts\Request class, serving as an interface for working with request data. It contains information about the path, HTTP method, variables from dynamic routes, and other request attributes. It is automatically created during the Router initialization and is accessible via $dotApp->router->request.

Key Features:

  • Retrieving the current URL path and method.
  • Accessing variables from dynamic routes via matchData().
  • Passing data to callbacks and hooks.

4.2 Accessing Data from Request

The Request object provides methods to retrieve basic request information:

  • getPath(): Returns the current URL path (e.g., /home).
  • getMethod(): Returns the HTTP method (e.g., get, post).
  • matchData(): Returns an array of variables extracted from a dynamic route.
  • hookData(): Returns data assigned to hooks (used with standalone before/after).
Example of Access:

$dotApp->router->get('/user/{id}', function ($request) {
    $path = $request->getPath();        // "/user/123"
    $method = $request->getMethod();    // "get"
    $id = $request->matchData()['id'];  // "123"
    return "Path: $path, Method: $method, ID: $id";
});
        

For a request to /user/123, it displays: "Path: /user/123, Method: get, ID: 123".

4.3 Using Request in Callbacks

The Request object is automatically passed as a parameter to all callbacks, controllers, and middleware defined in routes. It allows you to work with request data directly within the route’s logic.

Example with an Anonymous Function:

$dotApp->router->get('/profile/{name}', function ($request) {
    $name = $request->matchData()['name'];
    return "Hello, $name!";
});
        

For /profile/Jano, it displays: "Hello, Jano!".

Example with a Controller:

// app/parts/controllers/ProfileController.php
namespace Dotsystems\App\Parts\Controllers;

use Dotsystems\App\DotApp;

class ProfileController {
    public static function show($request) {
        $name = $request->matchData()['name'];
        return "Profile for: $name";
    }
}

// Route definition
$dotApp->router->get('/profile/{name}', 'ProfileController@show');
        

The result is the same as with the anonymous function.

Example with Middleware:

// app/parts/middleware/CheckMiddleware.php
namespace Dotsystems\App\Parts\Middleware;

use Dotsystems\App\DotApp;

class CheckMiddleware {
    public static function verify($request) {
        $path = $request->getPath();
        return "Verified path: $path";
    }
}

// Route definition
$dotApp->router->get('/check', 'parts:\\Middleware\\CheckMiddleware@verify');
        

For /check, it displays: "Verified path: /check".

Note: matchData() returns an empty array if the route contains no dynamic variables. Verify the existence of a key before use, e.g., isset($request->matchData()['id']), to avoid errors.

5. Middleware (Before and After Hooks)

Middleware in the Router of the DotApp Framework allows you to execute additional logic before or after the main route handler. These "hooks" are defined using the before() and after() methods and are ideal for tasks such as authentication, logging, or response modification.

5.1 What Are Hooks?

Hooks are functions that run automatically at specific stages of route processing:

  • before: Executes before the main route logic (e.g., callback or controller).
  • after: Executes after the main logic, with access to the route’s result.

Hooks accept the $request object as a parameter and can be defined globally, for a specific route, or for a method with a route.

5.2 Defining before()

The before() method is used to add logic that executes before the main handler. It can be applied in three ways:

  • Globally: For all routes.
  • For a Specific Route: Only for a given path.
  • For a Method and Route: Specifically for an HTTP method and path.
Global Before:

$dotApp->router->before(function ($request) {
    return "Before every route!";
});
$dotApp->router->get('/test', function ($request) {
    return "Test page";
});
        

The hook runs for all routes, e.g., for /test, "Before every route!" executes first.

Before for a Specific Route:

$dotApp->router->get('/secure', function ($request) {
    return "Secure page";
})->before(function ($request) {
    return "Verifying access...";
});
        

The hook runs only for /secure.

Before with a Method and Route:

$dotApp->router->before('get', '/login', function ($request) {
    return "Checking login for GET";
});
$dotApp->router->get('/login', function ($request) {
    return "Login page";
});
        

5.3 Defining after()

The after() method runs after the main handler and has the same definition options as before(). It’s useful for modifying results or logging.

Global After:

$dotApp->router->after(function ($request) {
    return "After every route!";
});
$dotApp->router->get('/test', function ($request) {
    return "Test page";
});
        

The hook runs after every route, e.g., for /test, "Test page" executes first, followed by "After every route!".

After for a Specific Route:

$dotApp->router->get('/profile', function ($request) {
    return "Profile page";
})->after(function ($request) {
    return "Profile has been displayed";
});
        
After with a Method and Route:

$dotApp->router->after('post', '/submit', function ($request) {
    return "Form has been processed";
});
$dotApp->router->post('/submit', function ($request) {
    return "Submission successful";
});
        

5.4 Using with Multiple Routes

You can assign hooks to multiple routes at once using an array of paths with the match() method or by calling before()/after() separately.

Example with Match:

$dotApp->router->match(['get'], ['/home', '/index'], function ($request) {
    return "Homepage";
})->before(function ($request) {
    return "Before the homepage";
})->after(function ($request) {
    return "After the homepage";
});
    

The hooks apply to both paths: /home and /index.

Example with an Array of Paths:

$dotApp->router->before('get', ['/page1', '/page2'], function ($request) {
    return "Before the pages";
});
$dotApp->router->get('/page1', function ($request) {
    return "Page 1";
});
$dotApp->router->get('/page2', function ($request) {
    return "Page 2";
});
    

Note: The output from hooks is appended to the route’s response. To modify the response, work directly with $request->response->body in the hook (more in advanced features).

6. Error and Exception Handling

The Router in the DotApp Framework allows developers to manage errors and exceptions that occur during request processing. This chapter explains how to handle standard errors like 404 and implement custom error-handling logic using callbacks and hooks.

6.1 Handling 404 Errors

If the Router finds no match for a request (neither a static nor dynamic route), it automatically sets the HTTP code to 404. The default behavior shows no output, so it’s up to the developer to define custom logic to catch and display the error.

Example with a Global After Hook:

$dotApp->router->after("*", function ($request) {
    if (http_response_code() === 404) {
        return "Page not found: " . $request->getPath();
    }
});

$dotApp->router->get('/home', function ($request) {
    return "Homepage";
});
    

For a request to /about (a non-existent route), it displays: "Page not found: /about". The hook with "*" runs for all routes and checks the status code.

Example with Script Termination:

$dotApp->router->after("*", function ($request) {
    if (http_response_code() === 404) {
        echo "404 - Page not found!";
        exit;
    }
});

$dotApp->router->get('/home', function ($request) {
    return "Homepage";
});
    

For /about, it displays "404 - Page not found!" and the script terminates.

6.2 Custom Error Handling

Developers can implement custom error-handling logic directly in callbacks or middleware using conditions and HTTP codes.

Example with a Condition in a Callback:

$dotApp->router->get('/user/{id:i}', function ($request) {
    $id = $request->matchData()['id'];
    if ($id > 100) {
        http_response_code(403);
        return "Access forbidden for IDs greater than 100!";
    }
    return "User profile: $id";
});
    

For /user/150, it displays "Access forbidden for IDs greater than 100!" with code 403.

Example with Middleware:

$dotApp->router->get('/user/{id:i}', function ($request) {
    $id = $request->matchData()['id'];
    return "User profile: $id";
})->before(function ($request) {
    $id = $request->matchData()['id'];
    if (!isset($id)) {
        http_response_code(400);
        return "ID is missing!";
    }
});
    

For /user/, it displays "ID is missing!" with code 400.

Note: Using http_response_code() in callbacks or hooks allows setting custom error states. It’s up to the developer whether to terminate the script with exit or return an error message.

7. Advanced Features

The Router in the DotApp Framework offers advanced features that extend its capabilities. This chapter covers method chaining, dynamic URL matching, a detailed explanation of creating dynamic addresses, and defining API endpoints.

7.1 Method Chaining

The Router supports method chaining, allowing you to define routes, hooks, and other settings in a single command. This improves code readability and organization.

Example of Chaining:

$dotApp->router->get('/profile/{id}', function ($request) {
    $id = $request->matchData()['id'];
    return "Profile ID: $id";
})->before(function ($request) {
    return "Checking before displaying the profile";
})->after(function ($request) {
    return "Profile displayed";
});
    

For /profile/123, before, the main logic, and after execute sequentially.

7.2 Dynamic Route Matching (matchUrl())

The matchUrl() method is used for manually matching a URL against a routing pattern. It returns an array of extracted variables if the pattern matches, or false if not. It’s useful for custom validations or route testing.

Example of Use:

$dotApp->router->get('/test', function ($request) {
    $pattern = '/user/{id:i}';
    $url = '/user/123';
    $match = $dotApp->router->matchUrl($pattern, $url);
    if ($match !== false) {
        return "Match! ID: " . $match['id'];
    }
    return "No match";
});
    

For /test, it displays "Match! ID: 123".

7.3 Dynamic Addresses and Patterns

Dynamic addresses in the Router allow defining routes with variables and optional parts using special syntax. These patterns are recognized consistently across methods (e.g., get(), post(), match()), and variables are available via $request->matchData(). Below is a detailed explanation with an example and a list of the most common patterns.

Example of a Dynamic Address:

$dotApp->router->get('/documentation/intro(?:/{language})?', function ($request) {
    $language = $request->matchData()['language'] ?? 'default';
    return "Introductory documentation, language: $language";
});
    

Explanation:

  • /documentation/intro(?:/{language})?: Defines a route where {language} is an optional part (marked with ?: and ?).
  • /documentation/intro: Valid (language is "default").
  • /documentation/intro/eng: Valid (language is "eng").
  • /documentation/intro/: Invalid (the Router expects a value after the slash if present).

The language variable is extracted into $request->matchData() if provided, otherwise it’s null.

Most Commonly Used Patterns:

Here’s a list of 10 common dynamic address patterns used in web applications, with examples and explanations:

  1. /{resource}/{id:i} - Basic CRUD Route
    
    $dotApp->router->get('/users/{id:i}', function ($request) {
        return "User ID: " . $request->matchData()['id'];
    });
                

    Valid: /users/123, Invalid: /users/abc

  2. /{category}/{slug:s} - Category and Article Slug
    
    $dotApp->router->get('/blog/{category}/{slug:s}', function ($request) {
        return "Category: " . $request->matchData()['category'] . ", Slug: " . $request->matchData()['slug'];
    });
                

    Valid: /blog/tech/how-to-code

  3. /api/v{version}/{endpoint} - Versioned API
    
    $dotApp->router->get('/api/v{version}/{endpoint}', function ($request) {
        return "API v" . $request->matchData()['version'] . ": " . $request->matchData()['endpoint'];
    });
                

    Valid: /api/v1/users

  4. /{page}(?:/{subpage})? - Optional Subpage
    
    $dotApp->router->get('/docs/{page}(?:/{subpage})?', function ($request) {
        $subpage = $request->matchData()['subpage'] ?? 'main';
        return "Page: " . $request->matchData()['page'] . ", Subpage: $subpage";
    });
                

    Valid: /docs/intro, /docs/intro/setup

  5. /{type}/{id:i}/{action} - Action on a Resource
    
    $dotApp->router->get('/posts/{id:i}/{action}', function ($request) {
        return "ID: " . $request->matchData()['id'] . ", Action: " . $request->matchData()['action'];
    });
                

    Valid: /posts/5/edit

  6. /{resource}/{filter:s}? - Optional Filter
    
    $dotApp->router->get('/products/{filter:s}?', function ($request) {
        $filter = $request->matchData()['filter'] ?? 'all';
        return "Products, filter: $filter";
    });
                

    Valid: /products, /products/new

  7. /{path*} - Wildcard for Entire Path
    
    $dotApp->router->get('/files/{path*}', function ($request) {
        return "File path: " . $request->matchData()['path'];
    });
                

    Valid: /files/images/photo.jpg

  8. /{lang:l}/{section} - Language and Section
    
    $dotApp->router->get('/{lang:l}/{section}', function ($request) {
        return "Language: " . $request->matchData()['lang'] . ", Section: " . $request->matchData()['section'];
    });
                

    Valid: /en/news, Invalid: /123/news

  9. /search(?:/{query})? - Optional Search Query
    
    $dotApp->router->get('/search(?:/{query})?', function ($request) {
        $query = $request->matchData()['query'] ?? 'empty';
        return "Search: $query";
    });
                

    Valid: /search, /search/php

  10. /{resource}/{id:i}(?:/{extra})? - Resource with an Optional Parameter
    
    $dotApp->router->get('/users/{id:i}(?:/{extra})?', function ($request) {
        $extra = $request->matchData()['extra'] ?? 'none';
        return "ID: " . $request->matchData()['id'] . ", Extra: $extra";
    });
                

    Valid: /users/10, /users/10/details

Note: These patterns are flexible and combinable. Use {?:} for optional parts and types (:i, :s, :l) for precise constraints.

7.4 Defining API Endpoints with apiPoint

The apiPoint method in the Router provides a convenient way to define API endpoints with support for versioning, modules, and dynamic parameters. It offers flexibility in defining custom paths and methods, and when combined with the built-in abstract Controller class and its apiDispatch (main logic) and api (shorter alias) methods, it enables automatic dispatching of requests to specific controller methods with dependency injection (DI) support.

Definition of the apiPoint Method:

public function apiPoint($version, $module, $controller, $custom = null) {
    $apiRoutes = array();
    
    if ($custom !== null) {
        if (is_array($custom)) {
            foreach ($custom as $value) {
                $apiRoutes[] = "/api/v".$version."/".$module."/".$value;
            }
        } else {
            $apiRoutes[] = "/api/v".$version."/".$module."/".$custom;
        }
    } else {
        $apiRoutes[] = "/api/v".$version."/".$module."/{resource}(?:/{id})?";
    }

    $this->any($apiRoutes, $controller);
}
    

Parameters:

  • $version: API version (e.g., "1" for v1).
  • $module: Module name (e.g., "dotcmsfe").
  • $controller: Callback or string in the format "Controller@method" (e.g., "PostsController@apiDispatch", "PostsController@api", or a custom method).
  • $custom (optional): Specific path (string) or array of paths. Supports regular expressions (e.g., (?:/{id})?).

If $custom is not provided, the default dynamic path /api/v{version}/{module}/{resource}(?:/{id})? is used. If specified, only the paths from $custom are applied. First route wins! Static paths must be listed before dynamic ones to avoid being overridden by dynamic logic.

Built-in Controller and apiDispatch/api Methods:

The framework provides an abstract class Dotsystems\App\Parts\Controller with the apiDispatch method, which automatically dispatches requests to specific methods in the format (e.g., postUsers, getPosts) based on the HTTP method and the value of the dynamic resource parameter. Simply point apiPoint to Controller@apiDispatch (or Controller@api as a shorter alias), and automatic dispatching works if the path includes {resource}. Unlike other frameworks (e.g., Laravel, Django), you don’t need to define routes for each endpoint – apiDispatch handles it for you with full DI support via self::$di->callStatic.


// Excerpt from Dotsystems\App\Parts\Controller
public static function apiDispatch($request) {
    $method = strtolower($request->getMethod());
    $resource = $request->matchData()['resource'] ?? null;
    $id = $request->matchData()['id'] ?? null;
    
    // Construct the method name: <httpMethod><Resource>
    if ($resource) {
        $targetMethod = $method . ucfirst($resource);
        if (method_exists(static::class, $targetMethod)) {
            return self::$di->callStatic($targetMethod, [$request]);
        }
    }

    // Attempt to call error404 if it exists
    if (method_exists(static::class, 'error404')) {
        return self::$di->callStatic('error404', [$request]);
    }

    // Default response if error404 doesn’t exist
    http_response_code(404);
    return "API: Resource '$resource' not found or method '$method' not supported in " . static::class;
}

public static function api($request) {
    // Shorter alias
    self::apiDispatch($request);
}
    
Advantages of Using Controller@apiDispatch with DI:

The apiDispatch method leverages the DI container to call specific methods, providing automatic dependency injection. This means controller methods can accept additional parameters (e.g., services like \SomeService) that are automatically injected from the DI container without manual instantiation. This approach simplifies code, increases flexibility, and sets DotApp apart from other frameworks by eliminating the need for explicit routing for every endpoint when using automation.

Customizing Errors:

If the target method (e.g., postUsers) doesn’t exist, apiDispatch first checks if the controller defines an error404 method. If so, it calls it, allowing the user to define custom logic for 404 errors (e.g., JSON response, logging). If error404 isn’t present, it returns a default error message with HTTP code 404.

Using with Automatic Dispatching:

Automatic dispatching via apiDispatch (or api) works only if the path includes the dynamic {resource} parameter in the correct position (e.g., /api/v1/dotcmsfe/{resource}). If $custom doesn’t maintain this format, the automation won’t work, and a custom method must be used.

Example without $custom (Automatic Dispatching):

$dotApp->router->apiPoint("1", "dotcmsfe", "PostsController@apiDispatch");
    

Resulting Paths:

  • POST /api/v1/dotcmsfe/users - Triggers postUsers if it exists.
  • GET /api/v1/dotcmsfe/posts - Triggers getPosts.
  • GET /api/v1/dotcmsfe/status - Triggers error404 if it exists, otherwise 404 with a default message.
  • /api/v1/dotcmsfe/posts/ - Not captured.
Example with $custom and Automatic Dispatching:

$dotApp->router->apiPoint("1", "dotcmsfe", "PostsController@apiDispatch", ["{resource}(?:/{id})?/details"]);
    

Resulting Paths:

  • POST /api/v1/dotcmsfe/users/details - Triggers postUsers.
  • GET /api/v1/dotcmsfe/posts/details - Triggers getPosts.
  • GET /api/v1/dotcmsfe/posts/abc123/details - Triggers getPosts.
  • PUT /api/v1/dotcmsfe/status/details - Triggers error404 if it exists, otherwise 404 with a default message.
  • /api/v1/dotcmsfe/users/ - Not captured.
Example with Custom Routes and a Custom Method:

$dotApp->router->apiPoint("1", "dotcmsfe", "PostsController@customMethod", ["users/details", "posts/summary"]);
    

Resulting Paths: Automatic dispatching doesn’t work here because {resource} is missing. The logic depends on the implementation of customMethod.

  • GET /api/v1/dotcmsfe/users/details - Triggers customMethod.
  • POST /api/v1/dotcmsfe/posts/summary - Triggers customMethod.
Example Controller with DI and Custom Error:

namespace Dotsystems\App\Modules\Dotcmsfe\Controllers;

class PostsController extends \Dotsystems\App\Parts\Controller {
    public static function postUsers($request, \SomeService $service) {
        return "Creating users: " . $service->process($request->getPath());
    }

    public static function getPosts($request) {
        $id = $request->matchData()['id'] ?? null;
        return "List of posts" . ($id ? " with ID: $id" : "");
    }

    public static function error404($request) {
        http_response_code(404);
        return json_encode([
            'error' => 'Not Found',
            'message' => "Resource '{$request->matchData()['resource']}' not found or method '{$request->getMethod()}' not supported",
            'path' => $request->getPath()
        ]);
    }

    public static function customMethod($request) {
        return "Custom method for path: " . $request->getPath();
    }
}
    

Note: The built-in Controller simplifies API handling with apiDispatch (or api) when the path includes {resource}. For custom routes without {resource}, you can use custom methods, but automatic dispatching won’t work. The order of paths in $custom is critical – static paths must precede dynamic ones.

8. Practical Examples

This chapter provides practical examples of using the Router in the DotApp Framework. It demonstrates how to combine basic and advanced features to address common scenarios in web applications.

8.1 Simple GET Route

The most basic example of defining a static route with a simple response.

Example:

$dotApp->router->get('/welcome', function ($request) {
    return "Welcome to the application!";
});
    

For a request to /welcome, it displays: "Welcome to the application!".

Use Case: Ideal for static pages like homepages or "About Us".

8.2 Dynamic Route with Variables

An example of a dynamic route with variable extraction to display user data.

Example:

$dotApp->router->get('/user/{id:i}/{name}', function ($request) {
    $id = $request->matchData()['id'];
    $name = $request->matchData()['name'];
    return "User ID: $id, Name: $name";
});
    

For /user/123/Jano, it displays: "User ID: 123, Name: Jano".

Use Case: Suitable for profiles, product details, or other resources with identifiers.

8.3 Using Middleware

An example combining a route with before and after hooks for verification and logging.

Example:

$dotApp->router->get('/dashboard', function ($request) {
    return "Welcome to the dashboard!";
})->before(function ($request) {
    $user = "guest"; // Simulated verification
    if ($user === "guest") {
        http_response_code(403);
        return "Access denied!";
    }
})->after(function ($request) {
    return "Dashboard displayed at " . date('H:i:s');
});
    

For /dashboard, it displays "Access denied!" with code 403 (since the simulated verification fails). If verification succeeded, it would show "Welcome to the dashboard!" followed by the display time.

Use Case: Authentication, access logging, or response modification.

8.4 Combining with Controllers

An example of integrating a route with a controller to separate logic from routing.

Example:

// app/parts/controllers/ArticleController.php
namespace Dotsystems\App\Parts\Controllers;

use Dotsystems\App\DotApp;

class ArticleController {
    public static function detail($request) {
        $slug = $request->matchData()['slug'];
        return "Article detail: $slug";
    }
}

// Route definition
$dotApp->router->get('/article/{slug:s}', 'ArticleController@detail');
    

For /article/how-to-code, it displays: "Article detail: how-to-code".

Use Case: Larger applications where code organization into controllers is needed.

9. Tips and Tricks

This chapter offers practical tips and tricks for effectively using the Router in the DotApp Framework. These will help you optimize your code, debug issues, and follow best practices.

9.1 Optimizing Routing

The Router in the DotApp Framework operates on a "first match wins" principle – the first matching route in the order of definition is used, and others are ignored, regardless of whether they are static or dynamic. The order of definition is therefore critical for optimization.

  • Define the most important routes first: Since the first match wins, place critical or frequently used routes at the top.
  • Use specific patterns: E.g., {id:i} instead of {id} to prevent unintended matches on incorrect routes.
  • Group similar routes: Use match() with an array of paths to reduce code duplication, but be mindful of order.
Example of Optimization:

$dotApp->router->get('/user/{id:i}', function ($request) { // First dynamic route
    return "Dynamic user ID: " . $request->matchData()['id'];
});
$dotApp->router->get('/user/123', function ($request) { // Second static route
    return "Static user 123";
});
    

For /user/123, the first route always wins ("Dynamic user ID: 123") because it was defined first, even though the second is static and more precise. To prioritize the static route, define it earlier.

Example with Reordered Priority:

$dotApp->router->get('/user/123', function ($request) { // First static route
    return "Static user 123";
});
$dotApp->router->get('/user/{id:i}', function ($request) { // Second dynamic route
    return "Dynamic user ID: " . $request->matchData()['id'];
});
    

Now, for /user/123, it displays "Static user 123" because it’s defined first.

9.2 Debugging Routes

When troubleshooting routing issues, use the tools available in the Router and PHP to identify which route is actually being triggered, especially with the "first match" rule.

  • Check the path: Use $request->getPath() to verify the URL the Router is processing.
  • Dump variables: Print $request->matchData() to see which values were extracted.
  • Test order: Add temporary outputs (e.g., echo) in callbacks to determine which route executed.
Example of Debugging:

$dotApp->router->get('/page/{id}', function ($request) {
    echo "Dynamic route triggered for ID: " . $request->matchData()['id'];
    return "Dynamic page " . $request->matchData()['id'];
});
$dotApp->router->get('/page/1', function ($request) {
    echo "Static route triggered for /page/1";
    return "Static page 1";
});
    

For /page/1, it displays "Dynamic route triggered for ID: 1" and "Dynamic page 1" because the dynamic route is defined first. Changing the order would prioritize the static route.

9.3 Best Practices for Route Structure

Following best practices helps maintain clarity and predictability in routing.

  • Logical order: Define routes from most specific to most general to leverage the "first match wins" rule.
  • Comments: Add comments above routes to clarify why they are in a specific order.
  • Separate logic: Use controllers for complex routes instead of inline callbacks.
Example of Best Practices:

// Most specific static route
$dotApp->router->get('/api/users/guest', function ($request) {
    return "Guest user";
});

// Specific dynamic route
$dotApp->router->get('/api/users/{id:i}', function ($request) {
    return "User ID: " . $request->matchData()['id'];
});

// General route last
$dotApp->router->get('/api/{resource}', function ($request) {
    return "Resource: " . $request->matchData()['resource'];
});
    

For /api/users/guest, the first route triggers; for /api/users/5, the second; and for /api/products, the third, thanks to logical ordering.

10. Conclusion

This chapter concludes the documentation for the Router in the DotApp Framework. It summarizes its benefits and offers a look at its future development and community.

10.1 Why Use the Router in DotApp?

The Router in the DotApp Framework is a simple yet powerful tool for managing routing in web applications. Its key advantages include:

  • Flexibility: Support for both static and dynamic routes with variables and optional parts.
  • Simplicity: Intuitive interface for defining routes via HTTP methods like get() and post().
  • Middleware: Ability to add before and after hooks for extended logic.
  • First Match Wins: Predictable behavior based on the order of route definition, giving developers full control.
  • Integration: Seamless collaboration with controllers and the Request object for request handling.

Whether you’re building a small application or a complex system, the Router provides the tools to map requests to logic quickly and efficiently.

Dependency Injection a Middleware v DotApp

Táto kapitola popisuje dependency injection (DI) a middleware v DotApp frameworku. Zameriava sa na flexibilné volania kontrolerov a middleware cez stringy, polia, callable objekty a anonymné funkcie, s automatickým vkladaním závislostí cez stringToCallable a vylepšenú metódu di.

1. Dependency Injection a Middleware v DotApp

Táto kapitola popisuje dependency injection (DI) a middleware v DotApp frameworku. Vysvetľuje DI kontajner, jeho registráciu závislostí cez bind a singleton, a ako sú tieto závislosti resolvované pomocou resolve. Tiež popisuje flexibilné volania kontrolerov a middleware cez stringy, polia, callable objekty a anonymné funkcie s automatickým DI.

1.1. Čo je Dependency Injection a Middleware?

Dependency Injection (DI) je technika, ktorá umožňuje automaticky vkladať závislosti (napr. DotApp) do metód a funkcií namiesto ich manuálneho vytvárania. V DotApp je DI spravované cez DI kontajner v triede DotApp, ktorý registruje závislosti a resolvuje ich pri volaniach.

Middleware sú funkcie alebo triedy, ktoré spracúvajú požiadavky pred alebo po hlavnej logike, využívajúc DI pre prístup k registrovaným závislostiam.

1.2. DI Kontajner v DotApp

DI kontajner v DotApp je jadrom správy závislostí a pozostáva z troch hlavných metód:

1.2.1. bind(string $key, callable $resolver)

Registruje závislosť, ktorá sa vytvorí nanovo pri každom volaní resolve. Používa sa pre nezdieľané (non-shared) inštancie.

Syntax: bind(string $key, callable $resolver): void

Príklad:


$DotApp->bind('logger', function () {
    return new Logger();
});
$logger = $DotApp->resolve('logger'); // Vytvorí novú inštanciu
$logger2 = $DotApp->resolve('logger'); // Vytvorí ďalšiu novú inštanciu
        

Poznámka: Každé volanie resolve vráti novú inštanciu, pretože shared je nastavené na false.

1.2.2. singleton(string $key, callable $resolver)

Registruje závislosť ako singleton – vytvorí sa iba raz a následne sa zdieľa pri všetkých volaniach resolve. Používa sa pre zdieľané inštancie, ako je napríklad samotný DotApp.

Syntax: singleton(string $key, callable $resolver): void

Príklad:


// V konštruktore DotApp
$this->singleton(DotApp::class, function () {
    return $this;
});
$dotApp1 = $DotApp->resolve(DotApp::class); // Vráti tú istú inštanciu
$dotApp2 = $DotApp->resolve(DotApp::class); // Vráti tú istú inštanciu
        

Poznámka: Singleton zaručuje, že existuje iba jedna inštancia danej závislosti, uložená v $this->instances.

1.2.3. resolve(string $key)

Resolvuje zaregistrovanú závislosť. Ak je to singleton, vráti zdieľanú inštanciu; ak je to bind, vytvorí novú.

Syntax: resolve(string $key)

Príklad:


$DotApp->singleton('db', function () {
    return new Database();
});
$db = $DotApp->resolve('db'); // Vráti singleton inštanciu
        

Výnimka: Ak kľúč nie je registrovaný, vyhodí Exception.

1.2.4. Ako funguje DI v praxi

DI kontajner ukladá závislosti v poli $this->bindings s informáciou, či sú zdieľané (shared). Pri resolúcii kontroluje, či už existuje inštancia (pre singletony) alebo volá resolver (pre bind). V konštruktore DotApp je napríklad DotApp::class zaregistrovaný ako singleton, aby bol prístupný všade:


$this->singleton(DotApp::class, function () { return $this; });
        

1.3. Možnosti volania kontrolerov a middleware

DotApp využíva DI kontajner na automatické vkladanie závislostí do callbackov:

1.3.1. String-based volania

Syntax: "modul:controller@funkcia".

Príklad:


$DotApp->router->get('/login', "auth:controllers\\UserController@login");
        

Výstup pri GET /login:


Prihlásenie bolo úspešné: GET
        
1.3.2. Pole ako callback

Syntax: ['modul', 'class', 'function'].

Príklad:


$DotApp->router->get('/login', ['auth', 'controllers\\UserController', 'login']);
        

Výstup pri GET /login:


Prihlásenie bolo úspešné: GET
        
1.3.3. Callable (Closure) s DI

Anonymné funkcie využívajú DI kontajner na resolúciu závislostí.

Príklad:


$DotApp->router->get('/status', function (DotApp $dotApp) {
    echo "Aktuálna metóda: " . $dotApp->router->request->getMethod();
});
        

Výstup pri GET /status:


Aktuálna metóda: GET
        
1.3.4. Dynamické volania cez di

Príklad:


class AuthService extends Dotsystems\App\Parts\Library {
    public function logAction(DotApp $dotApp) {
        echo "Akcia: " . $dotApp->router->request->getMethod();
    }
}
$service = new AuthService('auth', $DotApp);
$service->di->logAction();
        

Výstup:


Akcia: GET
        
1.3.5. Statické volania cez controller2::call

Príklad:


auth\controllers\UserController::call("login");
        

Výstup:


Prihlásenie bolo úspešné: GET
        

1.4. Práca s Middleware

Middleware využíva DI kontajner pre prístup k závislostiam:

1.4.1. String-based middleware

Príklad:


$DotApp->router->before(["/user/*"], "auth:controllers\\UserController@checkAuth");
$DotApp->router->get('/user/profile', "auth:controllers\\UserController@showProfile");
        

Výstup pri GET /user/profile:


Profil zobrazený: GET
        
1.4.2. Pole v middleware

Príklad:


$DotApp->middleware("authCheck", ['auth', 'controllers\\UserController', 'checkAuth']);
$DotApp->router->get('/secure', $DotApp->middleware("authCheck"));
        

Výstup pri GET /secure:


Overenie úspešné
        
1.4.3. Closure s DI v middleware

Príklad:


$DotApp->router->before(["/admin/*"], function (DotApp $dotApp, LogService $log) {
    echo "Metóda: " . $dotApp->router->request->getMethod() . ", Log: " . $log->getLogName() . "<br>";
});
$DotApp->router->get('/admin/dashboard', function () {
    echo "Admin panel";
});
        

Výstup pri GET /admin/dashboard:


Metóda: GET, Log: AdminLog<br>Admin panel
        
1.4.4. Manuálne volanie middleware

Príklad:


$DotApp->middleware("logAccess", function (DotApp $dotApp, ...$args) {
    auth\controllers\UserController::call("login", ...$args);
});
$DotApp->router->get('/access', $DotApp->middleware("logAccess"));
        

Výstup pri GET /access:


Prihlásenie bolo úspešné: GET
        

1.5. Praktické príklady

1. Registrácia a resolúcia singletonu:


$DotApp->singleton('cache', function () {
    return new CacheService();
});
$cache1 = $DotApp->resolve('cache');
$cache2 = $DotApp->resolve('cache');
echo ($cache1 === $cache2) ? "Rovnaká inštancia" : "Rôzne inštancie";
        

Výstup:


Rovnaká inštancia
        

2. Middleware s Closure a DI:


$DotApp->router->before(["/api/*"], function (DotApp $dotApp) {
    echo "Kontrola: " . $dotApp->router->request->getMethod() . "<br>";
});
$DotApp->router->get('/api/users', "auth:controllers\\UserController@showProfile");
        

Výstup pri GET /api/users:


Kontrola: GET
Profil zobrazený: GET
        

3. Kombinácia bind a Closure:


$DotApp->bind('logger', function () {
    return new Logger();
});
$DotApp->router->get('/log', function (DotApp $dotApp, Logger $logger) {
    echo "Log: " . $logger->getLogId() . ", Metóda: " . $dotApp->router->request->getMethod();
});
        

Výstup pri GET /log:


Log: 12345, Metóda: GET
        

1.6. Poznámky

  • Singleton vs. Bind: Používajte singleton pre zdieľané inštancie (napr. DotApp, databáza), bind pre nové inštancie pri každom volaní.
  • Closure DI: Funguje automaticky v routeri a middleware vďaka di.
  • Middleware parametre: Dynamické parametre z middlewareCall ešte nie sú implementované – odovzdávajte manuálne.
  • Best practice: Registrujte kľúčové služby ako singletony v inicializácii modulu.

DotBridge

DotBridge is a key component of the DotApp Framework, ensuring secure and efficient communication between server-side PHP and client-side JavaScript via AJAX requests. It allows you to define PHP functions callable from the front-end, manage inputs, and protect communication from unauthorized access or abuse. DotBridge is designed to provide flexibility, security, and easy integration of dynamic features into web applications.

1.1 What is DotBridge?

DotBridge in the DotApp Framework is a class Dotsystems\App\Parts\Bridge that serves as a bridge between back-end PHP logic and front-end JavaScript actions. It enables calling PHP functions from HTML elements (e.g., buttons, form inputs) through AJAX requests to the /dotapp/bridge path. Its primary role is to simplify client-server communication while ensuring it is verified, encrypted, and protected against attacks such as CSRF or request replay.

DotBridge is integrated directly into the framework’s core, so you don’t need to install or configure it separately – simply use it via $dotApp->bridge.

1.2 Key Features

DotBridge offers a wide range of features that simplify the development of secure and dynamic applications:

  • Secure Communication: Uses data encryption, key verification, and CRC checks to protect requests.
  • Calling PHP Functions: Define PHP functions callable from JavaScript with support for before and after callbacks.
  • Validation Filters: Real-time input validation (e.g., email, URL, password) with visual feedback on the client side.
  • Rate Limiting: Ability to set request limits per time (rateLimit(seconds,clicks)).
  • One-Time Keys: Support for oneTimeUse and regenerateId for enhanced security.
  • Flexibility: Supports various events (e.g., click, keyup) and dynamic inputs from HTML.
  • Chaining: Method chaining for cleaner code on both PHP and JavaScript sides.

1.3 Basic Operating Principles

DotBridge handles communication between the client and server in the following steps:

  1. Generates a unique session key and registers PHP functions on the server side via fn().
  2. In HTML, events (e.g., dotbridge:on(click)) and inputs (e.g., dotbridge:input) are defined and linked to PHP functions.
  3. When an event is triggered (e.g., a click), an AJAX request is sent to /dotapp/bridge with encrypted data.
  4. The server verifies the key, decrypts the data, checks request limits, and executes the requested PHP function.
  5. The result is returned as a JSON response, which JavaScript can further process.

Communication is safeguarded with data encryption, key verification, and limits to prevent abuse or unauthorized access.

Example of Basic Usage:

<button {{ dotbridge:on(click)="sayHello" }}>Click me</button>
        

$dotApp->bridge->fn("sayHello", function($data) {
    return ["status" => 1, "message" => "Hello from the server!"];
});
        

Upon clicking the button, the PHP function sayHello is called and returns a JSON response with the message "Hello from the server!".

2. Getting Started

This chapter guides you through the basics of working with DotBridge in the DotApp Framework – from initialization to defining your first function, adding a front-end event, and handling the response in JavaScript.

2.1 Initializing DotBridge

DotBridge is automatically initialized as part of the $dotApp instance when creating an application in the DotApp Framework. You access it via $dotApp->bridge. During initialization, a unique session key is generated (stored in _bridge.key in the session) and default settings such as request limits and built-in validation filters are applied.

Technical Details:

  • Bridge is an instance of the Dotsystems\App\Parts\Bridge class.
  • It receives a reference to $dotApp, giving it access to encryption methods, sessions (dsm), and the router.
  • It automatically registers the /dotapp/bridge path in the router for handling AJAX requests.

2.2 Defining a PHP Function

On the server side, you define a callable PHP function using the fn() method, which takes the function name and a callback. The callback receives a $data parameter containing data sent from the front-end (e.g., input values). The function should return an array with keys like status and status_txt for a consistent response.

Example:

$dotApp->bridge->fn("sendMessage", function($data) {
    $message = $data["message"] ?? "No message";
    return ["status" => 1, "status_txt" => "Message received: " . $message];
});
    

The sendMessage function is now ready to be called from the front-end and will return a JSON response with the received message.

2.3 Adding a Front-End Event

In HTML, use the attribute {{ dotbridge:on(event)="functionName(params)" }} to link an event (e.g., click, keyup) to a PHP function. You can define inputs with dotbridge-input="name" and add parameters like rateLimitM or oneTimeUse to control behavior.

Example:

<input type="text" {{ dotbridge:input="user.message" }}>
<button {{ dotbridge:on(click)="sendMessage(user.message)" rateLimitM="5" }}>Send</button>
    

When the button is clicked, the value from the user.message input is sent to the sendMessage function with a limit of 5 requests per minute.

2.4 Handling the Response in JavaScript

On the client side, you can use $dotapp().bridge() to define before and after callbacks to handle the state before sending the request and after receiving the response. These callbacks allow you to dynamically update the UI based on the server’s response.

Example:

$dotapp().bridge("sendMessage", "click")
    .before(function(data, element) {
        $(element).text("Sending...");
    })
    .after(function(data, element) {
        $(element).text("Done!");
        alert(data["status_txt"]);
    });
    

Before sending, the button text changes to "Sending...", and after receiving the response, it displays the message from the PHP function.

3. Advanced Usage

This chapter covers advanced features of DotBridge, such as validation filters, rate limiting, method chaining, and working with dynamic data. These tools enable the creation of more robust and secure applications with greater control over behavior.

3.1 Validation Filters

DotBridge provides built-in validation filters for real-time input validation on the client side. Filters like email, url, phone, or password apply regular expressions and visual feedback (CSS classes) based on input validity. They are used in HTML via the dotbridge-result="0" dotbridge-input="name" attribute.

Available Arguments:

  • filter: Name of the filter (e.g., email).
  • start_checking_length: Minimum number of characters to start validation.
  • class_ok: CSS class for valid input.
  • class_bad: CSS class for invalid input.
Example:

<input type="text" {{ dotbridge:input="user.email(email, 5, 'valid-email', 'invalid-email')" }}>
    

The user.email input starts validation after 5 characters. If the email is valid, the valid-email class is added; if invalid, invalid-email.

3.2 Rate Limiting and Security Mechanisms

DotBridge allows you to limit the number of requests using the rateLimit(seconds,clicks) parameters, protecting the application from abuse. Additional security features include oneTimeUse (single-use key) and regenerateId (key regeneration after each use).

Usage:

  • rateLimit(seconds,clicks): Maximum of clicks requests per seconds.
  • oneTimeUse: Key is valid for only one use.
  • regenerateId: Generates a new key after each call.
Example:

<button {{ dotbridge:on(click)="submitForm" rateLimit(60,10) rateLimit(3600,100) }}>Submit</button>
<button {{ dotbridge:on(click)="submitForm" oneTimeUse }}>Submit</button>
<button {{ dotbridge:on(click)="submitForm" regenerateId }}>Submit</button>
    

1. Example - The button allows a maximum of 10 clicks per minute, 100 per hour.

2. Example - The button allows only one click, and the listener is removed.

3. Example - The button regenerates its ID on each click.

3.3 Method Chaining

DotBridge supports method chaining on both the PHP and JavaScript sides, simplifying the definition of functions and callbacks. In PHP, use fn(), before(), and after(); in JavaScript, use $dotapp().bridge() with before() and after().

Example:

// PHP
$dotApp->bridge->fn("processData", function($data) {
    return ["status" => 1, "result" => $data["value"]];
})
->before(function($data) {
    $data["value"] = trim($data["value"] ?? "");
    return $data;
})
->after(function($result, $data) {
    $result["timestamp"] = time();
    return $result;
});
    

// JavaScript
$dotapp().bridge("processData", "click")
    .before(function(data, element) {
        $(element).addClass("loading");
    })
    .after(function(data, element) {
        $(element).removeClass("loading");
        console.log(data["result"]);
    });
    

Before processing, the input is cleaned (before), after processing a timestamp is added (after), and on the client side, a loading state is displayed.

3.4 Working with Dynamic Data

DotBridge allows sending and processing dynamic data from multiple inputs defined in HTML. Inputs are identified using dotbridge:input and sent in $_POST['data'], where the PHP function can process them.

Example:

<input type="text" {{ dotbridge:input="user.name" }}>
<input type="text" {{ dotbridge:input="user.email" }}>
<button {{ dotbridge:on(click)="saveUser(user.name, user.email)" }}>Save</button>
    

$dotApp->bridge->fn("saveUser", function($data) {
    $name = $data["user.name"] ?? "";
    $email = $data["user.email"] ?? "";
    return ["status" => 1, "status_txt" => "User $name ($email) saved"];
});
    

Upon clicking, the values from the user.name and user.email inputs are sent to the saveUser function and returned in the response.

4. Best Practices and Tips

This chapter provides recommendations and tips for effectively and securely using DotBridge. It covers security optimization, debugging issues, and integration with other parts of the DotApp Framework.

4.1 Optimizing Security

Security is a critical aspect when using DotBridge. The following recommendations will help minimize risks and ensure robust communication:

  • Use rate limiting: Always set rateLimitM and rateLimitH for actions sensitive to repeated calls to prevent abuse (e.g., brute force attacks).
  • Enable one-time keys: For critical operations (e.g., form submission), use oneTimeUse or regenerateId to prevent reuse of the same key.
  • Validate inputs: Combine validation filters with additional server-side checks (e.g., filter_var()) to ensure consistent validation.
  • Monitor sessions: Regularly check and clean old keys in _bridge.objects to avoid memory overflow.
  • Use encryption: Leverage the built-in DotApp encryption (encrypt(), decrypt()) for sensitive data in communication.
Example:

<button {{ dotbridge:on(click)="secureAction" rateLimitM="2" oneTimeUse }}>Execute</button>
    

$dotApp->bridge->fn("secureAction", function($data) {
    return ["status" => 1, "status_txt" => "Action executed securely"];
});
    

This example limits the action to 2 calls per minute and allows only one use of the key.

4.2 Debugging and Troubleshooting

While working with DotBridge, you may encounter errors. Here are common issues and their solutions:

  • CRC check failed (error_code 1): Verify that the data sent from the front-end hasn’t been modified. Check the integrity of the JavaScript code and network requests.
  • Bridge key does not match (error_code 2): Ensure the session key (_bridge.key) matches what the client sends. This could be due to an expired session.
  • Function not found (error_code 3): Confirm that the function is correctly registered with fn() and that the name matches the HTML call.
  • Rate limit exceeded (error_code 4): You’ve exceeded the set limit. Increase rateLimitM/rateLimitH or inform the user to wait.

Tip: Enable debugging in DotApp and monitor responses from /dotapp/bridge in the browser’s developer tools (Network tab) for detailed information.

4.3 Integration with Other Parts of DotApp

DotBridge is designed to work seamlessly with other DotApp components, such as Router, Request, and the database. Integration allows you to build complex applications with minimal effort.

Integration Examples:

  • With Router: DotBridge automatically uses $dotApp->router to register the /dotapp/bridge path.
  • With Request: Data from $_POST is made available in the callback as $data.
  • With Database: You can directly insert front-end data into the database via $dotApp->db.
Example with Database:

<input type="text" {{ dotbridge:input="user.email" }}>
<button {{ dotbridge:on(click)="saveEmail(user.email)" }}>Save</button>
    

$dotApp->bridge->fn("saveEmail", function($data) use ($dotApp) {
    $email = $data["user.email"] ?? "";
    if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $dotApp->db->insertInto("users")
            ->values("email", $email)
            ->execute();
        return ["status" => 1, "status_txt" => "Email saved"];
    }
    return ["status" => 0, "status_txt" => "Invalid email"];
});
    

Upon clicking, the email is validated and saved to the database, returning a success or error response.

Databaser

1. Úvod

1.1. Čo je Databaser?

Databaser je robustná a flexibilná knižnica na správu databázových interakcií, ktorá je integrovanou súčasťou DotApp Frameworku. Navrhnutá je tak, aby poskytovala jednoduchý, bezpečný a efektívny spôsob práce s databázami, či už ide o základné operácie, alebo pokročilé dotazy. Databaser eliminuje potrebu písania surových SQL dotazov (aj keď túto možnosť stále ponúka) a prináša moderný prístup k manipulácii s dátami prostredníctvom intuitívneho QueryBuildera a voliteľného ORM (Object-Relational Mapping) systému. Jeho hlavným cieľom je uľahčiť vývojárom prácu s databázami a zároveň zachovať vysokú mieru prispôsobiteľnosti a výkonu.

Databaser je priamo zabudovaný do jadra DotApp Frameworku, takže nie je potrebné ho samostatne inštalovať ani konfigurovať mimo frameworku. Po nastavení databázových pripojení v rámci DotAppu je pripravený na okamžité použitie.

1.2. Kľúčové vlastnosti

Databaser ponúka širokú škálu funkcií, ktoré ho robia výnimočným nástrojom pre prácu s databázami:

  • Jednoduché vytváranie a vykonávanie SQL dotazov: Podpora prepared statements zaisťuje bezpečnosť a jednoduchosť pri práci s dátami.
  • Správa viacerých databázových pripojení: Možnosť definovať a prepínať medzi rôznymi databázami s uloženými prihlasovacími údajmi.
  • Podpora vlastných driverov: Okrem predvolených driverov (MySQLi a PDO) je možné implementovať vlastné databázové drivery.
  • Voliteľný ORM: Pre MySQLi aj PDO je k dispozícii ORM s triedami Entity (jednotlivý riadok) a Collection (súbor riadkov), ktoré zjednodušujú prácu s dátami ako s objektmi.
  • Lazy loading a vzťahy: Podpora vzťahov typu HasOne, HasMany, MorphOne a MorphMany s možnosťou prispôsobiť dotazy pomocou callbackov.
  • Pokročilé vzťahy: Možnosť upraviť QueryBuilder v rámci vzťahov (napr. pridať limit, orderBy, where) cez voliteľný callback parameter.
  • Validácia a hromadné operácie: ORM obsahuje vstavanú validáciu dát a podporu hromadných operácií nad kolekciami.
  • Integrovaný QueryBuilder: Intuitívny nástroj na tvorbu dotazov, ktorý pokrýva od jednoduchých SELECTov až po zložité JOINy a subdotazy.
  • Callbacky pre SUCCESS a ERROR: Každá operácia vracia výsledky a debugovacie dáta cez callbacky, čo zjednodušuje spracovanie úspechov aj chýb.
  • Podpora transakcií: Jednoduchá správa transakcií s automatickým commitom alebo rollbackom.

1.3. RAW vs. ORM: Kedy použiť ktorý prístup?

Databaser ponúka dva hlavné spôsoby práce s dátami: RAW a ORM. Výber medzi nimi závisí od potrieb vášho projektu:

  • RAW MÓD:
    • Vracajú sa priamo výsledky databázových dotazov (napr. polia alebo databázové zdroje).
    • Ideálne pre jednoduché aplikácie, rýchle prototypy alebo situácie, kde potrebujete maximálnu kontrolu nad SQL dotazmi.
    • Príklad použitia: Jednoduchý SELECT na získanie zoznamu používateľov bez potreby objektovej manipulácie.
    • Výhody: Rýchle vykonanie, minimálna réžia, plná flexibilita pri písaní dotazov.
  • ORM MÓD:
    • Dáta sú mapované na objekty (Entity pre jeden riadok, Collection pre viac riadkov), čo uľahčuje prácu s dátami ako s objektmi.
    • Vhodné pre komplexné aplikácie, kde potrebujete vzťahy medzi tabuľkami, validáciu dát alebo hromadné operácie.
    • Príklad použitia: Správa používateľov s ich príspevkami (vzťah HasMany) a automatické ukladanie zmien.
    • Výhody: Objektovo-orientovaný prístup, podpora vzťahov, jednoduchá manipulácia s dátami.
Kedy použiť ktorý prístup?

  • Ak potrebujete rýchly výkon a jednoduché dotazy, zvoľte RAW.
  • Ak pracujete s komplexnými dátovými štruktúrami a chcete elegantné riešenie, siahnite po ORM.

1.4. Podpora databázových driverov (MySQLi, PDO)

Databaser podporuje dva hlavné databázové drivery, ktoré pokrývajú väčšinu bežných potrieb:

MySQLi
  • Legacy aj moderný prístup s ORM.
  • Vhodný pre projekty, ktoré už používajú MySQLi, alebo pre jednoduchšie aplikácie s MySQL databázami.
  • Podporuje všetky funkcie QueryBuildera a ORM.
PDO
  • Moderný prístup s podporou viacerých databáz (MySQL, PostgreSQL, SQLite atď.).
  • Flexibilnejší vďaka dynamickému DSN (Data Source Name), čo umožňuje pripojenie k rôznym typom databáz.
  • Rovnako podporuje QueryBuilder aj ORM.

Oba drivery sú navrhnuté tak, aby boli vzájomne zameniteľné – kód napísaný pre jeden driver funguje aj s druhým bez väčších úprav, pokiaľ rešpektujete špecifiká konkrétneho databázového systému.

1.5. Integrovaný QueryBuilder

QueryBuilder je srdcom Databaseru. Umožňuje vytvárať SQL dotazy pomocou reťaziteľných metód, čím zjednodušuje písanie bezpečných a čitateľných dotazov. Podporuje:

  • Základné operácie: select, insert, update, delete.
  • Podmienky: where, orWhere, vnorené podmienky cez Closure.
  • Spojenia tabuliek: join, leftJoin.
  • Agregácie: groupBy, having.
  • Zoradenie a obmedzenia: orderBy, limit, offset.
  • Surové dotazy: raw s podporou otáznikov (?) aj pomenovaných premenných (:name).

QueryBuilder automaticky spravuje prepared statements a bindings, čím zaisťuje ochranu pred SQL injection útokmi. Každá hodnota, ktorá sa použije v dotaze (napr. v podmienkach where alebo pri vkladaní dát cez insert), je automaticky escapovaná a nahradená placeholdermi (? alebo pomenovanými premennými :name). Tým sa minimalizuje riziko bezpečnostných zraniteľností a zároveň sa zvyšuje prehľadnosť kódu.

1.6. Callbacky pre SUCCESS a ERROR

Databaser používa systém callbackov na spracovanie výsledkov a chýb. Každá operácia (napr. execute(), save()) môže prijať dva voliteľné callbacky:

SUCCESS callback

Spustí sa pri úspešnom vykonaní operácie. Dostáva tri parametre:

  • $result: Výsledok operácie (napr. pole dát v RAW móde, objekt v ORM móde).
  • $db: Inštancia Databaseru, ktorá umožňuje ďalšie dotazy.
  • $debug: Debugovacie dáta (napr. vygenerovaný SQL dotaz, bindings).
ERROR callback

Spustí sa pri chybe. Dostáva rovnako tri parametre:

  • $error: Pole s informáciami o chybe (error – text chyby, errno – kód chyby).
  • $db: Inštancia Databaseru pre prípadné ďalšie operácie.
  • $debug: Debugovacie dáta pre analýzu problému.

Tento prístup zjednodušuje asynchrónne spracovanie a umožňuje reťazenie operácií priamo v callbackoch. Napríklad, ak pri vykonaní jedného dotazu potrebujete okamžite spustiť ďalší, môžete to urobiť priamo v SUCCESS callbacku pomocou $db->q(). Tento systém zároveň zvyšuje flexibilitu a čitateľnosť kódu, pretože logika pre úspech a chybu je oddelená a prehľadne definovaná. Ak nie je nastavený ERROR callback, je možné zachytiť chybu pomocou TRY-CATCH bloku. Naopak, ak je ERROR callback nastavený, TRY-CATCH blok nebude fungovať, pretože správa chýb je v tomto prípade plne delegovaná na callback.

2. Začíname

2.1. Inštalácia a konfigurácia Databaseru

Databaser je neoddeliteľnou súčasťou DotApp Frameworku, takže nie je potrebné ho samostatne inštalovať. Ak ste už nastavili DotApp Framework vo vašom projekte, Databaser je automaticky k dispozícii cez inštanciu $DotApp->DB. Predpokladáme, že máte framework nakonfigurovaný a pripravený na použitie.

2.2. Pridanie databázového pripojenia

Databaser umožňuje pridať a spravovať viacero databázových pripojení. Pripojenie sa definuje pomocou metódy add(), ktorá je volaná na inštancii $DotApp->DB. Príklad:

$DotApp->DB->add(
    'main',           // Názov pripojenia
    'localhost',      // Server
    'root',           // Používateľské meno
    'password123',    // Heslo
    'moja_databaza',  // Názov databázy
    'utf8mb4',        // Kódovanie
    'mysql'           // Typ databázy
);

2.3. Výber drivera (MySQLi alebo PDO)

Databaser podporuje MySQLi aj PDO. Výber drivera:

// Použitie MySQLi drivera
$DotApp->DB->driver('mysqli');

// Použitie PDO drivera
$DotApp->DB->driver('pdo');

2.4. Prvé spojenie s databázou

Po definovaní pripojenia a výbere drivera je potrebné aktivovať pripojenie:

$DotApp->DB->selectDb('main');

Príklad prvého jednoduchého dotazu:

$DotApp->DB
    ->driver('pdo')
    ->return('raw')
    ->selectDb('main')
    ->q(function ($qb) {
        $qb->select('*', 'users');
    })
    ->execute(
        function ($result, $db, $debug) {
            echo "Vygenerovaný dotaz: " . $debug['query'] . "\n";
            var_dump($result);
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']} (kód: {$error['errno']})\n";
        }
    );

Vysvetlenie
  • driver('pdo'): Nastavíme PDO ako aktívny driver.
  • return('raw'): Nastavíme RAW formát výstupu.
  • selectDb('main'): Aktivujeme pripojenie "main".
  • q() (alias qb()): Spustíme QueryBuilder a definujeme dotaz (v tomto prípade SELECT * FROM users).
  • execute(): Vykonáme dotaz s callbackmi pre úspech a chybu.
  • $result: Pole s výsledkami (v RAW móde).
  • $debug: Obsahuje vygenerovaný SQL dotaz a ďalšie informácie.

Výstup (príklad):

Vygenerovaný dotaz: SELECT * FROM users

array(2) {
  [0] => array(3) {
    ["id"] => string(1) "1"
    ["name"] => string(4) "Jano"
    ["age"] => string(2) "25"
  }
  [1] => array(3) {
    ["id"] => string(1) "2"
    ["name"] => string(5) "Maria"
    ["age"] => string(2) "30"
  }
}

3. QueryBuilder: Podrobný prehľad

QueryBuilder je kľúčovým nástrojom Databaseru, ktorý umožňuje vytvárať SQL dotazy pomocou reťaziteľných metód. Jeho hlavnou výhodou je jednoduchosť, čitateľnosť a bezpečnosť – automaticky spravuje prepared statements a bindings, čím chráni pred SQL injection útokmi. V tejto kapitole podrobne rozoberieme jeho fungovanie, dostupné metódy a ukážeme príklady od jednoduchých až po zložité dotazy.

3.1. Základné princípy QueryBuildera

QueryBuilder je objekt triedy Dotsystems\App\Parts\QueryBuilder, ktorý sa používa v rámci metódy q() alebo qb() na inštancii $DotApp->DB. Funguje tak, že postupne budujete dotaz volaním metód, pričom každá metóda pridáva časť SQL príkazu (napr. select, where, join). Na konci sa dotaz vykoná pomocou metód ako execute(), first() alebo all().

Základné vlastnosti:

  • Reťaziteľnosť: Metódy vracajú inštanciu QueryBuildera, takže ich môžete spájať do reťazca.
  • Prepared Statements: Všetky hodnoty sú automaticky escapované a nahrádzané placeholdermi (?).
  • Flexibilita: Podpora surových SQL dotazov cez metódu raw() pre špeciálne prípady.
  • Debugovateľnosť: Po vykonaní dotazu dostanete v $debug vygenerovaný SQL a bindings.

Príklad základného použitia:

$DotApp->DB->q(function ($qb) {
    $qb->select('*', 'users')->where('age', '>', 18);
})->execute(
    function ($result, $db, $debug) {
        echo $debug['query']; // "SELECT * FROM users WHERE age > ?"
        var_dump($debug['bindings']); // [18]
        var_dump($result);
    }
);

3.2. Zoznam metód QueryBuildera

Tu je podrobný prehľad všetkých hlavných metód QueryBuildera s vysvetlením a príkladmi.

3.2.1. select

Metóda select() definuje, ktoré stĺpce a z ktorej tabuľky chcete vybrať dáta.

Syntax: select($columns = '*', $table = null)

Parametre:

  • $columns: Reťazec alebo pole stĺpcov (napr. 'id, name' alebo ['id', 'name']).
  • $table: Názov tabuľky (voliteľné, ak použijete from()).

SQL ekvivalent: SELECT stĺpce FROM tabuľka

Príklad:

$qb->select('id, name', 'users');
// SQL: SELECT id, name FROM users
3.2.2. insert

Metóda insert() vloží nový riadok do tabuľky.

Syntax: insert($table, array $data)

Parametre:

  • $table: Názov tabuľky.
  • $data: Asociatívne pole s dátami (stĺpec => hodnota).

SQL ekvivalent: INSERT INTO tabuľka (stĺpce) VALUES (hodnoty)

Príklad:

$qb->insert('users', ['name' => 'Jano', 'age' => 25]);
// SQL: INSERT INTO users (name, age) VALUES (?, ?)
// Bindings: ['Jano', 25]
3.2.3. update

Metóda update() a set() aktualizuje existujúce riadky.

Syntax: update($table) + set(array $data)

Parametre:

  • $table: Názov tabuľky.
  • $data: Asociatívne pole s aktualizovanými hodnotami.
  • SQL ekvivalent: UPDATE tabuľka SET stĺpec = hodnota

    Príklad:

    $qb->update('users')->set(['age' => 26])->where('id', '=', 1);
    // SQL: UPDATE users SET age = ? WHERE id = ?
    // Bindings: [26, 1]
    
    3.2.4. delete

    Metóda delete() odstráni riadky z tabuľky.

    Syntax: delete($table = null)

    Parametre:

    • $table: Názov tabuľky (voliteľné, ak je definované inde).

    SQL ekvivalent: DELETE FROM tabuľka

    Príklad:

    $qb->delete('users')->where('id', '=', 1);
    // SQL: DELETE FROM users WHERE id = ?
    // Bindings: [1]
    
    3.2.5. where a orWhere

    Metódy where() a orWhere() pridávajú podmienky.

    Syntax: where($column, $operator = null, $value = null, $boolean = 'AND')

    Parametre:

    • $column: Stĺpec alebo Closure pre vnorené podmienky.
    • $operator: Operátor (napr. =, >, <).
    • $value: Hodnota alebo Closure pre subdotaz.
    • $boolean: Logický spoj (predvolené AND).

    SQL ekvivalent: WHERE stĺpec operátor hodnota

    Príklad:

    $qb->select('*', 'users')
       ->where('age', '>', 18)
       ->orWhere('name', '=', 'Jano');
    // SQL: SELECT * FROM users WHERE age > ? OR name = ?
    // Bindings: [18, 'Jano']
    
    3.2.6. join (INNER, LEFT)

    Metódy join() a leftJoin() spájajú tabuľky.

    Syntax: join($table, $first, $operator, $second, $type = 'INNER')

    Parametre:

    • $table: Tabuľka alebo subdotaz (QueryBuilder).
    • $first: Prvý stĺpec podmienky.
    • $operator: Operátor spojenia.
    • $second: Druhý stĺpec podmienky.
    • $type: Typ spojenia (INNER, LEFT).

    SQL ekvivalent: INNER JOIN tabuľka ON podmienka

    Príklad:

    $qb->select('users.name', 'users')
       ->join('posts', 'users.id', '=', 'posts.user_id');
    // SQL: SELECT users.name FROM users INNER JOIN posts ON users.id = posts.user_id
    
    3.2.7. groupBy

    Metóda groupBy() zoskupuje výsledky.

    Syntax: groupBy($columns)

    Parametre:

    • $columns: Stĺpec alebo pole stĺpcov.

    SQL ekvivalent: GROUP BY stĺpce

    Príklad:

    $qb->select('age', 'users')->groupBy('age');
    // SQL: SELECT age FROM users GROUP BY age
    
    3.2.8. having

    Metóda having() filtruje zoskupené výsledky.

    Syntax: having($column, $operator, $value)

    Parametre:

    • $column: Stĺpec.
    • $operator: Operátor.
    • $value: Hodnota.

    SQL ekvivalent: HAVING stĺpec operátor hodnota

    Príklad:

    $qb->select('age', 'users')->groupBy('age')->having('age', '>', 20);
    // SQL: SELECT age FROM users GROUP BY age HAVING age > ?
    // Bindings: [20]
    
    3.2.9. orderBy

    Metóda orderBy() zoraďuje výsledky.

    Syntax: orderBy($column, $direction = 'ASC')

    Parametre:

    • $column: Stĺpec.
    • $direction: Smer (ASC alebo DESC).

    SQL ekvivalent: ORDER BY stĺpec smer

    Príklad:

    $qb->select('*', 'users')->orderBy('age', 'DESC');
    // SQL: SELECT * FROM users ORDER BY age DESC
    
    3.2.11. limit a offset

    Metódy limit() a offset() obmedzujú počet výsledkov.

    Syntax: limit($limit) + offset($offset)

    Parametre:

    • $limit: Počet riadkov.
    • $offset: Počiatočný posun.

    SQL ekvivalent: LIMIT počet OFFSET posun

    Príklad:

    $qb->select('*', 'users')->limit(5)->offset(10);
    // SQL: SELECT * FROM users LIMIT ? OFFSET ?
    // Bindings: [5, 10]
    
    3.2.11. raw Query

    Metóda raw() umožňuje použiť surový SQL dotaz.

    Syntax: raw($sql, array $bindings = [])

    Parametre:

    • $sql: Surový SQL reťazec.
    • $bindings: Pole hodnôt pre placeholdery.

    SQL ekvivalent: Priamo zadaný dotaz.

    Príklad:

    $qb->raw('SELECT * FROM users WHERE age > ?', [18]);
    // SQL: SELECT * FROM users WHERE age > ?
    // Bindings: [18]
    

    3.3. Príklady od jednoduchých po zložité dotazy

    Jednoduchý select

    $qb->select('*', 'users');
    // SQL: SELECT * FROM users
    

    select s where podmienkou

    $qb->select('name', 'users')->where('age', '>', 18);
    // SQL: SELECT name FROM users WHERE age > ?
    // Bindings: [18]
    

    Vnorené podmienky (Closure)

    $qb->select('*', 'users')->where(function ($qb) {
        $qb->where('age', '>', 18)->orWhere('name', '=', 'Jano');
    });
    // SQL: SELECT * FROM users WHERE (age > ? OR name = ?)
    // Bindings: [18, 'Jano']
    

    join s viacerými tabuľkami

    $qb->select('users.name, posts.title', 'users')
       ->join('posts', 'users.id', '=', 'posts.user_id')
       ->leftJoin('comments', 'posts.id', '=', 'comments.post_id');
    // SQL: SELECT users.name, posts.title FROM users
    //      INNER JOIN posts ON users.id = posts.user_id
    //      LEFT JOIN comments ON posts.id = comments.post_id
    

    Subquery ako hodnota

    $qb->select('name', 'users')->where('id', '=', function ($qb) {
        $qb->select('user_id', 'posts')->where('title', '=', 'Novinka');
    });
    // SQL: SELECT name FROM users WHERE id = (SELECT user_id FROM posts WHERE title = ?)
    // Bindings: ['Novinka']
    

    raw dotaz s pomenovanými premennými

    $qb->raw('SELECT * FROM users WHERE age > :age AND name = :name', [
        'age' => 18,
        'name' => 'Jano'
    ]);
    // SQL: SELECT * FROM users WHERE age > ? AND name = ?
    // Bindings: [18, 'Jano']
    

    4. Práca s Databaserom v DotApp

    V tejto kapitole sa zameriame na praktické použitie Databaseru v rámci DotApp Frameworku. Ukážeme, ako nastaviť typ návratu, vykonávať dotazy, pracovať s ORM, spravovať transakcie a debugovať výsledky. Databaser je navrhnutý tak, aby poskytoval flexibilitu a jednoduchosť, či už preferujete RAW prístup, alebo objektovo-orientovaný ORM.

    4.1. Nastavenie typu návratu (RAW vs. ORM)

    Databaser umožňuje definovať, aký typ dát chcete dostať ako výsledok dotazu. Typ návratu sa nastavuje pomocou metódy return() a ovplyvňuje, ako budú dáta spracované po vykonaní dotazu.

    • RAW: Vráti surové dáta (napr. pole riadkov alebo databázový zdroj). Predvolený typ.
    • ORM: Vráti dáta ako objekty (Entity pre jeden riadok, Collection pre viac riadkov).

    Syntax: $DotApp->DB->return($type)

    $type: Reťazec 'RAW' alebo 'ORM' (nezáleží na veľkosti písmen).

    Príklad nastavenia RAW:

    $DotApp->DB->return('RAW')->q(function ($qb) {
        $qb->select('*', 'users');
    })->execute(
        function ($result, $db, $debug) {
            var_dump($result); // Pole riadkov
        }
    );
    

    Príklad nastavenia ORM:

    $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->execute(
        function ($result, $db, $debug) {
            var_dump($result); // Inštancia Collection
        }
    );
    

    Typ návratu môžete meniť pred každým dotazom, čo vám dáva flexibilitu pri práci s rôznymi časťami aplikácie.

    4.2. Metódy pre vykonanie dotazov

    Databaser ponúka niekoľko metód na vykonanie dotazov vytvorených QueryBuilderom. Každá metóda má svoje špecifické použitie.

    4.2.1. execute()

    Metóda execute() je najuniverzálnejšia – vykoná dotaz a výsledky spracuje cez callbacky.

    Syntax: execute($success = null, $error = null)

    Parametre:

    • $success: Callback pre úspech (function ($result, $db, $debug)).
    • $error: Callback pre chybu (function ($error, $db, $debug)).

    Výstup: Závisí od typu návratu (RAW: pole/zdroj, ORM: Collection/Entity).

    Príklad:

    $DotApp->DB->q(function ($qb) {
        $qb->select('*', 'users')->where('age', '>', 18);
    })->execute(
        function ($result, $db, $debug) {
            echo "Dotaz: " . $debug['query'] . "\n";
            var_dump($result);
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
        }
    );
    
    4.2.2. first()

    Metóda first() vráti prvý riadok výsledkov.

    Syntax: first()

    Výstup: RAW – pole, ORM – Entity alebo null.

    Príklad:

    $result = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    if ($result) {
        echo $result->name; // Prístup k atribútom v ORM
    }
    
    4.2.3. all()

    Metóda all() vráti všetky riadky výsledkov.

    Syntax: all()

    Výstup: RAW – pole riadkov, ORM – Collection.

    Príklad:

    $users = $DotApp->DB->return('RAW')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    foreach ($users as $user) {
        echo $user['name'] . "\n";
    }
    
    4.2.4. raw()

    Metóda raw() vráti surový výsledok dotazu (napr. databázový zdroj).

    Syntax: raw()

    Výstup: Závisí od drivera (napr. mysqli_result alebo PDO statement).

    Príklad:

    $result = $DotApp->DB->q(function ($qb) {
        $qb->select('*', 'users');
    })->raw();
    while ($row = $DotApp->DB->fetchArray($result)) {
        echo $row['name'] . "\n";
    }
    

    4.3. Práca s ORM

    ORM mód Databaseru umožňuje pracovať s dátami ako s objektmi, čo zjednodušuje manipuláciu a vzťahy medzi tabuľkami.

    4.3.1. Entity a Collection

    Entity: Predstavuje jeden riadok tabuľky. Má atribúty zodpovedajúce stĺpcom a metódy na manipuláciu.

    Collection: Zoskupenie viacerých Entity objektov s podporou iterácie a hromadných operácií.

    Príklad:

    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    foreach ($users as $user) {
        echo $user->name . "\n"; // Collection vracia Entity objekty
    }
    
    4.3.2. Ukladanie dát (save())

    Metóda save() na Entity uloží zmeny do databázy.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $user->age = 26;
    $user->save(
        function ($result, $db, $debug) {
            echo "Používateľ uložený!\n";
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
        }
    );
    
    4.3.3. Vzťahy (hasOne, hasMany)

    ORM podporuje vzťahy medzi tabuľkami:

    • hasOne: Jeden k jednému.
    • hasMany: Jeden k viacerým.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $posts = $user->hasMany('posts', 'user_id'); // Získa všetky príspevky používateľa
    foreach ($posts as $post) {
        echo $post->title . "\n";
    }
    
    4.3.4. Lazy Loading a Collection metódy

    Vzťahy sa načítajú lenivým spôsobom (lazy loading), pokiaľ ich explicitne nevyvoláte. Collection ponúka metódy ako filter(), map(), pluck().

    Príklad:

    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $names = $users->pluck('name'); // Získa pole mien
    var_dump($names->all());
    

    4.4. Transakcie

    Databaser podporuje transakcie na zabezpečenie konzistencie dát.

    4.4.1. transaction(), commit(), rollback()

    Ručné ovládanie transakcií:

    $DotApp->DB->transaction();
    $DotApp->DB->q(function ($qb) {
        $qb->insert('users', ['name' => 'Jano']);
    })->execute();
    $DotApp->DB->commit(); // Alebo rollback() pri chybe
    

    Automatická transakcia s transact():

    $DotApp->DB->transact(function ($db) {
        $db->q(function ($qb) {
            $qb->insert('users', ['name' => 'Jano']);
        })->execute();
    }, function ($result, $db, $debug) {
        echo "Transakcia úspešná!\n";
    }, function ($error, $db, $debug) {
        echo "Chyba: {$error['error']}\n";
    });
    

    4.5. Debugovanie a práca s výstupom

    Každá operácia vracia tri hlavné prvky:

    • result: Výsledok dotazu (RAW: pole, ORM: objekty).
    • db: Inštancia Databaseru pre ďalšie dotazy.
    • debug: Pole s informáciami (napr. query, bindings).

    Príklad debugovania:

    $DotApp->DB->q(function ($qb) {
        $qb->select('*', 'users')->where('age', '>', 18);
    })->execute(
        function ($result, $db, $debug) {
            echo "SQL: " . $debug['query'] . "\n";
            echo "Bindings: " . implode(', ', $debug['bindings']) . "\n";
            var_dump($result);
        }
    );
    

    5. Praktické príklady

    V tejto kapitole ukážeme praktické príklady použitia Databaseru v DotApp Frameworku. Zameriame sa na bežné scenáre, ako sú CRUD operácie (Create, Read, Update, Delete), pokročilé dotazy s join a subquery, práca s transakciami a ladenie chýb. Použijeme metódy insertedId() a affectedRows() namiesto priameho prístupu k $db->statement['execution_data'].

    5.1. Základné CRUD operácie v RAW móde

    Create (Vytvorenie):

    $DotApp->DB->return('RAW')->q(function ($qb) {
        $qb->insert('users', ['name' => 'Jano', 'age' => 25]);
    })->execute(
        function ($result, $db, $debug) {
            $id = $db->insertedId(); // Získanie ID nového záznamu
            echo "Nový používateľ s ID: $id bol vytvorený.\n";
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
        }
    );
    

    Read (Čítanie):

    $DotApp->DB->return('RAW')->q(function ($qb) {
        $qb->select('*', 'users')->where('age', '>', 20);
    })->execute(
        function ($result, $db, $debug) {
            foreach ($result as $user) {
                echo "Meno: {$user['name']}, Vek: {$user['age']}\n";
            }
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
        }
    );
    

    Update (Aktualizácia):

    $DotApp->DB->return('RAW')->q(function ($qb) {
        $qb->update('users')->set(['age' => 26])->where('name', '=', 'Jano');
    })->execute(
        function ($result, $db, $debug) {
            $rows = $db->affectedRows(); // Počet ovplyvnených riadkov
            echo "$rows riadkov bolo aktualizovaných.\n";
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
        }
    );
    

    Delete (Odstránenie):

    $DotApp->DB->return('RAW')->q(function ($qb) {
        $qb->delete('users')->where('name', '=', 'Jano');
    })->execute(
        function ($result, $db, $debug) {
            $rows = $db->affectedRows();
            echo "$rows riadkov bolo odstránených.\n";
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
        }
    );
    

    5.2. Základné CRUD operácie v ORM móde

    Create:

    $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->insert('users', ['name' => 'Maria', 'age' => 30]);
    })->execute(
        function ($result, $db, $debug) {
            $id = $db->insertedId();
            $user = $db->q(function ($qb) use ($id) {
                $qb->select('*', 'users')->where('id', '=', $id);
            })->first();
            echo "Vytvorený používateľ: {$user->name}\n";
        }
    );
    

    Read:

    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    foreach ($users as $user) {
        echo "Meno: {$user->name}, Vek: {$user->age}\n";
    }
    

    Update:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('name', '=', 'Maria');
    })->first();
    if ($user) {
        $user->age = 31;
        $user->save(
            function ($result, $db, $debug) {
                echo "Používateľ {$user->name} aktualizovaný.\n";
            },
            function ($error, $db, $debug) {
                echo "Chyba: {$error['error']}\n";
            }
        );
    }
    

    Delete:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('name', '=', 'Maria');
    })->first();
    if ($user) {
        $DotApp->DB->q(function ($qb) use ($user) {
            $qb->delete('users')->where('id', '=', $user->id);
        })->execute(
            function ($result, $db, $debug) {
                $rows = $db->affectedRows();
                echo "$rows riadkov bolo odstránených.\n";
            }
        );
    }
    

    5.3. Pokročilé príklady s join a subquery

    join s viacerými tabuľkami:

    $DotApp->DB->return('RAW')->q(function ($qb) {
        $qb->select('users.name, posts.title', 'users')
           ->join('posts', 'users.id', '=', 'posts.user_id')
           ->where('users.age', '>', 25);
    })->execute(
        function ($result, $db, $debug) {
            foreach ($result as $row) {
                echo "Používateľ: {$row['name']}, Príspevok: {$row['title']}\n";
            }
        }
    );
    

    Subquery v ORM:

    $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')
           ->where('id', '=', function ($subQb) {
               $subQb->select('user_id', 'posts')
                      ->where('title', '=', 'Novinka');
           });
    })->execute(
        function ($users, $db, $debug) {
            foreach ($users as $user) {
                echo "Používateľ s novinkou: {$user->name}\n";
            }
        }
    );
    

    5.4. Práca s transakciami

    Automatická transakcia:

    $DotApp->DB->transact(function ($db) {
        $db->q(function ($qb) {
            $qb->insert('users', ['name' => 'Peter', 'age' => 28]);
        })->execute();
        $id = $db->insertedId();
        $db->q(function ($qb) use ($id) {
            $qb->insert('posts', ['user_id' => $id, 'title' => 'Prvý príspevok']);
        })->execute();
    }, function ($result, $db, $debug) {
        echo "Transakcia úspešná, ID používateľa: " . $db->insertedId() . "\n";
    }, function ($error, $db, $debug) {
        echo "Chyba v transakcii: {$error['error']}\n";
    });
    

    Ručná transakcia:

    $DotApp->DB->transaction();
    $DotApp->DB->q(function ($qb) {
        $qb->insert('users', ['name' => 'Anna', 'age' => 22]);
    })->execute(
        function ($result, $db, $debug) {
            $id = $db->insertedId();
            $db->q(function ($qb) use ($id) {
                $qb->insert('posts', ['user_id' => $id, 'title' => 'Test']);
            })->execute(
                function ($result, $db, $debug) {
                    $db->commit();
                    echo "Transakcia dokončená.\n";
                },
                function ($error, $db, $debug) {
                    $db->rollback();
                    echo "Rollback: {$error['error']}\n";
                }
            );
        }
    );
    

    5.5. Ladenie a riešenie chýb

    Debugovanie dotazu:

    $DotApp->DB->q(function ($qb) {
        $qb->select('*', 'users')->where('age', '>', 18);
    })->execute(
        function ($result, $db, $debug) {
            echo "SQL: " . $debug['query'] . "\n";
            echo "Bindings: " . implode(', ', $debug['bindings']) . "\n";
            echo "Počet riadkov: " . $db->affectedRows() . "\n";
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']} (kód: {$error['errno']})\n";
            echo "SQL: " . $debug['query'] . "\n";
        }
    );
    

    Riešenie chyby:

    $DotApp->DB->q(function ($qb) {
        $qb->select('*', 'neexistujuca_tabuľka'); // Chybný dotaz
    })->execute(
        function ($result, $db, $debug) {
            echo "Úspech\n";
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
            // Možné ďalšie dotazy na opravu
            $db->q(function ($qb) {
                $qb->select('*', 'users'); // Skúsime inú tabuľku
            })->execute(
                function ($result, $db, $debug) {
                    echo "Opravený dotaz úspešný.\n";
                }
            );
        }
    );
    

    6. Práca so SchemaBuilderom

    SchemaBuilder je výkonný nástroj v Databaseri, ktorý slúži na definovanie a správu databázovej štruktúry. Umožňuje vytvárať, upravovať a odstraňovať tabuľky priamo z kódu bez nutnosti písania surových SQL príkazov na správu schémy. Je integrovaný do QueryBuildera a používa sa cez metódy ako createTable(), alterTable() a dropTable(). V tejto kapitole vysvetlíme jeho funkcie, vstupy a ukážeme praktické príklady.

    6.1. Základné princípy SchemaBuildera

    SchemaBuilder je trieda Dotsystems\App\Parts\SchemaBuilder, ktorá sa používa v callbacku metód schema() alebo priamo v createTable(), alterTable() a dropTable(). Jeho cieľom je poskytnúť programový spôsob definovania tabuliek, stĺpcov, indexov a cudzích kľúčov. Výsledné príkazy sú automaticky prevedené na SQL a vykonané cez aktívny driver (MySQLi alebo PDO).

    Kľúčové vlastnosti:

    • Reťaziteľné metódy: Podobne ako QueryBuilder, aj SchemaBuilder umožňuje reťazenie.
    • Abstrakcia: Funguje nezávisle od databázového drivera, hoci niektoré špecifické funkcie môžu závisieť od databázového systému.
    • Jednoduchosť: Umožňuje rýchlo definovať schému bez hlbokých znalostí SQL syntaxe.

    6.2. Dostupné metódy SchemaBuildera

    Tu je prehľad hlavných metód s vysvetlením a vstupmi:

    6.2.1. id()

    Pridá primárny kľúč typu BIGINT UNSIGNED AUTO_INCREMENT.

    Syntax: id($name = 'id')

    Parametre:

    • $name: Názov stĺpca (predvolené 'id').

    SQL ekvivalent: id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY

    Príklad:

    $schema->id(); // Vytvorí stĺpec `id`
    
    6.2.2. string()

    Pridá textový stĺpec typu VARCHAR.

    Syntax: string($name, $length = 255, $nullable = false)

    Parametre:

    • $name: Názov stĺpca.
    • $length: Dĺžka (predvolené 255).
    • $nullable: Povolí NULL hodnoty (predvolené false).

    SQL ekvivalent: VARCHAR(dĺžka) [NOT NULL | NULL]

    Príklad:

    $schema->string('name', 100, true); // `name` VARCHAR(100) NULL
    
    6.2.3. integer()

    Pridá číselný stĺpec typu INT.

    Syntax: integer($name, $nullable = false)

    Parametre:

    • $name: Názov stĺpca.
    • $nullable: Povolí NULL hodnoty (predvolené false).

    SQL ekvivalent: INT [NOT NULL | NULL]

    Príklad:

    $schema->integer('age'); // `age` INT NOT NULL
    
    6.2.4. timestamps()

    Pridá stĺpce created_at a updated_at pre časové značky.

    Syntax: timestamps()

    SQL ekvivalent:

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    

    Príklad:

    $schema->timestamps();
    
    6.2.5. foreign()

    Pridá cudzí kľúč.

    Syntax: foreign($column, $references = 'id', $on = null)

    Parametre:

    • $column: Stĺpec s cudzím kľúčom.
    • $references: Referenčný stĺpec (predvolené 'id').
    • $on: Referenčná tabuľka (ak nie je zadaná, odvodí sa z $column).

    SQL ekvivalent: FOREIGN KEY (column) REFERENCES table (references) ON DELETE CASCADE

    Príklad:

    $schema->foreign('user_id', 'id', 'users'); // Cudzí kľúč na `users(id)`
    
    6.2.6. index()

    Pridá index na stĺpec.

    Syntax: index($column)

    Parametre:

    • $column: Názov stĺpca.

    SQL ekvivalent: INDEX (column)

    Príklad:

    $schema->index('name');
    
    6.2.7. addColumn() (pre ALTER TABLE)

    Pridá nový stĺpec do existujúcej tabuľky.

    Syntax: addColumn($name, $type, $length = null, $nullable = false)

    Parametre:

    • $name: Názov stĺpca.
    • $type: Typ (napr. VARCHAR, INT).
    • $length: Dĺžka (voliteľné).
    • $nullable: Povolí NULL (predvolené false).

    SQL ekvivalent: ADD column type [length] [NOT NULL | NULL]

    Príklad:

    $schema->addColumn('email', 'VARCHAR', 150, true);
    
    6.2.8. dropColumn() (pre ALTER TABLE)

    Odstráni stĺpec z tabuľky.

    Syntax: dropColumn($name)

    Parametre:

    • $name: Názov stĺpca.

    SQL ekvivalent: DROP COLUMN column

    Príklad:

    $schema->dropColumn('email');
    

    6.3. Použitie SchemaBuildera

    SchemaBuilder sa používa v kombinácii s metódami QueryBuildera: createTable(), alterTable() a dropTable().

    6.3.1. Vytvorenie tabuľky

    Metóda createTable() vytvorí novú tabuľku.

    Príklad:

    $DotApp->DB->q(function ($qb) {
        $qb->createTable('users', function ($schema) {
            $schema->id();
            $schema->string('name', 50);
            $schema->integer('age', true);
            $schema->timestamps();
            $schema->index('name');
        });
    })->execute(
        function ($result, $db, $debug) {
            echo "Tabuľka 'users' bola vytvorená.\n";
            echo "SQL: {$debug['query']}\n";
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
        }
    );
    

    Vygenerovaný SQL:

    CREATE TABLE users (
        `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        `name` VARCHAR(50) NOT NULL,
        `age` INT NULL,
        `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        INDEX (`name`)
    )
    
    6.3.2. Úprava tabuľky

    Metóda alterTable() upraví existujúcu tabuľku.

    Príklad:

    $DotApp->DB->q(function ($qb) {
        $qb->alterTable('users', function ($schema) {
            $schema->addColumn('email', 'VARCHAR', 100, true);
            $schema->foreign('user_id', 'id', 'users');
            $schema->dropColumn('age');
        });
    })->execute(
        function ($result, $db, $debug) {
            echo "Tabuľka 'users' bola upravená.\n";
            echo "SQL: {$debug['query']}\n";
        }
    );
    

    Vygenerovaný SQL:

    ALTER TABLE users 
        ADD `email` VARCHAR(100) NULL,
        ADD FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
        DROP COLUMN `age`
    
    6.3.3. Odstránenie tabuľky

    Metóda dropTable() odstráni tabuľku.

    Príklad:

    $DotApp->DB->q(function ($qb) {
        $qb->dropTable('users');
    })->execute(
        function ($result, $db, $debug) {
            echo "Tabuľka 'users' bola odstránená.\n";
        }
    );
    

    Vygenerovaný SQL:

    DROP TABLE users
    

    6.4. Pokročilé príklady

    Vytvorenie tabuľky s cudzími kľúčmi:

    $DotApp->DB->q(function ($qb) {
        $qb->createTable('posts', function ($schema) {
            $schema->id();
            $schema->string('title', 200);
            $schema->integer('user_id');
            $schema->foreign('user_id', 'id', 'users');
            $schema->timestamps();
        });
    })->execute(
        function ($result, $db, $debug) {
            echo "Tabuľka 'posts' vytvorená.\n";
        }
    );
    

    Hromadná migrácia:

    $DotApp->DB->transact(function ($db) {
        $db->q(function ($qb) {
            $qb->createTable('users', function ($schema) {
                $schema->id();
                $schema->string('name');
            });
        })->execute();
        $db->q(function ($qb) {
            $qb->createTable('posts', function ($schema) {
                $schema->id();
                $schema->integer('user_id');
                $schema->foreign('user_id');
            });
        })->execute();
    }, function ($result, $db, $debug) {
        echo "Migrácia úspešná.\n";
    });
    

    6.5. Poznámky a obmedzenia

    Kompatibilita: Niektoré funkcie (napr. ON DELETE CASCADE) nemusia fungovať rovnako vo všetkých databázach (napr. SQLite má obmedzenia).

    Transakcie: Pri väčších zmenách schémy používajte transact() na zaistenie konzistencie.

    Debugovanie: Vždy kontrolujte $debug['query'] na overenie vygenerovaného SQL.

    7. CacheDriverInterface

    Databaser v DotApp Frameworku podporuje integráciu s cachingom, čo umožňuje ukladať výsledky dotazov a zvyšovať výkon aplikácie pri opakovaných požiadavkách na rovnaké dáta. Aby bolo možné caching využiť, je potrebné implementovať rozhranie CacheDriverInterface, ktoré definuje štandardné metódy pre prácu s cache. V tejto kapitole vysvetlíme, čo toto rozhranie obsahuje, aké metódy musí cache driver podporovať, a ukážeme, ako ho používať s Databaserom.

    7.1. Čo je CacheDriverInterface?

    CacheDriverInterface je rozhranie, ktoré určuje, ako má vyzerať driver pre ukladanie a načítanie dát z cache. Databaser ho používa na komunikáciu s ľubovoľným caching systémom (napr. Memcached, Redis, súborový systém), pričom logiku ukladania a správy cache si implementuje používateľ. Po nastavení cache drivera cez metódu cache() sa Databaser automaticky pokúsi načítavať výsledky z cache pred vykonaním dotazu a ukladať nové výsledky po jeho úspešnom dokončení.

    Výhody:

    • Zníženie zaťaženia databázy.
    • Rýchlejší prístup k často používaným dátam.
    • Flexibilita – môžete použiť akýkoľvek caching systém.

    7.2. Zloženie CacheDriverInterface

    Rozhranie CacheDriverInterface definuje štyri povinné metódy, ktoré musí každý cache driver implementovať:

    interface CacheDriverInterface {
        public function get($key);
        public function set($key, $value, $ttl = null);
        public function delete($key);
        public function deleteKeys($pattern);
    }
    
    7.2.1. get($key)

    Načíta hodnotu z cache na základe kľúča.

    Parameter:

    • $key: Reťazec – unikátny kľúč pre uložené dáta.

    Návratová hodnota: Uložená hodnota alebo null, ak kľúč neexistuje.

    Účel: Databaser volá túto metódu, aby skontroloval, či už výsledok dotazu existuje v cache.

    7.2.2. set($key, $value, $ttl = null)

    Uloží hodnotu do cache s daným kľúčom.

    Parametre:

    • $key: Reťazec – kľúč pre uloženie.
    • $value: Dáta na uloženie (môžu byť pole, objekt atď.).
    • $ttl: Čas životnosti v sekundách (voliteľné, null znamená bez expirácie).

    Návratová hodnota: Žiadna (alebo true/false podľa implementácie).

    Účel: Po úspešnom vykonaní dotazu Databaser ukladá výsledok do cache.

    7.2.3. delete($key)

    Odstráni konkrétny kľúč z cache.

    Parameter:

    • $key: Reťazec – kľúč na odstránenie.

    Návratová hodnota: Žiadna (alebo true/false).

    Účel: Používa sa na explicitné mazanie konkrétneho záznamu z cache.

    7.2.4. deleteKeys($pattern)

    Odstráni viacero kľúčov na základe vzoru.

    Parameter:

    • $pattern: Reťazec – vzor kľúčov (napr. "users:*").

    Návratová hodnota: Žiadna (alebo počet odstránených kľúčov).

    Účel: Databaser volá túto metódu pri aktualizácii dát (napr. save() v ORM), aby invalidoval súvisiace cache záznamy.

    7.3. Implementácia vlastného CacheDrivera

    Tu je príklad jednoduchej implementácie cache drivera využívajúceho súborový systém:

    class FileCacheDriver implements CacheDriverInterface {
        private $cacheDir;
    
        public function __construct($cacheDir = '/tmp/cache') {
            $this->cacheDir = $cacheDir;
            if (!is_dir($cacheDir)) {
                mkdir($cacheDir, 0777, true);
            }
        }
    
        public function get($key) {
            $file = $this->cacheDir . '/' . md5($key);
            if (file_exists($file)) {
                $data = unserialize(file_get_contents($file));
                if ($data['expires'] === null || $data['expires'] > time()) {
                    return $data['value'];
                }
                unlink($file); // Expirované, odstránime
            }
            return null;
        }
    
        public function set($key, $value, $ttl = null) {
            $file = $this->cacheDir . '/' . md5($key);
            $expires = $ttl ? time() + $ttl : null;
            $data = ['value' => $value, 'expires' => $expires];
            file_put_contents($file, serialize($data));
            return true;
        }
    
        public function delete($key) {
            $file = $this->cacheDir . '/' . md5($key);
            if (file_exists($file)) {
                unlink($file);
                return true;
            }
            return false;
        }
    
        public function deleteKeys($pattern) {
            $count = 0;
            foreach (glob($this->cacheDir . '/*') as $file) {
                $key = basename($file);
                if (fnmatch($pattern, $key)) {
                    unlink($file);
                    $count++;
                }
            }
            return $count;
        }
    }
    

    Vysvetlenie:

    • get(): Načíta dáta zo súboru, ak neexpirovali.
    • set(): Uloží dáta do súboru s voliteľným TTL.
    • delete(): Odstráni konkrétny súbor.
    • deleteKeys(): Odstráni súbory podľa vzoru (používa fnmatch).

    7.4. Použitie CacheDrivera s Databaserom

    Po implementácii cache drivera ho nastavíte pomocou metódy cache():

    $cacheDriver = new FileCacheDriver('/tmp/myapp_cache');
    $DotApp->DB->cache($cacheDriver);
    
    // Príklad dotazu s cachingom
    $DotApp->DB->q(function ($qb) {
        $qb->select('*', 'users')->where('age', '>', 18);
    })->execute(
        function ($result, $db, $debug) {
            echo "Výsledky (z cache alebo DB):\n";
            var_dump($result);
        }
    );
    

    Ako to funguje:

    • Databaser vygeneruje kľúč (napr. users:RAW:hash_dotazu).
    • Skontroluje cache cez get(). Ak nájde platné dáta, vráti ich bez dotazu na DB.
    • Ak dáta nie sú v cache, vykoná dotaz a uloží výsledok cez set() s TTL (predvolené 3600 sekúnd).
    • Pri aktualizácii (napr. save() v ORM) invaliduje súvisiace kľúče cez deleteKeys().

    7.5. Príklad s pokročilým cachingom

    Caching s ORM a invalidáciou:

    $cacheDriver = new FileCacheDriver();
    $DotApp->DB->cache($cacheDriver);
    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    
    $user = $users->first();
    $user->age = 40;
    $user->save(
        function ($result, $db, $debug) {
            echo "Používateľ uložený, cache invalidovaná.\n";
        }
    );
    

    Čo sa deje:

    • Prvý dotaz uloží Collection do cache.
    • Pri save() sa zavolá deleteKeys("users:ORM:*"), čím sa invalidujú všetky ORM záznamy pre users.

    7.6. Poznámky a tipy

    TTL: Nastavte rozumnú hodnotu TTL (napr. 3600 sekúnd) podľa potreby aplikácie.

    Vzory kľúčov: Databaser používa formát tabuľka:typ:hash, takže vzory ako "users:*" sú efektívne.

    Výkon: Pre produkčné prostredie zvážte rýchle cache systémy ako Redis namiesto súborov.

    Testovanie: Overte, či deleteKeys() správne invaliduje cache, aby ste predišli zastaraným dátam.

    8. Práca s Entity

    Entity je základnou stavebnou jednotkou ORM (Object-Relational Mapping) v Databaseri, ktorá reprezentuje jeden riadok v databázovej tabuľke ako objekt. Umožňuje jednoduchú manipuláciu s dátami, definovanie vzťahov a validáciu. Táto kapitola podrobne vysvetlí jej štruktúru, všetky dostupné metódy a ukáže ich praktické použitie.

    8.1. Čo je Entity?

    Entity je dynamicky generovaný objekt, ktorý mapuje riadok tabuľky na objekt s atribútmi zodpovedajúcimi stĺpcom. Vytvára sa automaticky, keď nastavíte typ návratu na 'ORM' a vykonáte dotaz vracajúci jeden riadok (napr. cez first()) alebo viac riadkov (v Collection cez all()). Poskytuje objektovo-orientovaný prístup k dátam, čím zjednodušuje prácu s databázou.

    Kľúčové vlastnosti:

    • Atribúty: Prístup k stĺpcom tabuľky ako k vlastnostiam objektu (napr. $entity->name).
    • Vzťahy: Podpora hasOne, hasMany, morphOne, morphMany pre prepojenie tabuliek.
    • Validácia: Možnosť definovať pravidlá pred uložením dát.
    • Lazy a Eager Loading: Vzťahy sa načítajú lenivo, s možnosťou prednačítania.

    8.2. Zloženie Entity

    Entity je anonymná trieda definovaná v createMysqliDriver() alebo createPdoDriver(). Obsahuje tieto hlavné prvky:

    Súkromné atribúty:

    • $attributes: Pole aktuálnych hodnôt stĺpcov (napr. ['id' => 1, 'name' => 'Jano']).
    • $originalAttributes: Pôvodné hodnoty pre sledovanie zmien.
    • $db: Referencia na inštanciu Databaseru.
    • $table: Názov tabuľky (odvodený z dotazu alebo odhadnutý).
    • $primaryKey: Primárny kľúč (predvolené 'id').
    • $rules: Pole validačných pravidiel.
    • $relations: Pole načítaných vzťahov (napr. hasMany).
    • $with: Pole vzťahov pre eager loading.
    • $morphRelations: Pole polymorfných vzťahov.

    Metódy:

    • with($relations): Nastaví vzťahy pre eager loading.
    • loadRelations(): Načíta vzťahy definované v $with.
    • setPrimaryKey($key): Nastaví primárny kľúč.
    • setRules(array $rules): Definuje validačné pravidlá.
    • save($callbackOk = null, $callbackError = null): Uloží zmeny do databázy.
    • hasOne($relatedTable, $foreignKey, $localKey = null, $callback = null): Definuje vzťah jeden k jednému.
    • hasMany($relatedTable, $foreignKey, $localKey = null, $callback = null): Definuje vzťah jeden k viacerým.
    • morphOne($relatedTable, $typeField, $idField, $typeValue, $localKey = null, $callback = null): Definuje polymorfný vzťah jeden k jednému.
    • morphMany($relatedTable, $typeField, $idField, $typeValue, $localKey = null, $callback = null): Definuje polymorfný vzťah jeden k viacerým.
    • toArray(): Vráti atribúty ako pole.

    8.3. Základné použitie Entity

    Získanie Entity:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    
    echo $user->name; // Prístup k atribútu
    $user->age = 26;  // Zmena atribútu
    $user->save();    // Uloženie zmien
    

    8.4. Podrobný prehľad metód Entity

    Tu je detailný popis všetkých verejne dostupných metód Entity:

    8.4.1. with($relations)

    Nastaví vzťahy, ktoré sa majú prednačítať (eager loading) namiesto lenivého načítania (lazy loading).

    Syntax: with($relations)

    Parameter:

    • $relations: Reťazec alebo pole názvov vzťahov (napr. 'hasMany:posts' alebo ['hasOne:profile', 'hasMany:posts']).

    Návratová hodnota: $this (reťaziteľné).

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $user->with('hasMany:posts');
    $user->loadRelations();
    foreach ($user->hasMany('posts', 'user_id') as $post) {
        echo $post->title . "\n";
    }
    
    8.4.2. loadRelations()

    Načíta všetky vzťahy definované v $with.

    Syntax: loadRelations()

    Návratová hodnota: Žiadna.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $user->with(['hasOne:profile', 'hasMany:posts']);
    $user->loadRelations();
    echo $user->hasOne('profiles', 'user_id')->bio;
    
    8.4.3. setPrimaryKey($key)

    Nastaví primárny kľúč namiesto predvoleného 'id'.

    Syntax: setPrimaryKey($key)

    Parameter:

    • $key: Reťazec – názov stĺpca (napr. 'user_id').

    Návratová hodnota: $this.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('user_id', '=', 1);
    })->first();
    $user->setPrimaryKey('user_id');
    $user->user_id = 2;
    $user->save();
    
    8.4.4. setRules(array $rules)

    Definuje validačné pravidlá pre atribúty pred uložením.

    Syntax: setRules(array $rules)

    Parameter:

    • $rules: Asociatívne pole (napr. 'name' => ['required', 'string', 'max:50']).

    Podporované pravidlá: required, integer, string, min:X, max:X, email.

    Návratová hodnota: $this.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $user->setRules([
        'name' => ['required', 'string', 'max:50'],
        'age' => ['integer', 'min:18']
    ]);
    $user->name = '';
    $user->save(
        null,
        function ($error, $db, $debug) {
            echo "Validácia zlyhala: {$error['error']}\n";
        }
    );
    
    8.4.5. save($callbackOk = null, $callbackError = null)

    Uloží zmeny v Entity do databázy (vkladá nový záznam alebo aktualizuje existujúci).

    Syntax: save($callbackOk = null, $callbackError = null)

    Parametre:

    • $callbackOk: Callback pri úspechu (function ($result, $db, $debug)).
    • $callbackError: Callback pri chybe (function ($error, $db, $debug)).

    Návratová hodnota: Žiadna.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $user->age = 27;
    $user->save(
        function ($result, $db, $debug) {
            echo "Používateľ uložený, SQL: {$debug['query']}\n";
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
        }
    );
    

    Vkladanie nového záznamu:

    $user = $DotApp->DB->newEntity();
    $user->table('users'); // Nastavenie tabuľky
    $user->name = 'Maria';
    $user->age = 30;
    $user->save(
        function ($result, $db, $debug) {
            echo "Nový používateľ s ID: {$db->insertedId()} vytvorený.\n";
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
        }
    );
    
    8.4.6. hasOne($relatedTable, $foreignKey, $localKey = null, $callback = null)

    Definuje vzťah jeden k jednému.

    Syntax: hasOne($relatedTable, $foreignKey, $localKey = null, $callback = null)

    Parametre:

    • $relatedTable: Názov súvisiacej tabuľky.
    • $foreignKey: Cudzí kľúč v súvisiacej tabuľke.
    • $localKey: Lokálny kľúč (predvolené 'id').
    • $callback: Closure na úpravu dotazu.

    Návratová hodnota: Entity alebo null.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $profile = $user->hasOne('profiles', 'user_id', 'id', function ($qb) {
        $qb->where('active', '=', 1);
    });
    echo $profile->bio;
    
    8.4.7. hasMany($relatedTable, $foreignKey, $localKey = null, $callback = null)

    Definuje vzťah jeden k viacerým.

    Syntax: hasMany($relatedTable, $foreignKey, $localKey = null, $callback = null)

    Parametre: Rovnaké ako hasOne.

    Návratová hodnota: Pole Entity objektov.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $posts = $user->hasMany('posts', 'user_id', null, function ($qb) {
        $qb->orderBy('created_at', 'DESC')->limit(2);
    });
    foreach ($posts as $post) {
        echo $post->title . "\n";
    }
    
    8.4.8. morphOne($relatedTable, $typeField, $idField, $typeValue, $localKey = null, $callback = null)

    Definuje polymorfný vzťah jeden k jednému.

    Syntax: morphOne($relatedTable, $typeField, $idField, $typeValue, $localKey = null, $callback = null)

    Parametre:

    • $typeField: Stĺpec s typom (napr. 'imageable_type').
    • $idField: Stĺpec s ID (napr. 'imageable_id').
    • $typeValue: Hodnota typu (napr. 'User').

    Návratová hodnota: Entity alebo null.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $image = $user->morphOne('images', 'imageable_type', 'imageable_id', 'User');
    echo $image->url;
    
    8.4.9. morphMany($relatedTable, $typeField, $idField, $typeValue, $localKey = null, $callback = null)

    Definuje polymorfný vzťah jeden k viacerým.

    Syntax: morphMany($relatedTable, $typeField, $idField, $typeValue, $localKey = null, $callback = null)

    Parametre: Rovnaké ako morphOne.

    Návratová hodnota: Pole Entity objektov.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $images = $user->morphMany('images', 'imageable_type', 'imageable_id', 'User');
    foreach ($images as $image) {
        echo $image->url . "\n";
    }
    
    8.4.10. toArray()

    Vráti atribúty Entity ako asociatívne pole.

    Syntax: toArray()

    Návratová hodnota: Pole s aktuálnymi hodnotami $attributes.

    Príklad:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $data = $user->toArray();
    var_dump($data); // ['id' => 1, 'name' => 'Jano', 'age' => 26]
    

    8.5. Praktické príklady

    Úprava a uloženie s validáciou:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $user->setRules(['name' => 'required', 'age' => 'integer']);
    $user->name = 'Peter';
    $user->age = 28;
    $user->save(
        function ($result, $db, $debug) {
            echo "Používateľ uložený: {$user->name}, vek: {$user->age}\n";
        }
    );
    

    Eager Loading viacerých vzťahov:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first()->with(['hasOne:profile', 'hasMany:posts']);
    $user->loadRelations();
    echo $user->hasOne('profiles', 'user_id')->bio . "\n";
    foreach ($user->hasMany('posts', 'user_id') as $post) {
        echo $post->title . "\n";
    }
    

    Polymorfný vzťah s filtrom:

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $recentImages = $user->morphMany('images', 'imageable_type', 'imageable_id', 'User', null, function ($qb) {
        $qb->orderBy('created_at', 'DESC')->limit(3);
    });
    foreach ($recentImages as $image) {
        echo "Najnovší obrázok: {$image->url}\n";
    }
    

    8.6. Poznámky

    Lazy vs. Eager Loading: Pre časté prístupy k vzťahom vždy používajte with() a loadRelations(), aby ste znížili počet dotazov.

    Validácia: Pravidlá kontrolujte pred save(), inak sa uložia nevalidné dáta.

    Prispôsobenie: Callbacky v save() a vzťahoch umožňujú flexibilitu bez potreby rozširovania triedy.

    9. Práca s Collection

    Collection je trieda v ORM (Object-Relational Mapping) vrstve Databaseru, ktorá slúži na správu viacerých Entity objektov – teda súbor riadkov z databázy. Umožňuje iteráciu, filtrovanie, transformáciu a hromadné operácie nad dátami. Táto kapitola podrobne vysvetlí jej štruktúru, všetky dostupné metódy a ukáže ich praktické použitie s HTML výstupmi.

    9.1. Čo je Collection?

    Collection je dynamicky generovaný objekt, ktorý uchováva zoznam Entity objektov vrátených z dotazu, keď nastavíte typ návratu na 'ORM' a použijete metódu all() alebo získate vzťah typu hasMany/morphMany. Poskytuje objektovo-orientovaný prístup k viacerým záznamom naraz, čím zjednodušuje manipuláciu s dátami.

    Kľúčové vlastnosti:

    • Iterovateľnosť: Implementuje IteratorAggregate pre jednoduchú iteráciu cez foreach.
    • Hromadné operácie: Podpora metód ako saveAll() na uloženie zmien vo všetkých entitách.
    • Filtrovanie a transformácia: Metódy ako filter(), map(), pluck() na prácu s dátami.
    • Vzťahy: Možnosť eager loadingu vzťahov pre optimalizáciu dotazov.

    9.2. Zloženie Collection

    Collection je trieda definovaná v Databaseri. Obsahuje tieto hlavné prvky:

    Súkromné atribúty:

    • $items: Pole Entity objektov (napr. [0 => Entity, 1 => Entity]).
    • $db: Referencia na inštanciu Databaseru.
    • $with: Pole vzťahov pre eager loading (napr. ['hasMany:posts']).

    Metódy:

    • with($relations): Nastaví vzťahy pre eager loading.
    • loadRelations(): Načíta vzťahy definované v $with.
    • all(): Vráti pole všetkých Entity objektov.
    • first(): Vráti prvú Entity alebo null.
    • filter($callback): Filtruje entity podľa podmienky.
    • map($callback): Transformuje entity pomocou callbacku.
    • pluck($field): Extrahuje hodnoty konkrétneho stĺpca.
    • saveAll($callbackOk = null, $callbackError = null): Uloží zmeny vo všetkých entitách.
    • toArray(): Vráti kolekciu ako pole polí.
    • count(): Vráti počet entít v kolekcii.

    9.3. Základné použitie Collection

    Získanie Collection:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    
    foreach ($users as $user) {
        echo "Meno: {$user->name}, Vek: {$user->age}\n";
    }
        

    9.4. Podrobný prehľad metód Collection

    Tu je detailný popis všetkých verejne dostupných metód Collection:

    9.4.1. with($relations)

    Nastaví vzťahy, ktoré sa majú prednačítať (eager loading) namiesto lenivého načítania.

    Syntax: with($relations)

    Parameter:

    • $relations: Reťazec alebo pole názvov vzťahov (napr. 'hasMany:posts' alebo ['hasOne:profile', 'hasMany:posts']).

    Návratová hodnota: $this (reťaziteľné).

    Príklad:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all()->with('hasMany:posts');
    $users->loadRelations();
    
    $html = "<table border='1'><tr><th>Meno</th><th>Príspevky</th></tr>";
    foreach ($users as $user) {
        $posts = implode(', ', array_map(fn($post) => $post->title, $user->hasMany('posts', 'user_id')));
        $html .= "<tr><td>{$user->name}</td><td>{$posts}</td></tr>";
    }
    $html .= "</table>";
    echo $html;
        

    Výstup:

    
    <table border="1">
        <tr><th>Meno</th><th>Príspevky</th></tr>
        <tr><td>Jano</td><td>Príspevok 1, Príspevok 2</td></tr>
        <tr><td>Maria</td><td>Novinka</td></tr>
    </table>
        
    9.4.2. loadRelations()

    Načíta všetky vzťahy definované v $with.

    Syntax: loadRelations()

    Návratová hodnota: Žiadna.

    Príklad:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all()->with(['hasOne:profile']);
    $users->loadRelations();
    
    $html = "<ul>";
    foreach ($users as $user) {
        $html .= "<li>{$user->name} - Profil: {$user->hasOne('profiles', 'user_id')->bio}</li>";
    }
    $html .= "</ul>";
    echo $html;
        

    Výstup:

    
    <ul>
        <li>Jano - Profil: Milovník technológií</li>
        <li>Maria - Profil: Cestovateľka</li>
    </ul>
        
    9.4.3. all()

    Vráti pole všetkých Entity objektov v kolekcii.

    Syntax: all()

    Návratová hodnota: Pole Entity objektov.

    Príklad:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $allUsers = $users->all();
    
    $html = "<table border='1'><tr><th>ID</th><th>Meno</th></tr>";
    foreach ($allUsers as $user) {
        $html .= "<tr><td>{$user->id}</td><td>{$user->name}</td></tr>";
    }
    $html .= "</table>";
    echo $html;
        

    Výstup:

    
    <table border="1">
        <tr><th>ID</th><th>Meno</th></tr>
        <tr><td>1</td><td>Jano</td></tr>
        <tr><td>2</td><td>Maria</td></tr>
    </table>
        
    9.4.4. first()

    Vráti prvú Entity z kolekcie alebo null, ak je prázdna.

    Syntax: first()

    Návratová hodnota: Entity alebo null.

    Príklad:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $firstUser = $users->first();
    
    $html = $firstUser ? "<p>Prvý používateľ: {$firstUser->name}</p>" : "<p>Žiadni používatelia</p>";
    echo $html;
        

    Výstup:

    
    <p>Prvý používateľ: Jano</p>
        
    9.4.5. filter($callback)

    Filtruje kolekciu na základe podmienky definovanej v callbacku.

    Syntax: filter($callback)

    Parameter:

    • $callback: Funkcia (function ($entity)) vracajúca true pre zachovanie položky.

    Návratová hodnota: Nová inštancia Collection.

    Príklad:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $adults = $users->filter(function ($user) {
        return $user->age >= 18;
    });
    
    $html = "<ul>";
    foreach ($adults as $adult) {
        $html .= "<li>{$adult->name} ({$adult->age})</li>";
    }
    $html .= "</ul>";
    echo $html;
        

    Výstup:

    
    <ul>
        <li>Jano (25)</li>
        <li>Maria (30)</li>
    </ul>
        
    9.4.6. map($callback)

    Transformuje každú Entity v kolekcii pomocou callbacku.

    Syntax: map($callback)

    Parameter:

    • $callback: Funkcia (function ($entity)) vracajúca upravenú Entity.

    Návratová hodnota: Nová inštancia Collection.

    Príklad:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $updated = $users->map(function ($user) {
        $user->age += 1;
        return $user;
    });
    
    $html = "<table border='1'><tr><th>Meno</th><th>Nový vek</th></tr>";
    foreach ($updated as $user) {
        $html .= "<tr><td>{$user->name}</td><td>{$user->age}</td></tr>";
    }
    $html .= "</table>";
    echo $html;
        

    Výstup:

    
    <table border="1">
        <tr><th>Meno</th><th>Nový vek</th></tr>
        <tr><td>Jano</td><td>26</td></tr>
        <tr><td>Maria</td><td>31</td></tr>
    </table>
        
    9.4.7. pluck($field)

    Extrahuje hodnoty konkrétneho atribútu zo všetkých Entity v kolekcii.

    Syntax: pluck($field)

    Parameter:

    • $field: Názov atribútu (napr. 'name').

    Návratová hodnota: Pole hodnôt.

    Príklad:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $names = $users->pluck('name');
    
    $html = "<ul>";
    foreach ($names as $name) {
        $html .= "<li>{$name}</li>";
    }
    $html .= "</ul>";
    echo $html;
        

    Výstup:

    
    <ul>
        <li>Jano</li>
        <li>Maria</li>
    </ul>
        
    9.4.8. saveAll($callbackOk = null, $callbackError = null)

    Uloží zmeny vo všetkých Entity v kolekcii do databázy.

    Syntax: saveAll($callbackOk = null, $callbackError = null)

    Parametre:

    • $callbackOk: Callback pri úspechu (function ($result, $db, $debug)).
    • $callbackError: Callback pri chybe (function ($error, $db, $debug)).

    Návratová hodnota: Žiadna.

    Príklad:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $updated = $users->map(function ($user) {
        $user->age += 1;
        return $user;
    });
    $updated->saveAll(
        function ($result, $db, $debug) {
            $html = "<p>Zmeny uložené, ovplyvnených riadkov: {$db->affectedRows()}</p>";
            echo $html;
        },
        function ($error, $db, $debug) {
            echo "Chyba: {$error['error']}\n";
        }
    );
    
    $html = "<table border='1'><tr><th>Meno</th><th>Nový vek</th></tr>";
    foreach ($updated as $user) {
        $html .= "<tr><td>{$user->name}</td><td>{$user->age}</td></tr>";
    }
    $html .= "</table>";
    echo $html;
        

    Výstup:

    
    <p>Zmeny uložené, ovplyvnených riadkov: 2</p>
    <table border="1">
        <tr><th>Meno</th><th>Nový vek</th></tr>
        <tr><td>Jano</td><td>26</td></tr>
        <tr><td>Maria</td><td>31</td></tr>
    </table>
        
    9.4.9. toArray()

    Vráti kolekciu ako pole asociatívnych polí.

    Syntax: toArray()

    Návratová hodnota: Pole polí.

    Príklad:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $data = $users->toArray();
    
    $html = "<table border='1'><tr><th>ID</th><th>Meno</th><th>Vek</th></tr>";
    foreach ($data as $user) {
        $html .= "<tr><td>{$user['id']}</td><td>{$user['name']}</td><td>{$user['age']}</td></tr>";
    }
    $html .= "</table>";
    echo $html;
        

    Výstup:

    
    <table border="1">
        <tr><th>ID</th><th>Meno</th><th>Vek</th></tr>
        <tr><td>1</td><td>Jano</td><td>25</td></tr>
        <tr><td>2</td><td>Maria</td><td>30</td></tr>
    </table>
        
    9.4.10. count()

    Vráti počet Entity objektov v kolekcii.

    Syntax: count()

    Návratová hodnota: Celé číslo.

    Príklad:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $count = $users->count();
    
    $html = "<p>Počet používateľov: {$count}</p>";
    echo $html;
        

    Výstup:

    
    <p>Počet používateľov: 2</p>
        

    9.5. Praktické príklady

    Filtrovanie a hromadné uloženie:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $adults = $users->filter(function ($user) {
        return $user->age >= 18;
    })->map(function ($user) {
        $user->status = 'Dospelý';
        return $user;
    })->saveAll(
        function ($result, $db, $debug) {
            echo "Dospelí používatelia aktualizovaní.\n";
        }
    );
    
    $html = "<table border='1'><tr><th>Meno</th><th>Stav</th></tr>";
    foreach ($adults as $user) {
        $html .= "<tr><td>{$user->name}</td><td>{$user->status}</td></tr>";
    }
    $html .= "</table>";
    echo $html;
        

    Výstup:

    
    <table border="1">
        <tr><th>Meno</th><th>Stav</th></tr>
        <tr><td>Jano</td><td>Dospelý</td></tr>
        <tr><td>Maria</td><td>Dospelý</td></tr>
    </table>
        

    Eager Loading vzťahov:

    
    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all()->with(['hasMany:posts', 'hasOne:profile']);
    $users->loadRelations();
    
    $html = "<table border='1'><tr><th>Meno</th><th>Profil</th><th>Príspevky</th></tr>";
    foreach ($users as $user) {
        $posts = implode(', ', array_map(fn($post) => $post->title, $user->hasMany('posts', 'user_id')));
        $html .= "<tr><td>{$user->name}</td><td>{$user->hasOne('profiles', 'user_id')->bio}</td><td>{$posts}</td></tr>";
    }
    $html .= "</table>";
    echo $html;
        

    Výstup:

    
    <table border="1">
        <tr><th>Meno</th><th>Profil</th><th>Príspevky</th></tr>
        <tr><td>Jano</td><td>Milovník technológií</td><td>Príspevok 1, Príspevok 2</td></tr>
        <tr><td>Maria</td><td>Cestovateľka</td><td>Novinka</td></tr>
    </table>
        

    9.6. Poznámky

    • Výkon: Pri veľkých kolekciách zvážte použitie limit() v dotaze, aby ste znížili zaťaženie pamäte.
    • Eager Loading: Používajte with() a loadRelations() na zníženie počtu dotazov pri práci so vzťahmi.
    • Flexibilita: Reťazenie metód ako filter() a map() umožňuje plynulé spracovanie dát.

    10. Migrácie

    Migrácie v Databaseri umožňujú definovať a spravovať štruktúru databázy pomocou kódu. Používajú sa na vytváranie, úpravu alebo odstraňovanie tabuliek a ich stĺpcov, pričom podporujú transakcie pre hromadné operácie. Táto kapitola podrobne popisuje prácu s migráciami, vrátane ich definície, dostupných metód a praktických príkladov.

    10.2. Čo sú migrácie?

    Migrácie sú nástroj na správu databázovej schémy, ktorý umožňuje programovo definovať štruktúru tabuliek a ich vzťahov. V Databaseri sa migrácie realizujú cez SchemaBuilder a môžu byť zabalené do transakcií pomocou metódy transact(). Hlavné výhody:

    • Automatizácia: Zmena databázy je súčasťou kódu a môže byť verzionovaná.
    • Transakcie: Hromadné operácie sú bezpečné a reverzibilné pri chybe.
    • Multiplatformovosť: Podpora rôznych driverov (MySQLi, PDO) s prispôsobením syntaxe.

    10.3. Základné princípy migrácií

    Migrácie v Databaseri fungujú na základe týchto princípov:

    • SchemaBuilder: Používa sa na definovanie tabuliek a stĺpcov (napr. id(), string(), foreign()).
    • Metóda migrate(): Slúži na spúšťanie migrácií ('up' pre vytvorenie, 'down' pre rollback).
    • Transakcie: Hromadné migrácie sa vykonávajú cez transact(), kde sa viaceré operácie spúšťajú ako jeden celok.
    • Podpora driverov: MySQLi aj PDO prispôsobujú syntax podľa typu databázy (napr. MySQL, PostgreSQL, SQLite).

    10.4. Použitie migrácií

    Základné použitie migrácií zahŕňa definovanie štruktúry databázy a jej aplikáciu. Príklad vytvorenia tabuľky:

    
    $DotApp->DB->schema(function ($schema) {
        $schema->createTable('users', function ($table) {
            $table->id();
            $table->string('name');
        });
    }, function ($result, $db, $debug) {
        echo "Tabuľka 'users' bola úspešne vytvorená.\n";
    });
        

    Výstup:

    
    Tabuľka 'users' bola úspešne vytvorená.
        

    Hromadná migrácia s transakciou (viac tabuliek):

    
    $DotApp->DB->transact(function ($db) {
        $db->q(function ($qb) {
            $qb->createTable('users', function ($schema) {
                $schema->id();
                $schema->string('name');
            });
        })->execute();
        $db->q(function ($qb) {
            $qb->createTable('posts', function ($schema) {
                $schema->id();
                $schema->integer('user_id');
                $schema->foreign('user_id');
            });
        })->execute();
    }, function ($result, $db, $debug) {
        echo "Migrácia úspešná.\n";
    });
        

    Výstup:

    
    Migrácia úspešná.
        

    10.5. Dostupné metódy pre migrácie

    Databaser ponúka nasledujúce metódy pre prácu s migráciami:

    10.5.1. schema($callback, $success, $error)

    Definuje a vykoná jednu migračnú operáciu (napr. vytvorenie tabuľky).

    Syntax: schema(callable $callback, callable $success = null, callable $error = null)

    Parametre:

    • $callback: Closure, ktorá definuje operáciu cez SchemaBuilder.
    • $success: Callback pri úspechu.
    • $error: Callback pri chybe.

    Príklad:

    
    $DotApp->DB->schema(function ($schema) {
        $schema->createTable('users', function ($table) {
            $table->id();
            $table->string('email', 100);
        });
    }, function ($result, $db, $debug) {
        echo "<p>Tabuľka vytvorená.</p>";
    });
        

    Výstup:

    
    <p>Tabuľka vytvorená.</p>
        
    10.5.2. migrate($direction, $success, $error)

    Spúšťa migráciu v smere 'up' (vytvorenie) alebo 'down' (odstránenie).

    Syntax: migrate(string $direction = 'up', callable $success = null, callable $error = null)

    Parametre:

    • $direction: 'up' alebo 'down'.
    • $success: Callback pri úspechu.
    • $error: Callback pri chybe.

    Príklad:

    
    $DotApp->DB->migrate('up', function ($result, $db, $debug) {
        echo "<p>Migrácia 'up' dokončená.</p>";
    });
        

    Výstup:

    
    <p>Migrácia 'up' dokončená.</p>
        
    10.5.3. transact($operations, $success, $error)

    Spúšťa hromadnú migráciu v rámci transakcie.

    Syntax: transact(callable $operations, callable $success = null, callable $error = null)

    Parametre:

    • $operations: Closure s viacerými migračnými operáciami.
    • $success: Callback pri úspechu.
    • $error: Callback pri chybe.

    Príklad:

    
    $DotApp->DB->transact(function ($db) {
        $db->q(function ($qb) {
            $qb->createTable('comments', function ($schema) {
                $schema->id();
                $schema->integer('post_id');
            });
        })->execute();
    }, function ($result, $db, $debug) {
        echo "<p>Hromadná migrácia úspešná.</p>";
    });
        

    Výstup:

    
    <p>Hromadná migrácia úspešná.</p>
        
    10.5.4. SchemaBuilder::createTable($table, $callback)

    Vytvorí novú tabuľku s definovanou štruktúrou.

    Syntax: createTable(string $table, callable $callback)

    Príklad:

    
    $DotApp->DB->schema(function ($schema) {
        $schema->createTable('products', function ($table) {
            $table->id();
            $table->string('name');
            $table->decimal('price', 8, 2);
        });
    });
        
    10.5.5. SchemaBuilder::alterTable($table, $callback)

    Upraví existujúcu tabuľku (napr. pridá stĺpec).

    Syntax: alterTable(string $table, callable $callback)

    Príklad:

    
    $DotApp->DB->schema(function ($schema) {
        $schema->alterTable('users', function ($table) {
            $table->addColumn('age', 'INT', null, true);
        });
    });
        
    10.5.6. SchemaBuilder::dropTable($table)

    Odstráni tabuľku.

    Syntax: dropTable(string $table)

    Príklad:

    
    $DotApp->DB->schema(function ($schema) {
        $schema->dropTable('users');
    });
        

    10.6. Praktické príklady

    Vytvorenie tabuliek s cudzím kľúčom:

    
    $DotApp->DB->transact(function ($db) {
        $db->q(function ($qb) {
            $qb->createTable('users', function ($schema) {
                $schema->id();
                $schema->string('username');
            });
        })->execute();
        $db->q(function ($qb) {
            $qb->createTable('posts', function ($schema) {
                $schema->id();
                $schema->string('title');
                $schema->integer('user_id');
                $schema->foreign('user_id', 'id', 'users');
            });
        })->execute();
    }, function ($result, $db, $debug) {
        echo "<p>Migrácia tabuliek dokončená.</p>";
    });
        

    Výstup:

    
    <p>Migrácia tabuliek dokončená.</p>
        

    Úprava tabuľky (pridanie stĺpca):

    
    $DotApp->DB->schema(function ($schema) {
        $schema->alterTable('users', function ($table) {
            $table->string('email', 100);
        });
    }, function ($result, $db, $debug) {
        echo "<p>Email stĺpec pridaný.</p>";
    });
        

    Výstup:

    
    <p>Email stĺpec pridaný.</p>
        

    Rollback migrácie:

    
    $DotApp->DB->migrate('down', function ($result, $db, $debug) {
        echo "<p>Tabuľka 'migrations' odstránená.</p>";
    });
        

    Výstup:

    
    <p>Tabuľka 'migrations' odstránená.</p>
        

    10.7. Poznámky

    • Transakcie: Používajte transact() pre hromadné migrácie, aby ste zaistili konzistenciu.
    • Podpora driverov: Syntax sa prispôsobuje podľa drivera (napr. MySQL vs. SQLite), ale niektoré funkcie (napr. ON UPDATE v Oracle) nemusia byť plne podporované.
    • Obmedzenia: Metóda migrate() aktuálne podporuje len základné operácie; pre komplexné migrácie použite schema() alebo transact().

    11. Prípadová štúdia: E-shop s ORM

    Táto kapitola predstavuje praktickú prípadovú štúdiu, ktorá ukazuje, ako použiť Databaser a jeho ORM na vytvorenie jednoduchého e-shopu. Navrhneme databázovú štruktúru, vytvoríme tabuľky, naplníme ich dátami a ukážeme, ako pracovať s dátami pomocou Entity a Collection. Súčasťou budú aj error callbacky na správu chýb, aby sa používatelia naučili robustné techniky.

    11.1. Návrh databázovej štruktúry

    Pre e-shop navrhneme tieto tabuľky:

    • users: Používatelia (zákazníci a administrátori).
    • products: Produkty v ponuke.
    • product_descriptions: Popisy produktov (jeden produkt môže mať viac popisov, napr. v rôznych jazykoch).
    • orders: Objednávky.
    • order_items: Položky v objednávkach (prepojenie produktov a objednávok).
    SQL na vytvorenie tabuliek

    Používateľ môže tieto príkazy skopírovať a spustiť v MySQL databáze:

    -- Tabuľka používateľov
    CREATE TABLE users (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(50) NOT NULL,
        email VARCHAR(100) NOT NULL UNIQUE,
        role ENUM('customer', 'admin') DEFAULT 'customer',
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    
    -- Tabuľka produktov
    CREATE TABLE products (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(100) NOT NULL,
        price DECIMAL(10, 2) NOT NULL,
        stock INT NOT NULL DEFAULT 0,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    
    -- Tabuľka popisov produktov
    CREATE TABLE product_descriptions (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        product_id BIGINT UNSIGNED NOT NULL,
        language VARCHAR(10) NOT NULL,
        description TEXT NOT NULL,
        FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
    );
    
    -- Tabuľka objednávok
    CREATE TABLE orders (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        user_id BIGINT UNSIGNED NOT NULL,
        total_price DECIMAL(10, 2) NOT NULL,
        status ENUM('pending', 'shipped', 'delivered') DEFAULT 'pending',
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
    );
    
    -- Tabuľka položiek objednávok
    CREATE TABLE order_items (
        id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        order_id BIGINT UNSIGNED NOT NULL,
        product_id BIGINT UNSIGNED NOT NULL,
        quantity INT NOT NULL DEFAULT 1,
        price DECIMAL(10, 2) NOT NULL,
        FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
        FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
    );
    
    SQL na naplnenie dát

    Tieto príkazy naplnia tabuľky ukážkovými dátami:

    -- Používatelia
    INSERT INTO users (name, email, role) VALUES
    ('Jano Novák', 'jano@example.com', 'customer'),
    ('Admin Peter', 'admin@example.com', 'admin');
    
    -- Produkty
    INSERT INTO products (name, price, stock) VALUES
    ('Tričko biele', 15.99, 50),
    ('Topánky čierne', 49.99, 20),
    ('Bunda zimná', 89.99, 10);
    
    -- Popisy produktov
    INSERT INTO product_descriptions (product_id, language, description) VALUES
    (1, 'sk', 'Pohodlné biele tričko z bavlny.'),
    (1, 'en', 'Comfortable white cotton t-shirt.'),
    (2, 'sk', 'Elegantné čierne topánky na každú príležitosť.'),
    (3, 'sk', 'Teplá zimná bunda s kapucňou.');
    
    -- Objednávky
    INSERT INTO orders (user_id, total_price, status) VALUES
    (1, 65.98, 'pending'),
    (1, 89.99, 'shipped');
    
    -- Položky objednávok
    INSERT INTO order_items (order_id, product_id, quantity, price) VALUES
    (1, 1, 2, 15.99),
    (1, 2, 1, 49.99),
    (2, 3, 1, 89.99);
    

    11.2. Implementácia v Databaser s ORM

    Predpokladáme, že Databaser je nakonfigurovaný a pripojený k databáze (viď kapitola 2). Použijeme ORM na prácu s týmito tabuľkami, vrátane správy chýb.

    11.2.1. Získanie používateľa a jeho objednávok

    Ukážeme, ako získať používateľa s jeho objednávkami a položkami.

    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first(
        function ($user, $db, $debug) {
            $user->with(['hasMany:orders']);
            $user->loadRelations(
                function ($result, $db, $debug) {
                    echo "Používateľ: {$user->name} ({$user->email})\n";
                    foreach ($user->hasMany('orders', 'user_id') as $order) {
                        echo "Objednávka #{$order->id}, Celková cena: {$order->total_price}, Stav: {$order->status}\n";
                    }
                },
                function ($error, $db, $debug) {
                    echo "Chyba pri načítaní vzťahov: {$error['error']}\n";
                }
            );
        },
        function ($error, $db, $debug) {
            echo "Chyba pri načítaní používateľa: {$error['error']}\n";
        }
    );
    
    11.2.2. Pridanie nového produktu s popisom

    Vytvoríme nový produkt a pridáme mu popis v slovenčine.

    $DotApp->DB->transact(function ($db) {
        $product = $db->newEntity();
        $product->table('products');
        $product->name = 'Šál zelený';
        $product->price = 19.99;
        $product->stock = 30;
        $product->save(
            function ($result, $db, $debug) {
                $productId = $db->insertedId();
                $description = $db->newEntity();
                $description->table('product_descriptions');
                $description->product_id = $productId;
                $description->language = 'sk';
                $description->description = 'Teplý zelený šál na zimu.';
                $description->save(
                    null,
                    function ($error, $db, $debug) {
                        echo "Chyba pri ukladaní popisu: {$error['error']}\n";
                    }
                );
            },
            function ($error, $db, $debug) {
                echo "Chyba pri ukladaní produktu: {$error['error']}\n";
            }
        );
    }, function ($result, $db, $debug) {
        echo "Produkt 'Šál zelený' pridaný s popisom.\n";
    }, function ($error, $db, $debug) {
        echo "Chyba v transakcii: {$error['error']}\n";
    });
    
    11.2.3. Zobrazenie produktu s popismi

    Získame produkt a jeho popisy pomocou hasMany.

    $product = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'products')->where('id', '=', 1);
    })->first(
        function ($product, $db, $debug) {
            $product->with('hasMany:product_descriptions');
            $product->loadRelations(
                function ($result, $db, $debug) {
                    echo "Produkt: {$product->name}, Cena: {$product->price} €\n";
                    foreach ($product->hasMany('product_descriptions', 'product_id') as $desc) {
                        echo "Popis ({$desc->language}): {$desc->description}\n";
                    }
                },
                function ($error, $db, $debug) {
                    echo "Chyba pri načítaní popisov: {$error['error']}\n";
                }
            );
        },
        function ($error, $db, $debug) {
            echo "Chyba pri načítaní produktu: {$error['error']}\n";
        }
    );
    
    11.2.4. Vytvorenie objednávky

    Vytvoríme novú objednávku pre používateľa a pridáme položky.

    $DotApp->DB->transact(function ($db) {
        $order = $db->newEntity();
        $order->table('orders');
        $order->user_id = 1;
        $order->total_price = 35.98;
        $order->status = 'pending';
        $order->save(
            function ($result, $db, $debug) {
                $orderId = $db->insertedId();
                $item = $db->newEntity();
                $item->table('order_items');
                $item->order_id = $orderId;
                $item->product_id = 1;
                $item->quantity = 2;
                $item->price = 15.99;
                $item->save(
                    function ($result, $db, $debug) {
                        $product = $db->return('ORM')->q(function ($qb) {
                            $qb->select('*', 'products')->where('id', '=', 1);
                        })->first(
                            function ($product, $db, $debug) {
                                $product->stock -= 2;
                                $product->save(
                                    null,
                                    function ($error, $db, $debug) {
                                        echo "Chyba pri aktualizácii skladu: {$error['error']}\n";
                                    }
                                );
                            },
                            function ($error, $db, $debug) {
                                echo "Chyba pri načítaní produktu: {$error['error']}\n";
                            }
                        );
                    },
                    function ($error, $db, $debug) {
                        echo "Chyba pri ukladaní položky: {$error['error']}\n";
                    }
                );
            },
            function ($error, $db, $debug) {
                echo "Chyba pri ukladaní objednávky: {$error['error']}\n";
            }
        );
    }, function ($result, $db, $debug) {
        echo "Objednávka vytvorená a sklad aktualizovaný.\n";
    }, function ($error, $db, $debug) {
        echo "Chyba v transakcii: {$error['error']}\n";
    });
    
    11.2.5. Zobrazenie objednávky s položkami

    Získame objednávku a jej položky.

    $order = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'orders')->where('id', '=', 1);
    })->first(
        function ($order, $db, $debug) {
            $order->with('hasMany:order_items');
            $order->loadRelations(
                function ($result, $db, $debug) {
                    echo "Objednávka #{$order->id}, Celková cena: {$order->total_price}, Stav: {$order->status}\n";
                    foreach ($order->hasMany('order_items', 'order_id') as $item) {
                        $db->return('ORM')->q(function ($qb) use ($item) {
                            $qb->select('name', 'products')->where('id', '=', $item->product_id);
                        })->first(
                            function ($product, $db, $debug) use ($item) {
                                echo "Položka: {$product->name}, Množstvo: {$item->quantity}, Cena: {$item->price} €\n";
                            },
                            function ($error, $db, $debug) {
                                echo "Chyba pri načítaní produktu: {$error['error']}\n";
                            }
                        );
                    }
                },
                function ($error, $db, $debug) {
                    echo "Chyba pri načítaní položiek: {$error['error']}\n";
                }
            );
        },
        function ($error, $db, $debug) {
            echo "Chyba pri načítaní objednávky: {$error['error']}\n";
        }
    );
    

    11.3. Použitie validácie

    Pridáme validáciu pre produkt pred uložením.

    $product = $DotApp->DB->newEntity();
    $product->table('products');
    $product->setRules([
        'name' => ['required', 'string', 'max:100'],
        'price' => ['required', 'numeric', 'min:0'],
        'stock' => ['integer', 'min:0']
    ]);
    $product->name = 'Dlhý názov produktu, ktorý presahuje 100 znakov a je neplatný kvôli maximálnej dĺžke';
    $product->price = -5;
    $product->stock = 10;
    $product->save(
        function ($result, $db, $debug) {
            echo "Produkt úspešne uložený.\n";
        },
        function ($error, $db, $debug) {
            echo "Validácia zlyhala: {$error['error']}\n";
        }
    );
    

    11.4. Poznámky k štúdii

    Transakcie: Použitie transact() zaisťuje konzistenciu; error callbacky informujú o zlyhaniach.

    Vzťahy: hasMany a eager loading (with()) zjednodušujú prácu s dátami, s chybovou kontrolou.

    Validácia: Pravidlá chránia pred neplatnými dátami, s jasným hlásením chýb.

    Chybová správa: error callbacky umožňujú používateľovi reagovať na problémy (napr. logovanie, upozornenia).

    12. Tipy a triky

    V tejto kapitole ponúkneme praktické rady, ako efektívne využívať Databaser v DotApp Frameworku. Zameriame sa na optimalizáciu dotazov, zabezpečenie bezpečnosti a možnosti rozšírenia.

    12.1. Optimalizácia dotazov

    Efektívne dotazy sú kľúčom k rýchlej aplikácii. Tu je niekoľko tipov:

    • Vyberajte len potrebné stĺpce: Namiesto select('*', 'users') používajte konkrétne stĺpce, napr. select('id, name', 'users'). Znižuje to objem prenesených dát.
    $DotApp->DB->q(function ($qb) {
        $qb->select('id, name', 'users')->where('age', '>', 18);
    })->execute(
        function ($result, $db, $debug) {
            var_dump($result);
        }
    );
    
    • Využívajte indexy: Pri častých filtroch v where() (napr. id, age) pridajte indexy v databáze pomocou schema():
    $DotApp->DB->schema(function ($schema) {
        $schema->alterTable('users', function ($table) {
            $table->index('age');
        });
    });
    
    • Limitujte výsledky: Používajte limit() a offset() na stránkovanie, aby ste neťahali zbytočne veľké dátové sady.
    $DotApp->DB->q(function ($qb) {
        $qb->select('*', 'users')->limit(10)->offset(20);
    })->all();
    
    • Cache pri opakovaných dotazoch: Ak máte cache driver implementovaný, využite ho na ukladanie výsledkov:
    $DotApp->DB->cache($myCacheDriver)->q(function ($qb) {
        $qb->select('*', 'users');
    })->execute(
        function ($result, $db, $debug) {
            echo "Výsledky z cache alebo DB: ";
            var_dump($result);
        }
    );
    

    12.2. Bezpečnosť (SQL Injection prevencia)

    Databaser je navrhnutý s ohľadom na bezpečnosť, no je dobré poznať osvedčené postupy:

    • Vždy používajte prepared statements: QueryBuilder automaticky escapuje hodnoty, takže nikdy nevkladajte premenné priamo do dotazu.

    Správne:

    $DotApp->DB->q(function ($qb) {
        $qb->select('*', 'users')->where('name', '=', 'Jano');
    })->execute();
    

    Nesprávne:

    $name = "Jano'; DROP TABLE users; --";
    $DotApp->DB->q(function ($qb) use ($name) {
        $qb->raw("SELECT * FROM users WHERE name = '$name'");
    })->execute(); // Nebezpečné!
    
    • Surové dotazy s RAW: Ak používate raw(), vždy zadávajte hodnoty cez bindings:
    $DotApp->DB->q(function ($qb) {
        $qb->raw('SELECT * FROM users WHERE age > ?', [18]);
    })->execute();
    
    • Validačné pravidlá v ORM: Pri ukladaní dát cez Entity nastavte pravidlá:
    $user = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users')->where('id', '=', 1);
    })->first();
    $user->setRules(['name' => 'required|string|max:50']);
    $user->name = 'Jano';
    $user->save();
    

    12.3. Rozšírenie Databaseru o vlastné drivery

    Databaser umožňuje pridať vlastné databázové drivery pomocou metódy addDriver(). Napríklad, ak chcete podporu pre neštandardnú databázu:

    Príklad vlastného drivera (simulovaný):

    $DotApp->DB->addDriver('custom', 'execute', function ($success, $error) use ($DotApp) {
        $queryData = $this->qb->getQuery();
        $query = $queryData['query'];
        $bindings = $queryData['bindings'];
        // Simulácia vlastnej logiky
        $result = ['id' => 1, 'name' => 'Test']; // Napr. volanie API alebo iného systému
        if ($result) {
            if (is_callable($success)) {
                $success($result, $this, ['query' => $query, 'bindings' => $bindings]);
            }
        } else {
            if (is_callable($error)) {
                $error(['error' => 'Custom driver failed'], $this, ['query' => $query]);
            }
        }
    });
    
    // Použitie
    $DotApp->DB->driver('custom')->q(function ($qb) {
        $qb->select('*', 'users');
    })->execute(
        function ($result, $db, $debug) {
            echo "Výsledok z vlastného drivera: ";
            var_dump($result);
        }
    );
    

    Kroky:

    • Definujte logiku pre kľúčové operácie (select_db, execute, atď.).
    • Pridajte ich cez addDriver().
    • Prepínajte na driver pomocou driver('custom').

    Toto umožňuje integrovanie Databaseru s akýmkoľvek databázovým systémom, pokiaľ implementujete potrebné metódy.

    12.4. Eager Loading vzťahov v ORM

    Pri práci s ORM môžete optimalizovať načítanie vzťahov pomocou with():

    Príklad:

    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all()->with('hasMany:posts');
    foreach ($users as $user) {
        foreach ($user->hasMany('posts', 'user_id') as $post) {
            echo "Používateľ: {$user->name}, Príspevok: {$post->title}\n";
        }
    }
    

    Výhoda: Zníži počet dotazov z N+1 na jeden hromadný dotaz.

    12.5. Hromadné operácie s Collection

    Collection v ORM ponúka metódy na hromadné spracovanie:

    $users = $DotApp->DB->return('ORM')->q(function ($qb) {
        $qb->select('*', 'users');
    })->all();
    $users->map(function ($user) {
        $user->age += 1;
        return $user;
    })->saveAll(
        function ($result, $db, $debug) {
            echo "Všetci používatelia boli aktualizovaní.\n";
        }
    );
    

    Metódy: filter(), map(), pluck(), saveAll().