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!
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".
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:
/{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
/{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
/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
/{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
/{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
/{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
/{path*}
- Wildcard for Entire Path
$dotApp->router->get('/files/{path*}', function ($request) {
return "File path: " . $request->matchData()['path'];
});
Valid: /files/images/photo.jpg
/{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
/search(?:/{query})?
- Optional Search Query
$dotApp->router->get('/search(?:/{query})?', function ($request) {
$query = $request->matchData()['query'] ?? 'empty';
return "Search: $query";
});
Valid: /search
, /search/php
/{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.
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.
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.
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.
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 QueryBuilder
a 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 DotApp
u 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
QueryBuilder
a 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 Databaser
u. 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 Databaser
u, 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 Databaser
u 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 Databaser
u, 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
QueryBuilder
a, 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 QueryBuilder
a 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 Databaser
u 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 QueryBuilder
om. 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 Databaser
u 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
Databaser
u 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 Databaser
u 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 Databaser
i, 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 QueryBuilder
a 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:
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:
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 QueryBuilder
a: 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 Databaser
om.
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 Databaser
i, 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 Databaser
u.
$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 Databaser
u, 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 Databaser
i. Obsahuje tieto hlavné prvky:
Súkromné atribúty:
$items
: Pole Entity
objektov (napr. [0 => Entity, 1 => Entity]
).
$db
: Referencia na inštanciu Databaser
u.
$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 Databaser
i 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 Databaser
i 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 Databaser
i 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 Databaser
u 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()
.