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.
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:
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:
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:
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 $dotApp->encKey in the file ./App/config.php
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:
Any before hooks are executed.
The main logic (callback, controller, or middleware) is performed.
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".
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).
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.
// 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.
$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.
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:
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.
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.
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.
$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.
// 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:
$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:
Generates a unique session key and registers PHP functions on the server side via fn().
In HTML, events (e.g., dotbridge:on(click)) and inputs (e.g., dotbridge:input) are defined and linked to PHP functions.
When an event is triggered (e.g., a click), an AJAX request is sent to /dotapp/bridge with encrypted data.
The server verifies the key, decrypts the data, checks request limits, and executes the requested PHP function.
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.
$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.
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.
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.
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.
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.
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().
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.
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.
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.
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:
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)
$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
}
);
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";
}
);
$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.
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().
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.
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.
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";
});
$DotApp->DB->schema(function ($schema) {
$schema->alterTable('users', function ($table) {
$table->string('email', 100);
});
}, function ($result, $db, $debug) {
echo "
Email stĺpec pridaný.
";
});
Výstup:
Email stĺpec pridaný.
Rollback migrácie:
$DotApp->DB->migrate('down', function ($result, $db, $debug) {
echo "
Tabuľka 'migrations' odstránená.
";
});
Výstup:
Tabuľka 'migrations' odstránená.
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
);
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.
$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á: