├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── docs
├── Database.md
├── README.md
├── Request.md
├── Response.md
├── Router.md
├── Validator.md
└── View.md
├── phpcs.xml
├── phpstan.neon
└── src
├── Cache.php
├── Config.php
├── Console.php
├── Container.php
├── Crypt.php
├── DB.php
├── Error.php
├── Event.php
├── Exceptions
├── ContainerException.php
├── CurlException.php
├── DataNotFoundException.php
├── EventException.php
├── JwtException.php
├── MikroException.php
├── PathException.php
├── ValidatorException.php
└── ViewException.php
├── Helper.php
├── Jwt.php
├── Locale.php
├── Logger.php
├── Request.php
├── Response.php
├── Router.php
├── Validator.php
└── View.php
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `mikro` will be documented in this file
4 |
5 | ## 1.1.0 - 2022-03-01
6 | - Exceptions added
7 | - `dont_report` method added to Error component
8 | - Fix on CurlError exception message
9 | - `isServerError`, `isClientError`, `isFailed`, `isOk`, `isRedirect` and `errorOnFail` methods added to Curl component
10 | - Some PHP 8 improvements
11 |
12 | ## 1.0.0 - 2022-10-01
13 | - initial release
14 | - It is not compatible with the previous version.
15 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | Please read and understand the contribution guide before creating an issue or pull request.
6 |
7 | ## Etiquette
8 |
9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code
10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be
11 | extremely unfair for them to suffer abuse or anger for their hard work.
12 |
13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
14 | world that developers are civilized and selfless people.
15 |
16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.
18 |
19 | ## Viability
20 |
21 | When requesting or submitting new features, first consider whether it might be useful to others. Open
22 | source projects are used by many developers, who may have entirely different needs to your own. Think about
23 | whether or not your feature is likely to be used by other users of the project.
24 |
25 | ## Procedure
26 |
27 | Before filing an issue:
28 |
29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
30 | - Check to make sure your feature suggestion isn't already present within the project.
31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
32 | - Check the pull requests tab to ensure that the feature isn't already in progress.
33 |
34 | Before submitting a pull request:
35 |
36 | - Check the codebase to ensure that your feature doesn't already exist.
37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
38 |
39 | ## Requirements
40 |
41 | If the project maintainer has any additional requirements, you will find them listed here.
42 |
43 | - **[PSR-12 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-12-extended-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).
44 |
45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
46 |
47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
48 |
49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.
50 |
51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
52 |
53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
54 |
55 | **Happy coding**!
56 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Yılmaz Demir
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Project in development. **Do not use** (yet)
2 |
3 | # mikro - micro approach to traditional
4 |
5 | [](https://packagist.org/packages/yidemir/mikro) [](https://packagist.org/packages/yidemir/mikro) [](https://packagist.org/packages/yidemir/mikro)
6 |
7 | This project is a tool developed to solve some tasks and requests with simple methods, rather than a framework.
8 |
9 | I tried to take this project, which I started as a hobby, one step further. There have been fundamental changes compared to the previous version.
10 |
11 | Available packages:
12 | * **Cache** - It is a simple caching structure.
13 | * **Config** - It is a simple config structure with setter and getter.
14 | * **Console** - Executes a callback according to the parameter from the command line.
15 | * **Container** - A simple service container.
16 | * **Crypt** - It encrypts and decrypts strings with OpenSSL.
17 | * **DB** - It simplifies your CRUD operations with a PDO instance.
18 | * **Event** - A simple event listener and emitter.
19 | * **Helper** - String and array helpers and more
20 | * **Jwt** - A simple JSON web token authentication structure.
21 | * **Locale** - Multi-language/localization structure
22 | * **Logger** - Basic logging
23 | * **Request** - An easy way to access PHP global request variables.
24 | * **Response** - Sends data/response to the client.
25 | * **Router** - An ultra-simple router with grouping and middleware support.
26 | * **Validator** - A simple data validation library.
27 | * **View** - A view renderer with block and template support.
28 |
29 | ## Installation
30 |
31 | You can install the package via composer:
32 |
33 | ```bash
34 | composer require yidemir/mikro
35 | ```
36 |
37 | ## Usage
38 |
39 | **Routing**
40 | ``` php
41 | Router\get('/', fn() => Response\view('home'));
42 | ```
43 |
44 | ```php
45 | Router\group('/admin', fn() => [
46 | Router\get('/', 'DashboardController::index'),
47 | Router\resource('/posts', PostController::class),
48 | Router\get('/service/status', fn() => Response\json(['status' => true], 200)
49 | ], ['AdminMiddleware::handle']);
50 |
51 | Router\files('/', __DIR__ . '/sync-directory');
52 | ```
53 |
54 | ```php
55 | Router\error(fn() => Response\html('Default 404 error', 404));
56 | ```
57 |
58 | **Database**
59 | ```php
60 | $products = DB\query('select * from products order by id desc')->fetchAll();
61 | $product = DB\query('select * from products where id=?', [$id])->fetch();
62 |
63 | DB\insert('products', ['name' => $name, 'description' => $description]);
64 | $id = DB\last_insert_id();
65 |
66 | DB\update('products', ['name' => $newName], 'where id=?', [$id]);
67 | DB\delete('products', 'where id=?', [$id]);
68 | ```
69 |
70 | **View and Templates**
71 | ```html
72 | @View\set('title', 'Page title!');
73 |
74 | @View\start('content');
75 |
Secure print: @=$message; or unsecure print @echo $message;
76 | @View\stop();
77 |
78 | @View\start('scripts');
79 |
80 | @View\push();
81 |
82 | @echo View\render('layout');
83 | ```
84 |
85 | ```html
86 |
87 |
88 |
89 |
90 |
91 |
92 | @View\get('title', 'Hey!');
93 |
94 |
95 | @View\get('content');
96 |
97 | @View\get('scripts');
98 |
99 |
100 | ```
101 |
102 | All methods and constants are documented at the source. The general documentation will be published soon.
103 |
104 | ### Testing
105 |
106 | ```bash
107 | composer test
108 | ```
109 |
110 | ### Changelog
111 |
112 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
113 |
114 | ## Contributing
115 |
116 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
117 |
118 | ### Security
119 |
120 | If you discover any security related issues, please email demiriy@gmail.com instead of using the issue tracker.
121 |
122 | ## Credits
123 |
124 | - [Yılmaz Demir](https://github.com/yidemir)
125 | - [All Contributors](../../contributors)
126 |
127 | ## License
128 |
129 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
130 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yidemir/mikro",
3 | "description": "Micro approach to traditional",
4 | "keywords": [
5 | "yidemir",
6 | "mikro",
7 | "framework",
8 | "php",
9 | "tool"
10 | ],
11 | "homepage": "https://github.com/yidemir/mikro",
12 | "license": "MIT",
13 | "type": "library",
14 | "authors": [
15 | {
16 | "name": "Yılmaz Demir",
17 | "email": "demiriy@gmail.com",
18 | "role": "Developer"
19 | }
20 | ],
21 | "require": {
22 | "php": "^8.1",
23 | "ext-curl": "*",
24 | "ext-json": "*",
25 | "ext-mbstring": "*",
26 | "ext-openssl": "*",
27 | "ext-pdo": "*",
28 | "ext-readline": "*"
29 | },
30 | "require-dev": {
31 | "phpstan/phpstan": "^1.2",
32 | "phpunit/phpunit": "^9.0"
33 | },
34 | "autoload": {
35 | "files": [
36 | "src/Cache.php",
37 | "src/Config.php",
38 | "src/Console.php",
39 | "src/Container.php",
40 | "src/Crypt.php",
41 | "src/DB.php",
42 | "src/Error.php",
43 | "src/Event.php",
44 | "src/Helper.php",
45 | "src/Jwt.php",
46 | "src/Locale.php",
47 | "src/Logger.php",
48 | "src/Request.php",
49 | "src/Response.php",
50 | "src/Router.php",
51 | "src/Validator.php",
52 | "src/View.php"
53 | ],
54 | "psr-4": {
55 | "Mikro\\Exceptions\\": "src/Exceptions"
56 | }
57 | },
58 | "autoload-dev": {
59 | "psr-4": {
60 | "Mikro\\Tests\\": "tests"
61 | }
62 | },
63 | "scripts": {
64 | "test": "vendor/bin/phpunit",
65 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage",
66 | "analyze": "vendor/bin/phpstan --memory-limit=2G"
67 | },
68 | "config": {
69 | "sort-packages": true
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/docs/Database.md:
--------------------------------------------------------------------------------
1 | # Database
2 |
3 | Setup:
4 | ```php
5 | $mikro[DB\CONNECTION] = new PDO('connection-string');
6 | ```
7 |
8 | ## Query
9 | ```php
10 | $sth = DB\query('select * from items');
11 | $items = $sth->fetchAll();
12 |
13 | $sth = DB\query('select * from items where id=:id', ['id' => 5]);
14 | $item = $sth->fetch();
15 |
16 | DB\query('insert into items (name, value) values (?, ?)', [$name, $value]);
17 | ```
18 |
19 | ## Execute
20 | ```php
21 | DB\exec('create table if not exists items ...');
22 | ```
23 |
24 | ## Insert
25 | ```php
26 | DB\insert('items', ['name' => 'value']);
27 | ```
28 |
29 | ## Update
30 | ```php
31 | DB\update('items', ['name' => 'foo', 'value' => 'bar']);
32 | DB\update('items', ['name' => 'foo', 'value' => 'bar'], 'where id=?', [$id]);
33 | ```
34 |
35 | ## Delete
36 | ```php
37 | DB\delete('items');
38 | DB\delete('items', 'where id=?', [$id]);
39 | ```
40 |
41 | ## Last Insert ID
42 | ```php
43 | DB\insert('items', ['name' => 'value']);
44 | DB\last_insert_id();
45 | ```
46 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # mikro docs
2 |
3 | - [Router](Router.md)
4 | - [Basics](Router.md#basics)
5 | - [Parameters](Router.md#route-parameters)
6 | - [Groups](Router.md#route-groups)
7 | - [Middleware](Router.md#middleware)
8 | - [Handling 404](Router.md#handling-404)
9 | - [Request](Request.md)
10 | - [Response](Response.md)
11 | - [Database](Database.md)
12 | - [View](View.md)
13 | - [Basics](View.md#basics)
14 | - [Blocks](View.md#blocks)
15 | - [Templating](View.md#templating)
16 |
--------------------------------------------------------------------------------
/docs/Request.md:
--------------------------------------------------------------------------------
1 | # Request
2 | Simple request handler
3 |
4 | ## Methods
5 |
6 | ### Method
7 | ```php
8 | Request\method(); // example result: 'GET'
9 | ```
10 |
11 | ### Request Path
12 | ```php
13 | Request\path(); // example result: '/posts/5'
14 | ```
15 |
16 | ### Request Query String
17 | ```php
18 | Request\query_string(); // example result: 'id=5&page=32'
19 | ```
20 |
21 | ### Request Query
22 | ```php
23 | Request\query(); // example result: ['id' => 5, 'page' => 32]
24 | ```
25 |
26 | ### Request Parameters
27 | ```php
28 | Request\all(); // example result: ['id' => 5, 'page' => 32]
29 | ```
30 |
31 | ### Request Parameter
32 | ```php
33 | Request\get('page'); // example result: '32'
34 | // or
35 | Request\input('page'); // example result: '32'
36 | ```
37 |
38 | ### Request Body
39 | Gets request body
40 | ```php
41 | Request\content(); // returns request body
42 | ```
43 |
44 | ### Request Body (array)
45 | Gets request body in array
46 | ```php
47 | Request\to_array(); // array
48 | ```
49 |
50 | ### Request Header
51 | ```php
52 | Request\headers(); // get all headers in array
53 | Request\header('Content-type'); // get Content-Type header
54 | ```
55 |
56 | ### Check Json Request
57 | ```php
58 | Request\wants_json(); // bool
59 | ```
60 |
61 | ### Bearer Token
62 | ```php
63 | Request\bearer_token(); // bool
64 | ```
65 |
66 | ### Only specific parameters
67 | ```php
68 | Request\only(['param1', 'param2']); // array
69 | ```
70 |
71 | ### Except specific parameters
72 | ```php
73 | Request\except(['param1', 'param2']); // array
74 | ```
75 |
--------------------------------------------------------------------------------
/docs/Response.md:
--------------------------------------------------------------------------------
1 | # Response
2 | Response handler
3 |
4 | ## HTML Response
5 | Prints and returns HTML response
6 | ```php
7 | Response\html('...');
8 |
9 | // Response\html(content: 'string', code: 200, headers: []): int
10 | ```
11 |
12 | ## JSON Response
13 | Prints and returns JSON response
14 | ```php
15 | Response\json(['message' => 'OK']);
16 |
17 | // Response\json(content: ['message' => 'OK'], code: 200, headers: []): int
18 | ```
19 |
20 | ## Plain-text Response
21 | Prints and returns plain text response
22 | ```php
23 | Response\text('plain text');
24 |
25 | // Response\text(content: 'string', code: 200, headers: []): int
26 | ```
27 |
28 | ## Rendered View Response
29 | ```php
30 | Response\view('view-file', ['parameter' => 'value']);
31 |
32 | // Response\view(file: 'view-file', data: [], code: 200, headers: []): int
33 | ```
34 |
35 | ## Redirects
36 | ```php
37 | Response\redirect('/to-path');
38 | Response\redirect('https://to-url.com');
39 | Response\redirect_back();
40 | ```
41 |
42 | ## Success Response
43 | ```php
44 | Response\ok('html response');
45 | Response\ok(['message' => 'json respnose']);
46 |
47 | // Response\ok(data: 'mixed', code: 200, headers: []): int
48 | ```
49 |
50 | ## Error Response
51 | ```php
52 | Response\error('html response'); // 500 server error
53 | Response\error(['message' => 'json respnose']); // 500 server error
54 |
55 | Response\error('404 page not found', Response\STATUS['HTTP_NOT_FOUND']);
56 | Response\error(['message' => '404 not found'], 404);
57 |
58 | // Response\error(data: 'mixed', code: 500, headers: []): int
59 | ```
60 |
--------------------------------------------------------------------------------
/docs/Router.md:
--------------------------------------------------------------------------------
1 | # Router
2 | Router is simple and powerful page handler.
3 |
4 | ## Basics
5 | Router is under the `Router` namespace.
6 |
7 | Router methods are:
8 | ```php
9 | Router\get('/', 'callback');
10 | Router\post('/save', 'callback');
11 | Router\patch('/update', 'callback');
12 | Router\put('/update', 'callback');
13 | Router\delete('/delete', 'callback');
14 | Router\any('/page', 'callback');
15 | Router\redirect('/page', '/to');
16 | Router\view('/path', 'viewfile');
17 | ```
18 |
19 | The `callback` parameter must be contain a PHP [callable](https://www.php.net/manual/en/language.types.callable.php).
20 |
21 | ### File Based Routing
22 | Converts files in the path and directory specified using the `RecursiveDirectoryIterator` to the route. For example:
23 |
24 | ```php
25 | Router\files('/', __DIR__ . '/pages');
26 | ```
27 |
28 | | File Path | Router Equivalent |
29 | |--|--|
30 | | `/pages/index.php` | `Router\get('/', 'callback');` |
31 | | `/pages/filename.php` | `Router\get('/filename', 'callback');` |
32 | | `/pages/index.post.php` | `Router\post('/', 'callback');` |
33 | | `/pages/index.put.php` | `Router\put('/', 'callback');` |
34 | | `/pages/index.patch.php` | `Router\patch('/', 'callback');` |
35 | | `/pages/index.options.php` | `Router\options('/', 'callback');` |
36 | | `/pages/index.delete.php` | `Router\delete('/', 'callback');` |
37 | | `/pages/filename.post.php` | `Router\post('/filename', 'callback');` |
38 | | `/pages/with-{param}.php` | `Router\get('/with-{param}', 'callback');` |
39 | | `/pages/with-{param}.post.php` | `Router\post('/with-{param}', 'callback');` |
40 | | `/pages/items/index.php` | `Router\get('/items', 'callback');` |
41 | | `/pages/items/index.post.php` | `Router\post('/items', 'callback');` |
42 | | `/pages/items/{id}.php` | `Router\get('/items/{id}', 'callback');` |
43 | | `/pages/items/{id}.put.php` | `Router\put('/items/{id}', 'callback');` |
44 |
45 | ## Route Parameters
46 | The Router uses regular expressions to map the routes. If you don't want to use Regex strings, you can use patterns.
47 |
48 | ```php
49 | Router\get('/items/{name}', 'ItemController::show');
50 | Router\put('/events/{id:num}', 'EventController::update');
51 | Router\get('/posts/{slug:str}', 'PostController::show');
52 | ```
53 |
54 | Available patterns:
55 | ```
56 | {parameter} => any string
57 | {parameter:num} => \d+ (digits)
58 | {parameter:str} => [\w\-_]+ (string)
59 | {parameter:any} => [^/]+ (any string, includes special chars, digits etc.)
60 | {parameter:all} => .* (any string on full uri)
61 | ```
62 |
63 | ### Getting Route Parameters
64 | To use the resolved route parameters in the callback, you must use the `parameters()` method. For example:
65 |
66 | ```php
67 | Router\get('/posts/{slug}', function () {
68 | $slug = Router\parameters('slug');
69 | });
70 | ```
71 |
72 | ## Route Groups
73 | Route Groups, provides grouping according to the specified prefix.
74 |
75 | ```php
76 | Router\get('/home', 'HomeController::index');
77 |
78 | Router\group('/admin', function () {
79 | Router\get('', 'Admin\DashboardController::index');
80 | Router\get('/stats', 'Admin\DashboardController::stats');
81 |
82 | // if you want, you can use nested groups or arrow functions
83 | Router\group('/posts', fn() => [
84 | Router\get('', 'Admin\PostController::index'),
85 | Router\get('/{id:num}', 'Admin\PostController::show'),
86 | Router\get('/create', 'Admin\PostController::create'),
87 | Router\post('', 'Admin\PostController::store'),
88 | ]);
89 | });
90 | ```
91 |
92 | ## Middleware
93 | The routes and groups includes a simple middleware support.
94 |
95 | **Usage**
96 | ```php
97 | Router\get('/', 'route_callback', ['middleware_callback']);
98 | ```
99 |
100 | **Example**
101 | ```php
102 | function middleware(\Closure $next)
103 | {
104 | // things
105 |
106 | return $next();
107 | }
108 |
109 | Router\get('/', 'callback', ['middleware']);
110 | ```
111 |
112 | ### Route Group Middleware
113 | ```php
114 | Router\group('/admin', fn() => [
115 | Router\get('/dashboard', 'DashboardController::index', ['DashboardMiddleware::handle'])
116 | ], ['AdminAuthenticationMiddleware::handle']);
117 | ```
118 |
119 | ## Handle 404
120 | It should be located at the end of the route definitions.
121 |
122 | **Usage**
123 | ```php
124 | Router\error(function () {
125 | return Response\html('404 page not found');
126 | });
127 | ```
128 |
129 |
--------------------------------------------------------------------------------
/docs/Validator.md:
--------------------------------------------------------------------------------
1 | # Validator
2 | Simple array validator
3 |
4 | ## Validation Method
5 | Validator performs validation using PHP callbacks. For example:
6 |
7 | ```php
8 | $data = ['key' => 'value', 'foo' => 'bar', 'baz' => ''];
9 |
10 | Validator\validate($data, 'key', 'isset|!empty'); // returns true, its valid
11 | Validator\validate($data, 'key', ['isset', '!empty']); // returns true, its valid
12 | Validator\validate($data, 'qux', 'isset|!empty'); // returns false, qux key not exists
13 | Validator\validate($data, 'qux', '!empty'); // returns false, qux key not exists
14 | Validator\validate($data, 'baz', '!empty'); // returns false, baz key is empty
15 | Validator\validate($data, 'baz', fn($value, $data) => strlen($value) >= 3);
16 | Validator\validate($data, 'baz', [fn($value, $data) => strlen($value) >= 3]);
17 |
18 | $callback = fn($value, $data) => true;
19 | Validator\valdiate(Request\all(), 'username', [$callback, 'ctype_alnum']);
20 | ```
21 |
22 | **Validate with parameters**
23 | ****
24 | ```php
25 | Validator\validate(Request\all(), 'email', ['isset', '!empty', 'filter_var:' . FILTER_VALIDATE_EMAIL]);
26 | ```
27 |
28 | **Validate and get all results in array**
29 | ```php
30 | Validator\validate_all(
31 | ['title' => 'foo', 'email' => 'bar'],
32 | [
33 | 'title' => 'isset|!empty|ctype_alnum',
34 | 'email' => 'isset|!empty|filter_var:' . FILTER_VALIDATE_EMAIL
35 | ]
36 | );
37 |
38 | // array(2) {
39 | // ["title"]=> bool(true)
40 | // ["email"]=> bool(false)
41 | // }
42 | ```
43 |
44 | ```php
45 | Validator\is_validate_all(
46 | ['title' => 'foo', 'email' => 'bar'],
47 | [
48 | 'title' => 'isset|!empty|ctype_alnum',
49 | 'email' => 'isset|!empty|filter_var:' . FILTER_VALIDATE_EMAIL
50 | ]
51 | ); // returns bool
52 | ```
53 |
--------------------------------------------------------------------------------
/docs/View.md:
--------------------------------------------------------------------------------
1 | # View
2 | Simple view handler
3 |
4 | ## Rendering View File
5 | ```php
6 | View\render('view-file', ['data' => 'value']); // returns string
7 | ```
8 |
9 | ## View Blocks
10 | **Set view block:**
11 | ```php
12 | View\start('content');
13 | echo 'Content
';
14 | View\stop();
15 |
16 | // or
17 |
18 | View\set('content', 'Content
');
19 | ```
20 |
21 | **Call view block:**
22 | ```php
23 | echo View\get('content');
24 | ```
25 |
26 | ## Example
27 |
28 | **index.php**
29 | ```php
30 | $mikro[View\PATH] = __DIR__ . '/views';
31 |
32 | View\render('index', ['message' => 'Hello world!']);
33 | ```
34 |
35 | **views/layout.php**
36 | ```php
37 |
38 |
39 |
40 |
41 |
42 | = View\get('title', 'Default Title') ?>
43 |
44 |
45 | = View\get('content') ?>
46 |
47 |
48 | ```
49 |
50 | **views/index.php**
51 | ```php
52 |
53 |
54 |
55 | Message: = View\e($message) ?>
56 |
57 |
58 | = View\render('layout') ?>
59 | ```
60 |
61 | ## View Templates
62 |
63 | ```php
64 | @echo 'simple php block';
65 | @='print secure string';
66 | ```
67 |
68 | rendered to:
69 |
70 | ```php
71 |
72 |
73 | ```
74 |
75 | another example:
76 |
77 | ```php
78 | @$data = [1, 2, 3, 4, 5];
79 | @foreach ($data as $number):;
80 | Number is: @=$number;
81 | @endforeach;
82 | ```
83 |
84 | rendered to:
85 |
86 | ```php
87 |
88 |
89 | Number is:
90 |
91 | ```
92 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Mikro PHPCS configuration file
4 | src
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | paths:
3 | - src
4 | level: 6
5 | checkMissingIterableValueType: false
6 |
--------------------------------------------------------------------------------
/src/Cache.php:
--------------------------------------------------------------------------------
1 | get($key);
45 |
46 | return \Memcached::RES_NOTFOUND === $memcached->getResultCode() ?
47 | null : $data;
48 | }
49 |
50 | if (! has($key)) {
51 | return null;
52 | }
53 |
54 | return \unserialize(\file_get_contents(path($key)));
55 | }
56 |
57 | /**
58 | * Defines a new cache item
59 | *
60 | * {@inheritDoc} **Example:**
61 | * ```php
62 | * Cache\set('items', $itemsData);
63 | * ```
64 | *
65 | * @throws PathException if cache path is not writeable
66 | */
67 | function set(string $key, mixed $data, int $ttl = 0): void
68 | {
69 | if ($memcached = memcached()) {
70 | $memcached->set($key, $data, $ttl);
71 |
72 | return;
73 | }
74 |
75 | if (! \is_writable($dirname = \dirname(path($key)))) {
76 | throw new PathException(\sprintf('Cache path not writable: %s', $dirname));
77 | }
78 |
79 | \file_put_contents(path($key), \serialize($data));
80 | }
81 |
82 | /**
83 | * Checks whether the cache item is defined
84 | *
85 | * {@inheritDoc} **Example:**
86 | * ```php
87 | * Cache\has('items'); // returns bool
88 | * ```
89 | */
90 | function has(string $key): bool
91 | {
92 | if ($memcached = memcached()) {
93 | $memcached->get($key);
94 |
95 | return \Memcached::RES_NOTFOUND !== $memcached->getResultCode();
96 | }
97 |
98 | return \is_readable(path($key));
99 | }
100 |
101 | /**
102 | * Deletes a defined cache item
103 |
104 | * {@inheritDoc} **Example:**
105 | * ```php
106 | * Cache\remove('items');
107 | * ```
108 | */
109 | function remove(string $key): void
110 | {
111 | if ($memcached = memcached()) {
112 | $memcached->delete($key);
113 |
114 | return;
115 | }
116 |
117 | if (! \is_writable(path())) {
118 | throw new PathException(\sprintf('Cache path not writable: %s', path()));
119 | }
120 |
121 | if (! has($key)) {
122 | return;
123 | }
124 |
125 | \unlink(path($key));
126 | }
127 |
128 | /**
129 | * Deletes all defined cache items
130 | *
131 | * {@inheritDoc} **Example:**
132 | * ```php
133 | * Cache\flush();
134 | * ```
135 | *
136 | * @throws PathException if cache path is not writable
137 | */
138 | function flush(): void
139 | {
140 | if ($memcached = memcached()) {
141 | $memcached->flush();
142 |
143 | return;
144 | }
145 |
146 | if (! \is_writable(path())) {
147 | throw new PathException(\sprintf('Cache path not writable: %s', path()));
148 | }
149 |
150 | foreach (\glob(path() . '/*.cache') as $file) {
151 | \unlink($file);
152 | }
153 | }
154 |
155 | /**
156 | * Returns memcached instance before checks
157 | *
158 | * {@inheritDoc} **Example:**
159 | * ```php
160 | * $mikro[Cache\DRIVER] = 'memcached';
161 | * $memcached = new Memcached();
162 | * $memcached->addServer('localhost', 11211);
163 | * Container\set(Memcached::class, $memcached);
164 | *
165 | * Cache\memcached(); // Memcached instance
166 | * ```
167 | *
168 | * @throws MikroException if memcached not loaded
169 | */
170 | function memcached(): \Memcached|bool
171 | {
172 | global $mikro;
173 |
174 | $driver = $mikro[DRIVER] ?? null;
175 |
176 | if ($driver !== 'memcached') {
177 | return false;
178 | }
179 |
180 | if (! \extension_loaded('memcached')) {
181 | throw new MikroException('Memcached extension is not available, please install');
182 | }
183 |
184 | if (! Container\has(\Memcached::class)) {
185 | throw new MikroException(
186 | 'In order to use the Memcached driver, you must define the Memcache driver in the Container'
187 | );
188 | }
189 |
190 | return Container\get(\Memcached::class);
191 | }
192 |
193 | /**
194 | * Remember cache with callback
195 | *
196 | * {@inheritDoc} **Example:**
197 | * ```php
198 | * $data = Cache\remember('posts', fn() => DB\query('...')->fetchAll(), 60);
199 | * ```
200 | */
201 | function remember(string $key, \Closure $callback, int $ttl = 0): mixed
202 | {
203 | if (has($key)) {
204 | return get($key);
205 | } else {
206 | set($key, $cache = $callback(), $ttl);
207 |
208 | return $cache;
209 | }
210 | }
211 |
212 | /**
213 | * Cache path constant
214 | *
215 | * {@inheritDoc} **Example:**
216 | * ```php
217 | * $mikro[Cache\PATH] = '/path/to/cache';
218 | * ```
219 | */
220 | const PATH = 'Cache\PATH';
221 |
222 | /**
223 | * Cache path constant
224 | *
225 | * {@inheritDoc} **Example:**
226 | * ```php
227 | * $mikro[Cache\DRIVER] = 'file';
228 | * // default driver is 'file'
229 | * // available drivers: file, memcached
230 | * ```
231 | */
232 | const DRIVER = 'Cache\DRIVER';
233 | }
234 |
--------------------------------------------------------------------------------
/src/Config.php:
--------------------------------------------------------------------------------
1 | [$key => $value],
22 | $value
23 | );
24 |
25 | $mikro[COLLECTION] = \array_replace_recursive($mikro[COLLECTION] ?? [], $replace);
26 | }
27 |
28 | /**
29 | * Returns the configuration value
30 | *
31 | * {@inheritDoc} **Example:**
32 | * ```php
33 | * Config\get('foo.bar');
34 | * Config\get('foo')['bar'];
35 | * ```
36 | */
37 | function get(string $key, mixed $default = null): mixed
38 | {
39 | global $mikro;
40 |
41 | if (\array_key_exists($key, $mikro[COLLECTION] ?? [])) {
42 | return $mikro[COLLECTION][$key];
43 | }
44 |
45 | return \array_reduce(
46 | \explode('.', $key),
47 | fn($config, $key) => $config[$key] ?? $default,
48 | $mikro[COLLECTION] ?? []
49 | ) ?? $default;
50 | }
51 |
52 | /**
53 | * Config collection constant
54 | *
55 | * @internal
56 | */
57 | const COLLECTION = 'Config\COLLECTION';
58 | }
59 |
--------------------------------------------------------------------------------
/src/Console.php:
--------------------------------------------------------------------------------
1 | Cache\flush());
13 | * // php console.php cache:clear
14 | *
15 | * Console\command('say:hello', function (array $args) {
16 | * if (isset($args[0])) {
17 | * return Console\write("Hello {$args[0]}!");
18 | * }
19 | *
20 | * Console\write('Hello world!');
21 | * });
22 | *
23 | * // php console.php say:hello // prints "Hello world!"
24 | * // php console.php say:hello Foo // prints "Hello Foo!"
25 | * ```
26 | */
27 | function command(string $name, callable $callback): mixed
28 | {
29 | global $argv;
30 |
31 | if (\PHP_SAPI === 'cli' && isset($argv[1]) && $argv[1] === $name) {
32 | $args = \array_values(\array_slice($argv, 2));
33 |
34 | foreach ($args as $arg) {
35 | if (\str_starts_with($arg, '-')) {
36 | $pieces = \explode('=', \preg_replace('/^-{1,2}(\w+)/', '$1', $arg));
37 |
38 | if (\str_starts_with($arg, '--')) {
39 | $args[$pieces[0]] = $pieces[1] ?? false;
40 | } else {
41 | foreach (\str_split($pieces[0]) as $letter) {
42 | $args[$letter] = false;
43 | }
44 | }
45 | }
46 | }
47 |
48 | return \call_user_func_array($callback, [$args]);
49 | }
50 |
51 | return null;
52 | }
53 |
54 | /**
55 | * Write command line
56 | *
57 | * {@inheritDoc} **Example:**
58 | * ```php
59 | * Console\write('message');
60 | * ```
61 | */
62 | function write(string $str): int
63 | {
64 | return print($str . "\n");
65 | }
66 |
67 | /**
68 | * Write command line (green color)
69 | *
70 | * {@inheritDoc} **Example:**
71 | * ```php
72 | * Console\info('message');
73 | * ```
74 | */
75 | function info(string $str): int
76 | {
77 | return write("\e[0;32m{$str}\e[0m");
78 | }
79 |
80 | /**
81 | * Write command line (red color)
82 | *
83 | * {@inheritDoc} **Example:**
84 | * ```php
85 | * Console\error('message');
86 | * ```
87 | */
88 | function error(string $str): int
89 | {
90 | return write("\e[0;31m{$str}\e[0m");
91 | }
92 |
93 | /**
94 | * Performs operations according to the input
95 | *
96 | * {@inheritDoc} **Example:**
97 | * ```php
98 | * Console\ask('How old are you?', function ($age) {
99 | * Console\info("You are $age");
100 | * });
101 | * ```
102 | */
103 | function ask(string $question, ?callable $callback = null): mixed
104 | {
105 | write($question);
106 | $line = \readline();
107 |
108 | if ($callback) {
109 | \call_user_func_array($callback, [$line]);
110 | }
111 |
112 | return $line;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Container.php:
--------------------------------------------------------------------------------
1 | new stdClass())
16 | * ```
17 | */
18 | function set(string $name, mixed $value): void
19 | {
20 | global $mikro;
21 |
22 | $mikro[COLLECTION][$name] = $value;
23 | }
24 |
25 | /**
26 | * Returns the defined container item
27 | *
28 | * {@inheritDoc} **Example:**
29 | * ```php
30 | * Container\get('variable'); // 'string'
31 | * Container\get('closure'); // Closure
32 | * ```
33 | */
34 | function get(string $name): mixed
35 | {
36 | if (! has($name)) {
37 | throw new ContainerException('Container item not exists');
38 | }
39 |
40 | global $mikro;
41 |
42 | return $mikro[COLLECTION][$name];
43 | }
44 |
45 | /**
46 | * Returns the value of the defined container item
47 | *
48 | * {@inheritDoc} **Example:**
49 | * ```php
50 | * Container\value('closure'); // stdClass
51 | * ```
52 | *
53 | * @throws ContainerException If the value is not callable
54 | */
55 | function value(string $name, array $args = []): mixed
56 | {
57 | if (! \is_callable($value = get($name))) {
58 | throw new ContainerException('Value is not callable');
59 | }
60 |
61 | return \call_user_func_array($value, $args);
62 | }
63 |
64 | /**
65 | * Defines a new container item with singleton pattern
66 | *
67 | * {@inheritDoc} **Example:**
68 | * ```php
69 | * Container\set('closure', fn() => new stdClass());
70 | * Container\value('closure') === Container\value('closure'); // true
71 | * ```
72 | */
73 | function singleton(string $name, callable $callback): void
74 | {
75 | set($name, function () use ($callback) {
76 | static $object;
77 |
78 | if ($object === null) {
79 | $object = \call_user_func($callback);
80 | }
81 |
82 | return $object;
83 | });
84 | }
85 |
86 | /**
87 | * Checks whether the container item is defined
88 | *
89 | * {@inheritDoc} **Example:**
90 | * ```php
91 | * Container\has('item'); // true or false
92 | * ```
93 | */
94 | function has(string $name): bool
95 | {
96 | global $mikro;
97 |
98 | return \array_key_exists($name, $mikro[COLLECTION] ?? []);
99 | }
100 |
101 | /**
102 | * Container collection constant
103 | *
104 | * @internal
105 | */
106 | const COLLECTION = 'Container\COLLECTION';
107 | }
108 |
--------------------------------------------------------------------------------
/src/Crypt.php:
--------------------------------------------------------------------------------
1 | fetchAll();
36 | * DB\query('select * from items where id=?', [$id])->fetch();
37 | * DB\query('insert into items (name, value) values (?, ?)', [$name, $value]);
38 | * ```
39 | */
40 | function query(string $query, array $params = []): \PDOStatement
41 | {
42 | $sth = connection()->prepare($query);
43 | $sth->execute($params);
44 |
45 | return $sth;
46 | }
47 |
48 | /**
49 | * Executes an SQL query
50 | *
51 | * {@inheritDoc} **Example:**
52 | * ```php
53 | * DB\exec('create table if not exists items ...');
54 | * ```
55 | */
56 | function exec(string $query): int|bool
57 | {
58 | return connection()->exec($query);
59 | }
60 |
61 | /**
62 | * Inserts data to the specified table
63 | *
64 | * {@inheritDoc} **Example:**
65 | * ```php
66 | * DB\insert('items', ['name' => 'foo', 'value' => 'bar']);
67 | * ```
68 | */
69 | function insert(string $table, array $data, string $queryPart = ''): \PDOStatement
70 | {
71 | $query = "INSERT INTO {$table} ";
72 | $query .= querify($data, 'insert');
73 | $query .= empty($queryPart) ? '' : " {$queryPart}";
74 |
75 | return query($query, \array_values($data));
76 | }
77 |
78 | /**
79 | * Updates the data(s) in the specified table
80 | *
81 | * {@inheritDoc} **Example:**
82 | * ```php
83 | * DB\update('items', ['name' => 'foo', 'value' => 'bar']);
84 | * DB\update('items', ['name' => 'foo', 'value' => 'bar'], 'where id=?', [$id]);
85 | * ```
86 | */
87 | function update(
88 | string $table,
89 | array $data,
90 | string $query = '',
91 | array $parameters = []
92 | ): \PDOStatement {
93 | $query = "UPDATE {$table} SET " . querify($data, 'update') . " {$query}";
94 | $params = \array_values($data);
95 |
96 | if (! empty($parameters)) {
97 | $params = \array_merge($params, $parameters);
98 | }
99 |
100 | return query($query, $params);
101 | }
102 |
103 | /**
104 | * Deletes the data(s) from the specified table
105 | *
106 | * {@inheritDoc} **Example:**
107 | * ```php
108 | * DB\delete('items');
109 | * DB\delete('items', 'where id=?', [$id]);
110 | * ```
111 | */
112 | function delete(
113 | string $table,
114 | string $query = '',
115 | array $parameters = []
116 | ): \PDOStatement {
117 | $query = "DELETE FROM {$table} {$query}";
118 |
119 | return query($query, $parameters);
120 | }
121 |
122 | /**
123 | * Get last insert id after insert query
124 | *
125 | * {@inheritDoc} **Example:**
126 | * ```php
127 | * DB\last_insert_id();
128 | * ```
129 | */
130 | function last_insert_id(): int|string|bool
131 | {
132 | $id = connection()->lastInsertId();
133 |
134 | if (\is_numeric($id)) {
135 | return (int) $id;
136 | }
137 |
138 | return $id;
139 | }
140 |
141 | /**
142 | * @internal
143 | */
144 | function querify(array $data, string $type): string
145 | {
146 | $string = '';
147 |
148 | switch ($type) {
149 | case 'insert':
150 | $arrayParameters = \array_values($data);
151 | $columnsString = \implode(',', \array_keys($data));
152 | $valuesString = \implode(',', \array_fill(0, \count($arrayParameters), '?'));
153 | $string = "({$columnsString}) VALUES ({$valuesString})";
154 | break;
155 | case 'update':
156 | foreach (\array_keys($data) as $key) {
157 | $string .= "{$key}=?,";
158 | }
159 |
160 | $string = \rtrim($string, ',');
161 | break;
162 | }
163 |
164 | return $string;
165 | }
166 |
167 | /**
168 | * DB connection constant
169 | *
170 | * {@inheritDoc} **Example:**
171 | * ```php
172 | * $mikro[DB\CONNECTION] = new PDO('...');
173 | * ```
174 | */
175 | const CONNECTION = 'DB\CONNECTION';
176 | }
177 |
--------------------------------------------------------------------------------
/src/Error.php:
--------------------------------------------------------------------------------
1 | getMessage(),
43 | $exception->getTrace()
44 | );
45 | }
46 |
47 | if (isset($mikro[EXCEPTIONS][$class])) {
48 | $specific = $mikro[EXCEPTIONS][$class]($exception);
49 |
50 | if ($specific) {
51 | return $specific;
52 | }
53 | }
54 |
55 | if ($callback) {
56 | $callback = \call_user_func_array($callback, [$exception]);
57 |
58 | if ($callback) {
59 | return $callback;
60 | }
61 | }
62 |
63 | return response($exception);
64 | });
65 | }
66 |
67 | /**
68 | * Prepare exception response
69 | *
70 | * {@inheritDoc} **Example:**
71 | * ```php
72 | * Error\handler(function (\Throwable $e) {
73 | * // handler
74 | *
75 | * Error\response($e);
76 | * });
77 | * ```
78 | */
79 | function response(\Throwable $exception): ?int
80 | {
81 | $class = \get_class($exception);
82 |
83 | if (\PHP_SAPI === 'cli') {
84 | Console\error("{$class} with message '{$exception->getMessage()}'");
85 | Console\write("in {$exception->getFile()} line {$exception->getLine()}");
86 | Console\write(\str_repeat('-', \strlen($class)));
87 | Console\write($exception->getTraceAsString());
88 |
89 | return null;
90 | }
91 |
92 | if (! \error_reporting()) {
93 | return null;
94 | }
95 |
96 | if (Request\wants_json()) {
97 | return Response\json([
98 | 'message' => $exception->getMessage(),
99 | 'data' => [
100 | 'exception' => $class,
101 | 'message' => $exception->getMessage(),
102 | 'file' => $exception->getFile(),
103 | 'line' => $exception->getLine(),
104 | 'trace' => $exception->getTrace()
105 | ]
106 | ], 500);
107 | }
108 |
109 | return Response\html('' . Helper\html('html', [
110 | Helper\html('head', [
111 | Helper\html('title', $class),
112 | Helper\html('style', '
113 | html { font: .9em/1.5 sans-serif }
114 | div.exception-wrapper { width: 50%; margin: 0 auto }
115 | h1.exception-title { color: gray; margin: 0 }
116 | h2.exception-message { margin: 0 }
117 | h3.exception-file { color: gray; margin: 0 }
118 | ')
119 | ]),
120 | Helper\html('body', Helper\html('div', [
121 | Helper\html('h1', $class)->class('exception-title'),
122 | Helper\html('h2', \htmlentities($exception->getMessage()))->class('exception-message'),
123 | Helper\html('h3', "in {$exception->getFile()} line {$exception->getLine()}")->class('exception-file'),
124 | Helper\html('pre', $exception->getTraceAsString())->class('exception-trace')
125 | ])->class('exception-wrapper'))
126 | ]), 500);
127 | }
128 |
129 | /**
130 | * Show all errors and exceptions
131 | *
132 | * {@inheritDoc} **Example:**
133 | * ```php
134 | * Error\show();
135 | * ```
136 | */
137 | function show(): void
138 | {
139 | \ini_set('display_errors', '1');
140 | \ini_set('display_startup_errors', '1');
141 | \error_reporting(\E_ALL);
142 | }
143 |
144 | /**
145 | * Hide all errors and exceptions
146 | *
147 | * {@inheritDoc} **Example:**
148 | * ```php
149 | * Error\hide();
150 | * ```
151 | */
152 | function hide(): void
153 | {
154 | \ini_set('display_errors', '0');
155 | \error_reporting(0);
156 | }
157 |
158 | /**
159 | * Handle spesific exception
160 | *
161 | * {@inheritDoc} **Example:**
162 | * ```php
163 | * Error\handle(\InvalidArgumentException::class, function (\Throwable $e) {
164 | * return Response\html($e->getMessage());
165 | * });
166 | *
167 | * throw new \InvalidArgumentException('Invalid argument');
168 | * ```
169 | */
170 | function handle(string $class, callable $callback): void
171 | {
172 | global $mikro;
173 |
174 | $mikro[EXCEPTIONS][$class] = $callback;
175 | }
176 |
177 | /**
178 | * Do not report spesific exception
179 | *
180 | * {@inheritDoc} **Example:**
181 | * ```php
182 | * Error\dont_report(Mikro\Exceptions\MikroException::class);
183 | * ```
184 | */
185 | function dont_report(string $class): void
186 | {
187 | global $mikro;
188 |
189 | $mikro[DONT_REPORT][] = $class;
190 | }
191 |
192 | /**
193 | * Log spesific exception with specific log level
194 | *
195 | * {@inheritDoc} **Example:**
196 | * ```php
197 | * Error\log(PDOException::class, Logger\LEVEL_CRITICAL);
198 | * ```
199 | */
200 | function log(string $exception = '*', string $logLevel = Logger\LEVEL_DEBUG): void
201 | {
202 | global $mikro;
203 |
204 | $mikro[LOG][$exception] = $logLevel;
205 | }
206 |
207 | /**
208 | * Handled exceptions collection constant
209 | *
210 | * @internal
211 | */
212 | const EXCEPTIONS = 'Error\EXCEPTIONS';
213 |
214 | /**
215 | * Collection of hidden exceptions
216 | *
217 | * @internal
218 | */
219 | const DONT_REPORT = 'Error\DONT_REPORT';
220 |
221 | /**
222 | * Collection of exception logger
223 | *
224 | * @internal
225 | */
226 | const LOG = 'Error\LOG';
227 | }
228 |
--------------------------------------------------------------------------------
/src/Event.php:
--------------------------------------------------------------------------------
1 | Logger\debug('Order created', $order));
15 | * Event\listen('order.created', function ($order) {
16 | * if ($order->status === 'foo') {
17 | * //
18 | * }
19 | * });
20 | * ```
21 | */
22 | function listen(string $name, callable $callback): void
23 | {
24 | global $mikro;
25 |
26 | $mikro[COLLECTION][$name][] = $callback;
27 | }
28 |
29 | /**
30 | * Emits events
31 | *
32 | * {@inheritDoc} **Example:**
33 | * ```php
34 | * Event\emit('order.created', [$order]);
35 | * ```
36 | */
37 | function emit(string $name, array $arguments = []): void
38 | {
39 | global $mikro;
40 |
41 | if (! \array_key_exists($name, $mikro[COLLECTION] ?? [])) {
42 | throw new EventException("'{$name}' named event not exists");
43 | }
44 |
45 | foreach ($mikro[COLLECTION][$name] as $event) {
46 | if (\is_callable($event)) {
47 | \call_user_func_array($event, $arguments);
48 | }
49 | }
50 | }
51 |
52 | /**
53 | * Sync listeners with files. Files must be returns callable/Closure
54 | *
55 | * {@inheritDoc} **Example:**
56 | * ```php
57 | * Event\sync(__DIR__ . '/app/events');
58 | * ```
59 | */
60 | function sync(string $path): void
61 | {
62 | foreach (\glob("{$path}/*.php") as $file) {
63 | if (\is_callable($event = require_once($file))) {
64 | listen(\basename($file, '.php'), $event);
65 | }
66 | }
67 | }
68 |
69 | /**
70 | * Event collection constant
71 | *
72 | * @internal
73 | */
74 | const COLLECTION = 'Event\COLLECTION';
75 | }
76 |
--------------------------------------------------------------------------------
/src/Exceptions/ContainerException.php:
--------------------------------------------------------------------------------
1 | getStatus() >= 400 && $this->getStatus() < 500;
39 | }
40 |
41 | public function isServerError(): bool
42 | {
43 | return $this->getStatus() >= 500;
44 | }
45 |
46 | public function isCurlError(): bool
47 | {
48 | return ! $this->getStatus();
49 | }
50 |
51 | public function getResponse(): ?string
52 | {
53 | return self::$response;
54 | }
55 |
56 | public function getStatus(): ?int
57 | {
58 | return self::$status;
59 | }
60 |
61 | public function getDetails(): array
62 | {
63 | return self::$details;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Exceptions/DataNotFoundException.php:
--------------------------------------------------------------------------------
1 | generateRandom();
19 | * ```
20 | */
21 | public static function generateRandom(int $strength): string
22 | {
23 | if (\extension_loaded('openssl')) {
24 | return \hash('sha512', \openssl_random_pseudo_bytes($strength));
25 | }
26 |
27 | return \hash('sha512', \random_bytes($strength));
28 | }
29 |
30 | /**
31 | * Validates CSRF token
32 | *
33 | * {@inheritDoc} **Example:**
34 | * ```php
35 | * if (! Helper\csrf()->validate(Request\get('__CSRF_TOKEN'))) {
36 | * throw new Exception('CSRF token does not match');
37 | * }
38 | * ```
39 | */
40 | public static function validate(?string $value = null): bool
41 | {
42 | if (! isset($_SESSION['__csrf'])) {
43 | return false;
44 | }
45 |
46 | if ($value === null) {
47 | $value = Request\get('__CSRF_TOKEN');
48 | }
49 |
50 | return \hash_equals($value, $_SESSION['__csrf']);
51 | }
52 |
53 | /**
54 | * Generate CSRF token
55 | *
56 | * {@inheritDoc} **Example:**
57 | * ```php
58 | * $token = Helper\csrf()->get();
59 | * ```
60 | */
61 | public static function get(): string
62 | {
63 | if (\session_status() !== \PHP_SESSION_ACTIVE) {
64 | throw new MikroException('Start the PHP Session first');
65 | }
66 |
67 | if (isset($_SESSION['__csrf'])) {
68 | return $_SESSION['__csrf'];
69 | }
70 |
71 | return $_SESSION['__csrf'] = self::generateRandom(32);
72 | }
73 |
74 | /**
75 | * Generate CSRF input in HTML
76 | *
77 | * {@inheritDoc} **Example:**
78 | * ```php
79 | * echo Helper\csrf()->field();
80 | * ```
81 | */
82 | public static function field(): string
83 | {
84 | return (string) html('input', '')
85 | ->name('__CSRF_TOKEN')
86 | ->type('hidden')
87 | ->value(self::get());
88 | }
89 | };
90 | }
91 |
92 | function flash(?string $message = null): mixed
93 | {
94 | $flash = new class {
95 | /**
96 | * Add flash message to session store
97 | *
98 | * {@inheritDoc} **Example:**
99 | * ```php
100 | * Helper\flash()->add('Flash message'); // add default message
101 | * Helper\flash()->add('Flash message', 'error'); // add error message
102 | * ```
103 | */
104 | public static function add(string|array $message, string $type = 'default'): string|array
105 | {
106 | if (\session_status() !== \PHP_SESSION_ACTIVE) {
107 | throw new MikroException('Start the PHP Session first');
108 | }
109 |
110 | $messages = $_SESSION['__flash_' . $type] ?? [];
111 |
112 | if (is_array($message)) {
113 | $messages = array_merge($messages, $message);
114 | } else {
115 | $messages[] = $message;
116 | }
117 |
118 | return $_SESSION['__flash_' . $type] = $messages;
119 | }
120 |
121 | /**
122 | * Get flash messages on session store
123 | *
124 | * {@inheritDoc} **Example:**
125 | * ```php
126 | * Helper\flash()->get(); // get default messages
127 | * Helper\flash()->set('error'); // get error messages
128 | * ```
129 | */
130 | public static function get(string $type = 'default'): ?array
131 | {
132 | if (\session_status() !== \PHP_SESSION_ACTIVE) {
133 | throw new MikroException('Start the PHP Session first');
134 | }
135 |
136 | $messages = $_SESSION['__flash_' . $type] ?? [];
137 | unset($_SESSION['__flash_' . $type]);
138 |
139 | return $messages;
140 | }
141 | };
142 |
143 | return $message ? $flash->add($message) : $flash;
144 | }
145 |
146 | /**
147 | * Creates a Html tag
148 | *
149 | * {@inheritDoc} **Example:**
150 | * ```php
151 | * echo Helper\html('br'); //
152 | * echo Helper\html('table') //
153 | * echo Helper\html('p', 'Hello world!'); // Hello world!
154 | * echo Helper\html('p', 'Hey!', ['class' => 'hey']); // Hey!
155 | * echo Helper\html('p', 'Hey!')->class('hey'); // Hey!
156 | * echo Helper\html('p', 'Hey!', ['PascalCase' => 'true'])->snakeCase('true')->kebabCase('false');
157 | * // Hey!
158 | *
159 | * echo Helper\html('div', [
160 | * Helper\html('a', 'Link')->href('http://url.com')->style('text-decoration:none;'),
161 | * Helper\html('a', 'Link')->href('http://url.com')->style(['text-decoration' => 'none'])
162 | * ]); // same result
163 | * ```
164 | *
165 | * New feature: Invokable tag
166 | * Always returns string, instead of \Stringable
167 | * ```php
168 | * Helper\html('div')->class('foo')('Content'); // content
169 | * Helper\html('span')('Text', ['class' => 'bg-light']); // Text
170 | * Helper\html('ul', Helper\html('li')('Item'))(); //
171 | * ```
172 | */
173 | function html(string $name, mixed $content = '', array $attributes = []): object
174 | {
175 | return new class ($name, $content, $attributes) implements \Stringable {
176 | public function __construct(
177 | protected string $name,
178 | protected mixed $content,
179 | protected array $attributes = []
180 | ) {
181 | if (\is_array($content)) {
182 | $content = \implode('', \array_map(fn($tag) => (string) $tag, $content));
183 | }
184 |
185 | $this->content = (string) $content;
186 | }
187 |
188 | public function __call(string $name, array $args): self
189 | {
190 | $name = \strtolower(\preg_replace('/(?attributes[$name] = isset($this->attributes[$name]) && $append === true ?
195 | $this->attributes[$name] . $attribute : $attribute;
196 |
197 | return $this;
198 | }
199 |
200 | public function __toString(): string
201 | {
202 | $attributes = '';
203 |
204 | foreach ($this->attributes as $key => $value) {
205 | if (\is_array($value)) {
206 | $value = \implode('', \array_map(function ($key, $value) {
207 | return "{$key}:{$value};";
208 | }, \array_keys($value), \array_values($value)));
209 | }
210 |
211 | if ($value === null) {
212 | $attributes .= "{$key} ";
213 | } elseif ($value === false) {
214 | // pass
215 | } else {
216 | $attributes .= \sprintf('%s="%s" ', $key, $value);
217 | }
218 | }
219 |
220 | $attributes = empty($attributes) ? '' : ' ' . \trim($attributes);
221 | $tag = \sprintf('<%s%s>', $this->name, $attributes);
222 |
223 | if ($this->content === "0" || $this->content) {
224 | $tag .= $this->content;
225 | }
226 |
227 | $selfCloseTags = [
228 | 'br',
229 | 'hr',
230 | 'col',
231 | 'img',
232 | 'wbr',
233 | 'area',
234 | 'base',
235 | 'link',
236 | 'meta',
237 | 'embed',
238 | 'input',
239 | 'param',
240 | 'track',
241 | 'source',
242 | ];
243 |
244 | if (! \in_array($this->name, $selfCloseTags)) {
245 | $tag .= \sprintf('%s>', $this->name);
246 | }
247 |
248 | return $tag;
249 | }
250 |
251 | public function __set(string $attribute, mixed $value): void
252 | {
253 | $this->attributes[$attribute] = $value;
254 | }
255 |
256 | public function __get(string $attribute): mixed
257 | {
258 | return $this->attributes[$attribute] ?? null;
259 | }
260 |
261 | public function __isset(string $attribute): bool
262 | {
263 | return \array_key_exists($attribute, $this->attributes);
264 | }
265 | };
266 | }
267 |
268 | /**
269 | * Creates a Curl request
270 | *
271 | * {@inheritDoc} **Example:**
272 | * ```php
273 | * $curl = Helper\curl('http://url.com');
274 | * $textResponse = (string) $curl->exec(); // string
275 | * $textResponse = $curl->text(); // string
276 | * $arrayResponse = $curl->json(); // array or null
277 | *
278 | * // Request with data
279 | * Helper\curl('http://foo.com')->data(['foo' => 'bar'])->json(); // send request with json body
280 | * Helper\curl('http://foo.com')->asForm()->data(['foo' => 'bar'])->json(); // send request as form
281 | * Helper\curl('http://foo.com')->asForm()->data(['foo' => 'bar'])->json(); // send request as form
282 | *
283 | * // Request with header
284 | * Helper\curl('http://foo.com')->headers(['Content-type' => 'application/json'])->json();
285 | *
286 | * // Request with another method
287 | * Helper\curl('http://foo.com', 'post');
288 | * Helper\curl('http://foo.com')->method('post')->json();
289 | *
290 | * // Get request details
291 | * $curl = Helper\curl('http://foo.com')->method('post')->data(['x' => 'y'])->exec();
292 | *
293 | * $curl->getInfo(); // info array
294 | * https://www.php.net/manual/function.curl-getinfo.php
295 | * $curl->getInfo('http_code'); // 200
296 | * $curl->getInfo('url'); // http://foo.com
297 | * $curl->getInfo('total_time');
298 | * $curl->getInfo('connect_time');
299 | * $curl->getInfo('redirect_url');
300 | * $curl->getInfo('request_header');
301 | * ...
302 | *
303 | * // Request with Curl options
304 | * Helper\curl('http://foo.com', 'PUT', [
305 | * \CURLOPT_FOLLOWLOCATION => true
306 | * ]);
307 | * Helper\curl('http://foo.com', 'PUT')->followLocation(); // same as above
308 | *
309 | * Helper\curl('http://foo.com')->options([
310 | * \CURLOPT_FOLLOWLOCATION => true,
311 | * \CURLOPT_URL => 'http://newurl.com'
312 | * ])->json();
313 | * ```
314 | */
315 | function curl(string $url, string $method = 'GET', array $options = []): object
316 | {
317 | return new class ($url, $method, $options) {
318 | /**
319 | * Curl resource
320 | *
321 | * @var mixed
322 | */
323 | public mixed $curl;
324 |
325 | /**
326 | * Send request as form
327 | *
328 | * @var boolean
329 | */
330 | public bool $asForm = false;
331 |
332 | /**
333 | * Throw exception on fail
334 | *
335 | * @var boolean
336 | */
337 | public bool $errorOnFail = false;
338 |
339 | /**
340 | * Response data
341 | *
342 | * @var null|string
343 | */
344 | public ?string $response = null;
345 |
346 | public function __construct(
347 | public string $url,
348 | public string $method,
349 | public array $options
350 | ) {
351 | $this->method($method);
352 | $this->options($options);
353 | $this->curl = \curl_init();
354 | }
355 |
356 | /**
357 | * Execute actual request
358 | *
359 | * {@inheritDoc} **Example:**
360 | * ```php
361 | * Helper\curl('url')->exec();
362 | * ```
363 | */
364 | public function exec(): self
365 | {
366 | $options = [
367 | \CURLOPT_URL => $this->url,
368 | \CURLOPT_RETURNTRANSFER => true
369 | ];
370 |
371 | if ($this->method !== 'GET' && ! isset($this->options[\CURLOPT_POSTFIELDS])) {
372 | $options[\CURLOPT_POSTFIELDS] = '';
373 | }
374 |
375 | if (\in_array($this->method, ['HEAD', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH'])) {
376 | $options[\CURLOPT_CUSTOMREQUEST] = $this->method;
377 | }
378 |
379 | \curl_setopt_array($this->curl, $options + $this->options);
380 |
381 | $this->response = (string) \curl_exec($this->curl);
382 |
383 | if ($this->errorOnFail) {
384 | if (! empty($this->getError())) {
385 | throw CurlException::curlError($this->getError(), [
386 | 'method' => $this->method,
387 | 'options' => $options + $this->options,
388 | 'info' => $this->getInfo(),
389 | ]);
390 | }
391 |
392 | if ($this->isClientError()) {
393 | throw CurlException::clientError($this->text(), $this->getStatus(), [
394 | 'method' => $this->method,
395 | 'options' => $options + $this->options,
396 | 'info' => $this->getInfo(),
397 | ]);
398 | }
399 |
400 | if ($this->isServerError()) {
401 | throw CurlException::clientError($this->text(), $this->getStatus(), [
402 | 'method' => $this->method,
403 | 'options' => $options + $this->options,
404 | 'info' => $this->getInfo(),
405 | ]);
406 | }
407 | }
408 |
409 | return $this;
410 | }
411 |
412 | public function __toString(): string
413 | {
414 | return $this->response ?? '';
415 | }
416 |
417 | /**
418 | * Return response as text
419 | *
420 | * {@inheritDoc} **Example:**
421 | * ```php
422 | * Helper\curl('url')->text();
423 | * ```
424 | */
425 | public function text(): string
426 | {
427 | if ($this->response === null) {
428 | $this->exec();
429 | }
430 |
431 | return (string) $this->response;
432 | }
433 |
434 | /**
435 | * Return response as array
436 | *
437 | * {@inheritDoc} **Example:**
438 | * ```php
439 | * Helper\curl('url')->json();
440 | * ```
441 | */
442 | public function json(): ?array
443 | {
444 | if ($this->response === null) {
445 | $this->exec();
446 | }
447 |
448 | try {
449 | return \json_decode($this->response, true, 512, \JSON_THROW_ON_ERROR);
450 | } catch (\JsonException) {
451 | return null;
452 | }
453 | }
454 |
455 | /**
456 | * Return curl info data
457 | *
458 | * {@inheritDoc} **Example:**
459 | * ```php
460 | * $curl = Helper\curl('url')->exec();
461 | * $status = $curl->getInfo('status_code');
462 | * ```
463 | */
464 | public function getInfo(?string $key = null): mixed
465 | {
466 | if (\curl_errno($this->curl)) {
467 | return null;
468 | }
469 |
470 | $info = \curl_getinfo($this->curl);
471 |
472 | if ($key === null) {
473 | return $info;
474 | }
475 |
476 | return $info[$key] ?? null;
477 | }
478 |
479 | /**
480 | * Set request method
481 | *
482 | * {@inheritDoc} **Example:**
483 | * ```php
484 | * Helper\curl('url')->method('POST')->exec();
485 | * ```
486 | */
487 | public function method(string $method): self
488 | {
489 | $this->method = \strtoupper($method);
490 |
491 | return $this;
492 | }
493 |
494 | /**
495 | * Set curl options
496 | *
497 | * {@inheritDoc} **Example:**
498 | * ```php
499 | * Helper\curl('url')->options(['CURLOPT_FOLLOWLOCATION' => true])->exec();
500 | * ```
501 | */
502 | public function options(array $options): self
503 | {
504 | $this->options = $options;
505 |
506 | return $this;
507 | }
508 |
509 | /**
510 | * Set request form/body data
511 | *
512 | * {@inheritDoc} **Example:**
513 | * ```php
514 | * Helper\curl('url')->data(['foo' => 'bar'])->exec();
515 | * ```
516 | */
517 | public function data(array $data): self
518 | {
519 | $data = $this->asForm ? \http_build_query($data) : \json_encode($data);
520 |
521 | switch ($this->method) {
522 | case 'GET':
523 | $this->url .= '?' . $data;
524 | break;
525 |
526 | default:
527 | $this->options[\CURLOPT_POSTFIELDS] = $data;
528 | break;
529 | }
530 |
531 | return $this;
532 | }
533 |
534 | /**
535 | * Set request headers
536 | *
537 | * {@inheritDoc} **Example:**
538 | * ```php
539 | * Helper\curl('url')->headers(['Authorization' => 'Bearer token'])->exec();
540 | * ```
541 | */
542 | public function headers(array $headers): self
543 | {
544 | $this->options[\CURLOPT_HTTPHEADER] = \array_map(
545 | fn($key, $val) => "{$key}: {$val}",
546 | \array_keys($headers),
547 | $headers
548 | );
549 |
550 | return $this;
551 | }
552 |
553 | /**
554 | * Set curl 'follow location' parameter
555 | *
556 | * {@inheritDoc} **Example:**
557 | * ```php
558 | * Helper\curl('url')->followLocation()->exec();
559 | * ```
560 | */
561 | public function followLocation(): self
562 | {
563 | $this->options[\CURLOPT_FOLLOWLOCATION] = true;
564 |
565 | return $this;
566 | }
567 |
568 | /**
569 | * Send request as form
570 | *
571 | * {@inheritDoc} **Example:**
572 | * ```php
573 | * Helper\curl('url')->asForm()->data(['foo' => 'bar'])->exec();
574 | * ```
575 | */
576 | public function asForm(): self
577 | {
578 | $this->asForm = true;
579 |
580 | return $this;
581 | }
582 |
583 | /**
584 | * Get actual response status
585 | *
586 | * {@inheritDoc} **Example:**
587 | * ```php
588 | * Helper\curl('url')->exec()->getStatus(); // 200
589 | * ```
590 | */
591 | public function getStatus(): ?int
592 | {
593 | return $this->getInfo('http_code');
594 | }
595 |
596 | /**
597 | * Get curl error
598 | *
599 | * {@inheritDoc} **Example:**
600 | * ```php
601 | * Helper\curl('url')->exec()->getError();
602 | * ```
603 | */
604 | public function getError(): string
605 | {
606 | return \curl_error($this->curl);
607 | }
608 |
609 | /**
610 | * Check is request successful
611 | *
612 | * {@inheritDoc} **Example:**
613 | * ```php
614 | * $request = Helper\curl('url')->exec();
615 | *
616 | * $request->isOk(); // bool
617 | * ```
618 | */
619 | public function isOk(): bool
620 | {
621 | return $this->getStatus() >= 200 && $this->getStatus() < 300;
622 | }
623 |
624 | /**
625 | * Check is request redirected
626 | *
627 | * {@inheritDoc} **Example:**
628 | * ```php
629 | * $request = Helper\curl('url')->exec();
630 | *
631 | * $request->isRedirect();
632 | * ```
633 | */
634 | public function isRedirect(): bool
635 | {
636 | return $this->getStatus() >= 300 && $this->getStatus() < 400;
637 | }
638 |
639 | /**
640 | * Check is request failed
641 | *
642 | * {@inheritDoc} **Example:**
643 | * ```php
644 | * $request = Helper\curl('url')->exec();
645 | *
646 | * $request->isFailed();
647 | * ```
648 | */
649 | public function isFailed(): bool
650 | {
651 | return $this->isServerError() || $this->isClientError();
652 | }
653 |
654 | /**
655 | * Check is server responded 5xx request
656 | *
657 | * {@inheritDoc} **Example:**
658 | * ```php
659 | * $request = Helper\curl('url')->exec();
660 | *
661 | * $request->isServerError();
662 | * ```
663 | */
664 | public function isServerError(): bool
665 | {
666 | return $this->getStatus() >= 500;
667 | }
668 |
669 | /**
670 | * Check is server responded 4xx request
671 | *
672 | * {@inheritDoc} **Example:**
673 | * ```php
674 | * $request = Helper\curl('url')->exec();
675 | *
676 | * $request->isClientError();
677 | * ```
678 | */
679 | public function isClientError(): bool
680 | {
681 | return $this->getStatus() >= 400 && $this->getStatus() < 500;
682 | }
683 |
684 | /**
685 | * Set request is failed, throw an exception
686 | *
687 | * {@inheritDoc} **Example:**
688 | * ```php
689 | * Helper\curl('url')->errorOnFail()->exec();
690 | * ```
691 | */
692 | public function errorOnFail(bool $errorOnFail = true): self
693 | {
694 | $this->errorOnFail = $errorOnFail;
695 |
696 | return $this;
697 | }
698 | };
699 | }
700 |
701 | /**
702 | * Returns pagination data based on the total number of items
703 | *
704 | * {@inheritDoc} **Example:**
705 | * ```php
706 | * Helper\paginate(100);
707 | * Helper\paginate(100, 2);
708 | * Helper\paginate(100, Request\input('page'), 25);
709 | * ```
710 | */
711 | function paginate(int|string $total, int|string $page = 1, int|string $limit = 10): object
712 | {
713 | return new class (
714 | \intval($total),
715 | \intval($page),
716 | \intval($limit)
717 | ) implements \ArrayAccess, \IteratorAggregate, \Countable {
718 | public array $data;
719 | public array $items = [];
720 |
721 | public function __construct(public int $total, public int $page, public int $limit)
722 | {
723 | $this->data = [
724 | 'page' => $page = $page < 1 ? 1 : $page,
725 | 'max' => $max = \ceil($total / $limit) * $limit,
726 | 'limit' => $limit,
727 | 'offset' => ($offset = ($page - 1) * $limit) > $max ? $max : $offset,
728 | 'total_page' => $totalPage = \intval($max / $limit),
729 | 'current_page' => $currentPage = $page > $totalPage ? $totalPage : $page,
730 | 'next_page' => $currentPage + 1 > $totalPage ? $totalPage : $currentPage + 1,
731 | 'previous_page' => $currentPage - 1 ?: 1,
732 | ];
733 | }
734 |
735 | /**
736 | * Get pagination data
737 | *
738 | * {@inheritDoc} **Example:**
739 | * ```php
740 | * Helper\paginate(100, Request\get('currentPage'))->getData();
741 | * ```
742 | */
743 | public function getData(): array
744 | {
745 | return $this->data;
746 | }
747 |
748 | /**
749 | * Get current page
750 | *
751 | * {@inheritDoc} **Example:**
752 | * ```php
753 | * Helper\paginate(100, Request\get('currentPage'))->getPage();
754 | * ```
755 | */
756 | public function getPage(): int
757 | {
758 | return $this->data['page'];
759 | }
760 |
761 | /**
762 | * Get pagination limit
763 | *
764 | * {@inheritDoc} **Example:**
765 | * ```php
766 | * Helper\paginate(100, Request\get('currentPage'))->getLimit();
767 | * ```
768 | */
769 | public function getLimit(): int
770 | {
771 | return $this->data['limit'];
772 | }
773 |
774 | /**
775 | * Get pagination offset
776 | *
777 | * {@inheritDoc} **Example:**
778 | * ```php
779 | * Helper\paginate(100, Request\get('currentPage'))->getOffset();
780 | * ```
781 | */
782 | public function getOffset(): int
783 | {
784 | return $this->data['offset'];
785 | }
786 |
787 | /**
788 | * Get pagination total page
789 | *
790 | * {@inheritDoc} **Example:**
791 | * ```php
792 | * $totalPage = Helper\paginate(100, Request\get('currentPage'))->getTotalPage();
793 | * $pages = range(1, $totalPage);
794 | * ```
795 | */
796 | public function getTotalPage(): int
797 | {
798 | return $this->data['total_page'];
799 | }
800 |
801 | /**
802 | * Get pagination current page
803 | *
804 | * {@inheritDoc} **Example:**
805 | * ```php
806 | * Helper\paginate(100, Request\get('currentPage'))->getCurrentPage();
807 | * ```
808 | */
809 | public function getCurrentPage(): int
810 | {
811 | return $this->data['current_page'];
812 | }
813 |
814 | /**
815 | * Get pagination next page
816 | *
817 | * {@inheritDoc} **Example:**
818 | * ```php
819 | * Helper\paginate(100, Request\get('currentPage'))->getNextPage();
820 | * ```
821 | */
822 | public function getNextPage(): int
823 | {
824 | return $this->data['next_page'];
825 | }
826 |
827 | /**
828 | * Get pagination previous page
829 | *
830 | * {@inheritDoc} **Example:**
831 | * ```php
832 | * Helper\paginate(100, Request\get('currentPage'))->getPreviousPage();
833 | * ```
834 | */
835 | public function getPreviousPage(): int
836 | {
837 | return $this->data['previous_page'];
838 | }
839 |
840 | /**
841 | * Get pagination pages data as array
842 | *
843 | * {@inheritDoc} **Example:**
844 | * ```php
845 | * Helper\paginate(100, Request\get('currentPage'))->getPages();
846 | * ```
847 | */
848 | public function getPageNumbers(): array
849 | {
850 | return \range(1, $this->getTotalPage());
851 | }
852 |
853 | /**
854 | * Set pagination items
855 | *
856 | * {@inheritDoc} **Example:**
857 | * ```php
858 | * $paginator = Helper\paginate(100, Request\get('currentPage'));
859 | * $paginator->setItems($data);
860 | * ```
861 | */
862 | public function setItems(array $items): self
863 | {
864 | $this->items = $items;
865 |
866 | return $this;
867 | }
868 |
869 | /**
870 | * Get pagination items
871 | *
872 | * {@inheritDoc} **Example:**
873 | * ```php
874 | * $paginator = Helper\paginate(100, Request\get('currentPage'));
875 | * $paginator->setItems($data);
876 | * $items = $paginator->getItems();
877 | * ```
878 | */
879 | public function getItems(): array
880 | {
881 | return $this->items;
882 | }
883 |
884 | public function getIterator(): \Traversable
885 | {
886 | return new \ArrayIterator($this->getItems());
887 | }
888 |
889 | public function count(): int
890 | {
891 | return \count($this->getItems());
892 | }
893 |
894 | public function offsetExists(mixed $offset): bool
895 | {
896 | return isset($this->items[$offset]);
897 | }
898 |
899 | public function offsetGet(mixed $offset): mixed
900 | {
901 | return $this->items[$offset];
902 | }
903 |
904 | public function offsetSet(mixed $offset, mixed $value): void
905 | {
906 | if ($offset === null) {
907 | $this->items[] = $value;
908 | } else {
909 | $this->items[$offset] = $value;
910 | }
911 | }
912 |
913 | public function offsetUnset(mixed $offset): void
914 | {
915 | unset($this->items[$offset]);
916 | }
917 | };
918 | }
919 | }
920 |
--------------------------------------------------------------------------------
/src/Jwt.php:
--------------------------------------------------------------------------------
1 | $uid,
47 | 'exp' => $expiration,
48 | 'iss' => $issuer,
49 | 'iat' => $iat ?? \time()
50 | ]);
51 | }
52 |
53 | /**
54 | * Decode Jwt token
55 | *
56 | * {@inheritDoc} **Example:**
57 | * ```php
58 | * try {
59 | * $object = Jwt\decode('Jwt token');
60 | * $uid = $object->uid;
61 | * } catch (Exception $e) {
62 | * echo 'Jwt token invalid';
63 | * }
64 | *
65 | * $object = Jwt\decode('Jwt token', false);
66 | * ```
67 | *
68 | * @throws JwtException If invalid Jwt token string
69 | * @throws JwtException If invalid segments encoding
70 | * @throws JwtException If empty algorithm
71 | * @throws JwtException If signature verification fail
72 | */
73 | function decode(string $jwt, bool $verify = true): object
74 | {
75 | $pieces = \explode('.', $jwt);
76 |
77 | if (\count($pieces) !== 3) {
78 | throw new JwtException('Wrong number of segments');
79 | }
80 |
81 | [$headerB64, $payloadB64, $cryptoB64] = $pieces;
82 |
83 | $header = \json_decode(url_safe_base64_decode($headerB64), false, 512, \JSON_THROW_ON_ERROR);
84 | $payload = \json_decode(url_safe_base64_decode($payloadB64), false, 512, \JSON_THROW_ON_ERROR);
85 |
86 | if (empty(\get_object_vars($header))) {
87 | throw new JwtException('Invalid segment encoding');
88 | }
89 |
90 | if (empty(\get_object_vars($payload))) {
91 | throw new JwtException('Invalid segment encoding');
92 | }
93 |
94 | $sign = url_safe_base64_decode($cryptoB64);
95 |
96 | if ($verify) {
97 | if (! isset($header->alg) || empty($header->alg)) {
98 | throw new JwtException('Empty algorithm');
99 | }
100 |
101 | if ($sign != sign("$headerB64.$payloadB64", $header->alg)) {
102 | throw new JwtException('Signature verification failed');
103 | }
104 | }
105 |
106 | return $payload;
107 | }
108 |
109 | /**
110 | * Creates Jwt token with object/array
111 | *
112 | * {@inheritDoc} **Example:**
113 | * ```php
114 | * Jwt\encode(['uid' => 1, 'iat' => time(), 'exp' => time() + 60, 'iss' => 'foo']);
115 | * ```
116 | */
117 | function encode(object|array $payload, string $algo = 'HS256'): string
118 | {
119 | $header = ['typ' => 'JWT', 'alg' => $algo];
120 | $segments = [];
121 | $segments[] = url_safe_base64_encode(\json_encode($header, \JSON_THROW_ON_ERROR));
122 | $segments[] = url_safe_base64_encode(\json_encode($payload, \JSON_THROW_ON_ERROR));
123 | $input = \implode('.', $segments);
124 | $signature = sign($input, $algo);
125 | $segments[] = url_safe_base64_encode($signature);
126 |
127 | return \implode('.', $segments);
128 | }
129 |
130 | /**
131 | * Signs Jwt payload
132 | *
133 | * @internal
134 | * @throws JwtException If algorithm not supported
135 | */
136 | function sign(string $message, string $method = 'HS256'): string
137 | {
138 | $methods = [
139 | 'HS256' => 'sha256',
140 | 'HS384' => 'sha384',
141 | 'HS512' => 'sha512',
142 | ];
143 |
144 | if (! isset($methods[$method])) {
145 | throw new JwtException('Algorithm not supported');
146 | }
147 |
148 | return \hash_hmac($methods[$method], $message, secret(), true);
149 | }
150 |
151 | /**
152 | * @internal
153 | */
154 | function url_safe_base64_decode(string $input): string
155 | {
156 | $remainder = \strlen($input) % 4;
157 |
158 | if ($remainder) {
159 | $padlen = 4 - $remainder;
160 | $input .= \str_repeat('=', $padlen);
161 | }
162 |
163 | return \base64_decode(\strtr($input, '-_', '+/'));
164 | }
165 |
166 | /**
167 | * @internal
168 | */
169 | function url_safe_base64_encode(string $input): string
170 | {
171 | return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
172 | }
173 |
174 | /**
175 | * Validates token
176 | *
177 | * {@inheritDoc} **Example:**
178 | * ```php
179 | * if (Jwt\validate('token')) {
180 | * echo 'It\'s ok!';
181 | * }
182 | * ```
183 | */
184 | function validate(string $token): bool
185 | {
186 | try {
187 | decode($token);
188 |
189 | return true;
190 | } catch (JwtException) {
191 | return false;
192 | }
193 | }
194 |
195 | /**
196 | * Check token is expired
197 | *
198 | * {@inheritDoc} **Example:**
199 | * ```php
200 | * if (! Jwt\expired('token')) {
201 | * echo 'It\'s ok!';
202 | * }
203 | * ```
204 | */
205 | function expired(string $token): bool
206 | {
207 | if (! validate($token)) {
208 | return false;
209 | }
210 |
211 | $payload = decode($token);
212 |
213 | if (! \property_exists($payload, 'exp')) {
214 | return false;
215 | }
216 |
217 | return $payload->exp < \time();
218 | }
219 |
220 | /**
221 | * Jwt secret constant
222 | *
223 | * {@inheritDoc} **Example:**
224 | * ```php
225 | * $mikro[Jwt\SECRET] = 'secretstring';
226 | * ```
227 | */
228 | const SECRET = 'Jwt\SECRET';
229 | }
230 |
--------------------------------------------------------------------------------
/src/Locale.php:
--------------------------------------------------------------------------------
1 | 'Merhaba Dünya'
21 | * ];
22 | * ```
23 | */
24 | function t(string $phrase): string
25 | {
26 | return data()[$phrase] ?? $phrase;
27 | }
28 |
29 | /**
30 | * Sets current locale
31 | *
32 | * {@inheritDoc} **Example:**
33 | * ```php
34 | * Locale\set('tr');
35 | * ```
36 | */
37 | function set(string $locale): void
38 | {
39 | global $mikro;
40 |
41 | $mikro[CURRENT] = $locale;
42 | }
43 |
44 | /**
45 | * Gets current locale
46 | *
47 | * {@inheritDoc} **Example:**
48 | * ```php
49 | * Locale\get(); // tr
50 | * ```
51 | */
52 | function get(): string
53 | {
54 | global $mikro;
55 |
56 | return $mikro[CURRENT] ?? ($mikro[FALLBACK] ?? 'en');
57 | }
58 |
59 | /**
60 | * Gets current localization data
61 | *
62 | * {@inheritDoc} **Example:**
63 | * ```php
64 | * Locale\data(); // array
65 | * ```
66 | *
67 | * @throw MikroException If not have mikro locale path
68 | */
69 | function data(): array
70 | {
71 | global $mikro;
72 |
73 | if (! isset($mikro[DATA][get()])) {
74 | if (! isset($mikro[PATH])) {
75 | throw new MikroException('Please specify the locale path');
76 | }
77 |
78 | $path = $mikro[PATH] . \DIRECTORY_SEPARATOR . get() . '.php';
79 | $mikro[DATA][get()] = \is_file($path) ? (array) require($path) : [];
80 | }
81 |
82 | return $mikro[DATA][get()];
83 | }
84 |
85 | /**
86 | * Localization files path constant
87 | *
88 | * {@inheritDoc} **Example:**
89 | * ```php
90 | * $mikro[Locale\PATH] = __DIR__ . '/languages';
91 | * ```
92 | */
93 | const PATH = 'Locale\PATH';
94 |
95 | /**
96 | * Current language constant
97 | *
98 | * {@inheritDoc} **Example:**
99 | * ```php
100 | * $mikro[Locale\CURRENT] = 'tr';
101 | * ```
102 | */
103 | const CURRENT = 'Locale\CURRENT';
104 |
105 | /**
106 | * Fallback language constant
107 | *
108 | * {@inheritDoc} **Example:**
109 | * ```php
110 | * $mikro[Locale\FALLBACK] = 'en';
111 | * ```
112 | */
113 | const FALLBACK = 'Locale\FALLBACK';
114 |
115 | /**
116 | * @internal
117 | */
118 | const DATA = 'Locale\DATA';
119 | }
120 |
--------------------------------------------------------------------------------
/src/Logger.php:
--------------------------------------------------------------------------------
1 | 'data']);
95 | * ```
96 | */
97 | function emergency(string $message, array|object|null $context = null): void
98 | {
99 | log(LEVEL_EMERGENCY, $message, $context);
100 | }
101 |
102 | /**
103 | * Writes to log file with 'alert' level
104 | *
105 | * {@inheritDoc} **Example:**
106 | * ```php
107 | * Logger\alert('log message', ['log' => 'data']);
108 | * ```
109 | */
110 | function alert(string $message, array|object|null $context = null): void
111 | {
112 | log(LEVEL_ALERT, $message, $context);
113 | }
114 |
115 | /**
116 | * Writes to log file with 'critical' level
117 | *
118 | * {@inheritDoc} **Example:**
119 | * ```php
120 | * Logger\critical('log message', ['log' => 'data']);
121 | * ```
122 | */
123 | function critical(string $message, array|object|null $context = null): void
124 | {
125 | log(LEVEL_CRITICAL, $message, $context);
126 | }
127 |
128 | /**
129 | * Writes to log file with 'error' level
130 | *
131 | * {@inheritDoc} **Example:**
132 | * ```php
133 | * Logger\error('log message', ['log' => 'data']);
134 | * ```
135 | */
136 | function error(string $message, array|object|null $context = null): void
137 | {
138 | log(LEVEL_ERROR, $message, $context);
139 | }
140 |
141 | /**
142 | * Writes to log file with 'warning' level
143 | *
144 | * {@inheritDoc} **Example:**
145 | * ```php
146 | * Logger\warning('log message', ['log' => 'data']);
147 | * ```
148 | */
149 | function warning(string $message, array|object|null $context = null): void
150 | {
151 | log(LEVEL_WARNING, $message, $context);
152 | }
153 |
154 | /**
155 | * Writes to log file with 'notice' level
156 | *
157 | * {@inheritDoc} **Example:**
158 | * ```php
159 | * Logger\notice('log message', ['log' => 'data']);
160 | * ```
161 | */
162 | function notice(string $message, array|object|null $context = null): void
163 | {
164 | log(LEVEL_NOTICE, $message, $context);
165 | }
166 |
167 | /**
168 | * Writes to log file with 'info' level
169 | *
170 | * {@inheritDoc} **Example:**
171 | * ```php
172 | * Logger\info('log message', ['log' => 'data']);
173 | * ```
174 | */
175 | function info(string $message, array|object|null $context = null): void
176 | {
177 | log(LEVEL_INFO, $message, $context);
178 | }
179 |
180 | /**
181 | * Writes to log file with 'debug' level
182 | *
183 | * {@inheritDoc} **Example:**
184 | * ```php
185 | * Logger\debug('log message', ['log' => 'data']);
186 | * ```
187 | */
188 | function debug(string $message, array|object|null $context = null): void
189 | {
190 | log(LEVEL_DEBUG, $message, $context);
191 | }
192 |
193 | /**
194 | * Constant of emergency level
195 | */
196 | const LEVEL_EMERGENCY = 'emergency';
197 |
198 | /**
199 | * Constant of alert level
200 | */
201 | const LEVEL_ALERT = 'alert';
202 |
203 | /**
204 | * Constant of critical level
205 | */
206 | const LEVEL_CRITICAL = 'critical';
207 |
208 | /**
209 | * Constant of error level
210 | */
211 | const LEVEL_ERROR = 'error';
212 |
213 | /**
214 | * Constant of warning level
215 | */
216 | const LEVEL_WARNING = 'warning';
217 |
218 | /**
219 | * Constant of notice level
220 | */
221 | const LEVEL_NOTICE = 'notice';
222 |
223 | /**
224 | * Constant of info level
225 | */
226 | const LEVEL_INFO = 'info';
227 |
228 | /**
229 | * Constant of debug level
230 | */
231 | const LEVEL_DEBUG = 'debug';
232 |
233 | /**
234 | * Logger path constant
235 | *
236 | * {@inheritDoc} **Example:**
237 | * ```php
238 | * $mikro[Logger\PATH] = '/path/to/logs';
239 | * ```
240 | */
241 | const PATH = 'Logger\PATH';
242 |
243 | /**
244 | * Logger type constant
245 | *
246 | * {@inheritDoc} **Example:**
247 | * ```php
248 | * $mikro[Logger\TYPE] = 'daily';
249 | * $mikro[Logger\TYPE] = 'single';
250 | * ```
251 | */
252 | const TYPE = 'Logger\TYPE';
253 | }
254 |
--------------------------------------------------------------------------------
/src/Request.php:
--------------------------------------------------------------------------------
1 | 5, 'order' => 'title']
62 | * ```
63 | */
64 | function query(): array
65 | {
66 | \parse_str(query_string(), $query);
67 |
68 | return $query;
69 | }
70 |
71 | /**
72 | * Gets all request data
73 | *
74 | * {@inheritDoc} **Example:**
75 | * ```php
76 | * Request\all(); // array ['foo' => 5, 'bar' => 'baz']
77 | * ```
78 | */
79 | function all(): array
80 | {
81 | return \array_merge($_REQUEST, $_FILES, to_array());
82 | }
83 |
84 | /**
85 | * Gets request data
86 | *
87 | * {@inheritDoc} **Example:**
88 | * ```php
89 | * Request\get('foo'); // string 'bar'
90 | * Request\get('value', 'default'); // string 'default'
91 | * ```
92 | */
93 | function get(string $key, mixed $default = null): mixed
94 | {
95 | return $_REQUEST[$key] ?? $_FILES[$key] ?? to_array()[$key] ?? $default;
96 | }
97 |
98 | /**
99 | * Gets request data
100 | *
101 | * {@inheritDoc} **Example:**
102 | * ```php
103 | * Request\input('foo'); // string 'bar'
104 | * Request\input('value', 'default'); // string 'default'
105 | * ```
106 | */
107 | function input(string $key, mixed $default = null): mixed
108 | {
109 | return get($key, $default);
110 | }
111 |
112 | /**
113 | * Gets request body
114 | */
115 | function content(): string
116 | {
117 | return \file_get_contents('php://input');
118 | }
119 |
120 | /**
121 | * Parse request body to array
122 | *
123 | * {@inheritDoc} **Example:**
124 | * ```php
125 | * Request\to_array(); // array
126 | * ```
127 | */
128 | function to_array(): array
129 | {
130 | $content = content();
131 |
132 | if (header('Content-Type') === 'application/x-www-form-urlencoded') {
133 | \parse_str($content, $array);
134 | } else {
135 | $array = (array) \json_decode($content);
136 | }
137 |
138 | return $array;
139 | }
140 |
141 | /**
142 | * Gets header
143 | *
144 | * {@inheritDoc} **Example:**
145 | * ```php
146 | * Request\header('Content-type'); // string 'text/html'
147 | * ```
148 | */
149 | function header(string $key, mixed $default = null): mixed
150 | {
151 | return headers()[$key] ?? $default;
152 | }
153 |
154 | /**
155 | * Gets all header data
156 | *
157 | * {@inheritDoc} **Example:**
158 | * ```php
159 | * Request\headers(); // array ['Content-type' => '..']
160 | * ```
161 | */
162 | function headers(): array
163 | {
164 | return \function_exists('getallheaders') ? \getallheaders() : [];
165 | }
166 |
167 | /**
168 | * Determine if the current request is asking for JSON.
169 | *
170 | * {@inheritDoc} **Example:**
171 | * ```php
172 | * Request\wants_json(); // bool
173 | * ```
174 | */
175 | function wants_json(): bool
176 | {
177 | $header = header('Accept', '');
178 | $pieces = \explode(',', $header);
179 | $first = $pieces[0] ?? '';
180 |
181 | return \str_contains($first, '/json') || \str_contains($first, '+json');
182 | }
183 |
184 | /**
185 | * Get bearer token.
186 | *
187 | * {@inheritDoc} **Example:**
188 | * ```php
189 | * Request\bearer_token(); // token string
190 | * ```
191 | */
192 | function bearer_token(): ?string
193 | {
194 | $auth = header('Authorization');
195 |
196 | return $auth ? \preg_replace('/^Bearer /', '', $auth) : null;
197 | }
198 |
199 | /**
200 | * Returns certain parameters.
201 | *
202 | * {@inheritDoc} **Example:**
203 | * ```php
204 | * Request\only(['param1', 'param2']); // array
205 | * ```
206 | */
207 | function only(array $keys): array
208 | {
209 | return \array_intersect_key(all(), \array_flip($keys));
210 | }
211 |
212 | /**
213 | * Returns it by excluding certain parameters.
214 | *
215 | * {@inheritDoc} **Example:**
216 | * ```php
217 | * Request\except(['param3', 'param2']); // array
218 | * ```
219 | */
220 | function except(array $keys): array
221 | {
222 | return \array_diff_key(all(), \array_flip($keys));
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/src/Response.php:
--------------------------------------------------------------------------------
1 | 'http://foo']);
16 | * ```
17 | */
18 | function output(
19 | ?string $content = null,
20 | int $code = STATUS['HTTP_OK'],
21 | array $headers = []
22 | ): int {
23 | foreach ($headers as $key => $value) {
24 | header($key, $value);
25 | }
26 |
27 | \http_response_code($code);
28 |
29 | return print($content);
30 | }
31 |
32 | /**
33 | * Output a html response
34 | *
35 | * {@inheritDoc} **Example:**
36 | * ```php
37 | * Response\html('Hello world!');
38 | * Response\html('404 page not found!', Response\STATUS['HTTP_NOT_FOUND']);
39 | * Response\html('Error!', Response\STATUS['HTTP_INTERNAL_SERVER_ERROR']);
40 | * ```
41 | */
42 | function html(
43 | string $content,
44 | int $code = STATUS['HTTP_OK'],
45 | array $headers = []
46 | ): int {
47 | if (! \array_key_exists('Content-Type', $headers)) {
48 | $headers['Content-Type'] = 'text/html;charset=utf-8';
49 | }
50 |
51 | return output($content, $code, $headers);
52 | }
53 |
54 | /**
55 | * Output a json response
56 | *
57 | * {@inheritDoc} **Example:**
58 | * ```php
59 | * Response\json(['status' => true]);
60 | * Response\json(['status' => false], Response\STATUS['HTTP_NOT_FOUND']);
61 | * Response\json(['status' => false], Response\STATUS['HTTP_INTERNAL_SERVER_ERROR']);
62 | * ```
63 | */
64 | function json(
65 | mixed $content,
66 | int $code = STATUS['HTTP_OK'],
67 | array $headers = []
68 | ): int {
69 | if (! \array_key_exists('Content-Type', $headers)) {
70 | $headers['Content-Type'] = 'application/json;charset=utf-8';
71 | }
72 |
73 | $json = (string) \json_encode($content);
74 |
75 | return output($json, $code, $headers);
76 | }
77 |
78 | /**
79 | * Output a text response
80 | *
81 | * {@inheritDoc} **Example:**
82 | * ```php
83 | * Response\text('Hello world!');
84 | * Response\text('404 page not found!', Response\STATUS['HTTP_NOT_FOUND']);
85 | * Response\text('Error!', Response\STATUS['HTTP_INTERNAL_SERVER_ERROR']);
86 | * ```
87 | */
88 | function text(
89 | string $content,
90 | int $code = STATUS['HTTP_OK'],
91 | array $headers = []
92 | ): int {
93 | if (! \array_key_exists('Content-Type', $headers)) {
94 | $headers['Content-Type'] = 'text/plain;charset=utf-8';
95 | }
96 |
97 | return output($content, $code, $headers);
98 | }
99 |
100 | /**
101 | * Redirect response
102 | *
103 | * {@inheritDoc} **Example:**
104 | * ```php
105 | * Response\redirect('url');
106 | * Response\redirect('url', STATUS['HTTP_PERMANENTLY_REDIRECT']);
107 | * ```
108 | */
109 | function redirect(string $to, int $code = STATUS['HTTP_MOVED_PERMANENTLY']): void
110 | {
111 | \http_response_code($code);
112 |
113 | header('Location', $to);
114 | }
115 |
116 | /**
117 | * Redirect response to referer url
118 | *
119 | * {@inheritDoc} **Example:**
120 | * ```php
121 | * Response\redirect_back();
122 | * ```
123 | */
124 | function redirect_back(int $code = STATUS['HTTP_MOVED_PERMANENTLY']): void
125 | {
126 | \http_response_code($code);
127 | $referer = $_SERVER['HTTP_REFERER'] ?? '/';
128 |
129 | header('Location', $referer);
130 | }
131 |
132 | /**
133 | * Render view and output html response
134 | *
135 | * {@inheritDoc} **Example:**
136 | * ```php
137 | * Response\view('view_file');
138 | * Response\view('view_file', ['data' => 'foo']);
139 | * Response\view('errors/404', ['data' => 'foo'], STATUS['HTTP_NOT_FOUND']);
140 | * Response\view('errors/500', [], STATUS['HTTP_INTERNAL_SERVER_ERROR'], [...$headers]);
141 | * ```
142 | */
143 | function view(
144 | string $file,
145 | array $data = [],
146 | int $code = STATUS['HTTP_OK'],
147 | array $headers = []
148 | ): int {
149 | return html(View\render($file, $data), $code, $headers);
150 | }
151 |
152 | /**
153 | * Send header with key/value
154 | *
155 | * {@inheritDoc} **Example:**
156 | * ```php
157 | * Response\header('Content-type', 'text/html');
158 | * Response\header('Location', 'url');
159 | * ```
160 | */
161 | function header(string $key, string $value, mixed ...$args): void
162 | {
163 | \header(\sprintf('%s:%s', $key, $value), ...$args);
164 | }
165 |
166 | /**
167 | * Output a success response (status code: 200)
168 | *
169 | * {@inheritDoc} **Example:**
170 | * ```php
171 | * Response\ok(['response_type' => 'JSON']);
172 | * Response\ok('Response type: HTML');
173 | * ```
174 | */
175 | function ok(mixed $data, int $code = STATUS['HTTP_OK'], array $headers = []): int
176 | {
177 | if (\is_array($data) || \is_object($data)) {
178 | return json($data, $code, $headers);
179 | }
180 |
181 | return html($data, $code, $headers);
182 | }
183 |
184 | /**
185 | * Output a fail response (status code: 500)
186 | *
187 | * {@inheritDoc} **Example:**
188 | * ```php
189 | * Response\error(['response_type' => 'JSON']);
190 | * Response\error('Response type: HTML');
191 | * Response\error('error details', Response\STATUS['HTTP_NOT_FOUND']);
192 | * ```
193 | */
194 | function error(mixed $data, int $code = STATUS['HTTP_INTERNAL_SERVER_ERROR'], array $headers = []): int
195 | {
196 | if (\is_array($data) || \is_object($data)) {
197 | return json($data, $code, $headers);
198 | }
199 |
200 | return html($data, $code, $headers);
201 | }
202 |
203 | /**
204 | * Http status codes
205 | *
206 | * {@inheritDoc} **Example:**
207 | * ```php
208 | * Response\json($data, Response\STATUS['HTTP_OK']);
209 | * Response\html($data, Response\STATUS['HTTP_NOT_FOUND']);
210 | * Response\view('pages/error', ['error' => $e], Response\STATUS['HTTP_INTERNAL_SERVER_ERROR']);
211 | * ```
212 | */
213 | const STATUS = [
214 | 'HTTP_CONTINUE' => 100,
215 | 'HTTP_SWITCHING_PROTOCOLS' => 101,
216 | 'HTTP_PROCESSING' => 102,
217 | 'HTTP_EARLY_HINTS' => 103,
218 | 'HTTP_OK' => 200,
219 | 'HTTP_CREATED' => 201,
220 | 'HTTP_ACCEPTED' => 202,
221 | 'HTTP_NON_AUTHORITATIVE_INFORMATION' => 203,
222 | 'HTTP_NO_CONTENT' => 204,
223 | 'HTTP_RESET_CONTENT' => 205,
224 | 'HTTP_PARTIAL_CONTENT' => 206,
225 | 'HTTP_MULTI_STATUS' => 207,
226 | 'HTTP_ALREADY_REPORTED' => 208,
227 | 'HTTP_IM_USED' => 226,
228 | 'HTTP_MULTIPLE_CHOICES' => 300,
229 | 'HTTP_MOVED_PERMANENTLY' => 301,
230 | 'HTTP_FOUND' => 302,
231 | 'HTTP_SEE_OTHER' => 303,
232 | 'HTTP_NOT_MODIFIED' => 304,
233 | 'HTTP_USE_PROXY' => 305,
234 | 'HTTP_RESERVED' => 306,
235 | 'HTTP_TEMPORARY_REDIRECT' => 307,
236 | 'HTTP_PERMANENTLY_REDIRECT' => 308,
237 | 'HTTP_BAD_REQUEST' => 400,
238 | 'HTTP_UNAUTHORIZED' => 401,
239 | 'HTTP_PAYMENT_REQUIRED' => 402,
240 | 'HTTP_FORBIDDEN' => 403,
241 | 'HTTP_NOT_FOUND' => 404,
242 | 'HTTP_METHOD_NOT_ALLOWED' => 405,
243 | 'HTTP_NOT_ACCEPTABLE' => 406,
244 | 'HTTP_PROXY_AUTHENTICATION_REQUIRED' => 407,
245 | 'HTTP_REQUEST_TIMEOUT' => 408,
246 | 'HTTP_CONFLICT' => 409,
247 | 'HTTP_GONE' => 410,
248 | 'HTTP_LENGTH_REQUIRED' => 411,
249 | 'HTTP_PRECONDITION_FAILED' => 412,
250 | 'HTTP_REQUEST_ENTITY_TOO_LARGE' => 413,
251 | 'HTTP_REQUEST_URI_TOO_LONG' => 414,
252 | 'HTTP_UNSUPPORTED_MEDIA_TYPE' => 415,
253 | 'HTTP_REQUESTED_RANGE_NOT_SATISFIABLE' => 416,
254 | 'HTTP_EXPECTATION_FAILED' => 417,
255 | 'HTTP_I_AM_A_TEAPOT' => 418,
256 | 'HTTP_MISDIRECTED_REQUEST' => 421,
257 | 'HTTP_UNPROCESSABLE_ENTITY' => 422,
258 | 'HTTP_LOCKED' => 423,
259 | 'HTTP_FAILED_DEPENDENCY' => 424,
260 | 'HTTP_TOO_EARLY' => 425,
261 | 'HTTP_UPGRADE_REQUIRED' => 426,
262 | 'HTTP_PRECONDITION_REQUIRED' => 428,
263 | 'HTTP_TOO_MANY_REQUESTS' => 429,
264 | 'HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE' => 431,
265 | 'HTTP_UNAVAILABLE_FOR_LEGAL_REASONS' => 451,
266 | 'HTTP_INTERNAL_SERVER_ERROR' => 500,
267 | 'HTTP_NOT_IMPLEMENTED' => 501,
268 | 'HTTP_BAD_GATEWAY' => 502,
269 | 'HTTP_SERVICE_UNAVAILABLE' => 503,
270 | 'HTTP_GATEWAY_TIMEOUT' => 504,
271 | 'HTTP_VERSION_NOT_SUPPORTED' => 505,
272 | 'HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL' => 506,
273 | 'HTTP_INSUFFICIENT_STORAGE' => 507,
274 | 'HTTP_LOOP_DETECTED' => 508,
275 | 'HTTP_NOT_EXTENDED' => 510,
276 | 'HTTP_NETWORK_AUTHENTICATION_REQUIRED' => 511,
277 | ];
278 | }
279 |
--------------------------------------------------------------------------------
/src/Router.php:
--------------------------------------------------------------------------------
1 | [new HomeController(), 'index']());
22 | * Router\map('GET|POST|PUT', '/posts/{id:num}', fn() => print('Post ID: ' . Router\parameters('id')));
23 | * ```
24 | */
25 | function map(
26 | array|string $methods,
27 | string $path,
28 | mixed $callback,
29 | array|string $middleware = []
30 | ): void {
31 | global $mikro;
32 |
33 | if (\is_string($methods)) {
34 | $methods = \explode('|', $methods);
35 | }
36 |
37 | $path = ($mikro[PREFIX] ?? '') . $path;
38 |
39 | if (\is_string($middleware)) {
40 | $middleware = \explode('|', $middleware);
41 | }
42 |
43 | $middleware = \array_merge($mikro[MIDDLEWARE] ?? [], $middleware);
44 | $requestPath = \rawurldecode(\rtrim(Request\path(), '/') ?: '/');
45 |
46 | if (\in_array(Request\method(), $methods) && $requestPath === $path) {
47 | goto found;
48 | }
49 |
50 | $path = \rtrim(parse_path($path), '/');
51 |
52 | if (
53 | \in_array(Request\method(), $methods) &&
54 | (\preg_match(\sprintf('@^%s$@i', $path), $requestPath, $params) >= 1) &&
55 | ($mikro[FOUND] ?? null) !== true
56 | ) {
57 | found:
58 | $mikro[FOUND] = true;
59 |
60 | if (isset($params)) {
61 | \array_shift($params);
62 | $mikro[PARAMETERS] = $params;
63 | }
64 |
65 | $result = \array_reduce(\array_reverse($middleware), function ($stack, $item) {
66 | return function () use ($stack, $item) {
67 | return $item($stack);
68 | };
69 | }, $callback);
70 |
71 | \call_user_func($result);
72 | }
73 | }
74 |
75 | /**
76 | * Parse route parameters
77 | *
78 | * @internal
79 | */
80 | function parse_path(string $path): string
81 | {
82 | if (\preg_match('/(\/{.*}\?)/i', $path, $matches)) {
83 | foreach (\range(1, \count($matches)) as $match) {
84 | $path = \preg_replace('/\/({.*}\?)/', '/?$1', $path);
85 | }
86 | }
87 |
88 | \preg_replace_callback('/[\[{\(].*[\]}\)]/U', function ($match) use (&$path): string {
89 | $match = \str_replace(['{', '}'], '', $match[0]);
90 |
91 | if (\str_contains($match, ':')) {
92 | [$name, $type] = \explode(':', $match, 2);
93 | } else {
94 | $name = $match;
95 | $type = 'any';
96 | }
97 |
98 | $patterns = [
99 | 'num' => '(?\d+)',
100 | 'str' => '(?[\w\-_]+)',
101 | 'any' => '(?[^/]+)',
102 | 'all' => '(?.*)',
103 | ];
104 | $replaced = \str_replace('name', $name, ($patterns[$type] ?? $patterns['any']));
105 | $path = \str_replace("{{$name}:$type}", $replaced, $path);
106 | $path = \str_replace("{{$name}}", $replaced, $path);
107 |
108 | return $path;
109 | }, $path);
110 |
111 | return $path;
112 | }
113 |
114 | /**
115 | * Maps the GET route
116 | *
117 | * {@inheritDoc} **Example:**
118 | * ```php
119 | * Router\get('/', 'callback');
120 | * Router\get('/', 'callback', $middlewareArray);
121 | * Router\get('/', function () {
122 | * return Response\view('home');
123 | * });
124 | * ```
125 | */
126 | function get(string $path, mixed $callback, array|string $middleware = []): void
127 | {
128 | map('GET', $path, $callback, $middleware);
129 | }
130 |
131 | /**
132 | * Maps the POST route
133 | *
134 | * {@inheritDoc} **Example:**
135 | * ```php
136 | * Router\post('/save', 'callback');
137 | * Router\post('/', 'callback', $middlewareArray);
138 | * Router\post('/posts', function () {
139 | * return DB\insert('posts', ['title' => Request\get('title')]);
140 | * });
141 | * ```
142 | *
143 | */
144 | function post(string $path, mixed $callback, array|string $middleware = []): void
145 | {
146 | map('POST', $path, $callback, $middleware);
147 | }
148 |
149 | /**
150 | * Maps the PATCH route
151 | *
152 | * {@inheritDoc} **Example:**
153 | * ```php
154 | * Router\patch('/update', 'callback');
155 | * Router\patch('/update', 'callback', $middlewareArray);
156 | * ```
157 | */
158 | function patch(string $path, mixed $callback, array|string $middleware = []): void
159 | {
160 | map('PATCH', $path, $callback, $middleware);
161 | }
162 |
163 | /**
164 | * Maps the PUT route
165 | *
166 | * {@inheritDoc} **Example:**
167 | * ```php
168 | * Router\put('/update', 'callback');
169 | * Router\put('/update', 'callback', $middlewareArray);
170 | * ```
171 | */
172 | function put(string $path, mixed $callback, array|string $middleware = []): void
173 | {
174 | map('PUT', $path, $callback, $middleware);
175 | }
176 |
177 | /**
178 | * Maps the DELETE route
179 | *
180 | * {@inheritDoc} **Example:**
181 | * ```php
182 | * Router\delete('/destroy', 'callback');
183 | * Router\delete('/destroy', 'callback', $middlewareArray);
184 | * ```
185 | */
186 | function delete(string $path, mixed $callback, array|string $middleware = []): void
187 | {
188 | map('DELETE', $path, $callback, $middleware);
189 | }
190 |
191 | /**
192 | * Maps the OPTIONS route
193 | *
194 | * {@inheritDoc} **Example:**
195 | * ```php
196 | * Router\options('/', 'callback');
197 | * Router\options('/', 'callback', $middlewareArray);
198 | * ```
199 | */
200 | function options(string $path, mixed $callback, array|string $middleware = []): void
201 | {
202 | map('OPTIONS', $path, $callback, $middleware);
203 | }
204 |
205 | /**
206 | * Maps the any route
207 | *
208 | * {@inheritDoc} **Example:**
209 | * ```php
210 | * Router\any('/{anything:any}', 'callback');
211 | * Router\any('/{anything:any}', 'callback', $middlewareArray);
212 | * ```
213 | */
214 | function any(string $path, mixed $callback, array|string $middleware = []): void
215 | {
216 | map('GET|POST|PATCH|PUT|DELETE', $path, $callback, $middleware);
217 | }
218 |
219 | /**
220 | * Maps the view route
221 | *
222 | * {@inheritDoc} **Example:**
223 | * ```php
224 | * Router\view('/page', 'templates/page');
225 | * Router\view('/page', 'templates/page', ['with' => 'data']);
226 | * ```
227 | */
228 | function view(string $path, string $file, array $data = [], array|string $middleware = []): void
229 | {
230 | any($path, fn() => Response\view($file, $data), $middleware);
231 | }
232 |
233 | /**
234 | * Maps the redirect route
235 | *
236 | * {@inheritDoc} **Example:**
237 | * ```php
238 | * Router\redirect('/page', '/new/page/url');
239 | * Router\redirect('/other-page', 'https://url');
240 | * ```
241 | */
242 | function redirect(string $path, string $to, array|string $middleware = []): void
243 | {
244 | any($path, fn() => Response\redirect($to), $middleware);
245 | }
246 |
247 | /**
248 | * Checks route is match. It must be located at the end of all route definitions.
249 | *
250 | * {@inheritDoc} **Example:**
251 | * ```php
252 | * if (! Router\is_found()) {
253 | * echo '404 not found!';
254 | * }
255 | * ```
256 | */
257 | function is_found(): bool
258 | {
259 | global $mikro;
260 |
261 | return isset($mikro[FOUND]) && $mikro[FOUND] === true;
262 | }
263 |
264 | /**
265 | * Define 404 error route. It must be located at the end of all route definitions.
266 | *
267 | * {@inheritDoc} **Example:**
268 | * ```php
269 | * Router\error('callback');
270 | * Router\error(function () {
271 | * Response\html('404 not found!', Response\STATUS['HTTP_NOT_FOUND']);
272 | * });
273 | * Router\error([
274 | * fn() => Response\html('Default 404 error handler'),
275 | * '/posts' => fn() => Response\html('/posts 404 error handler'),
276 | * '/products' => fn() => 'ProductController::notFoundHandler',
277 | * ])
278 | * ```
279 | */
280 | function error(mixed $callback = []): void
281 | {
282 | if (! \is_array($callback)) {
283 | $callback = [$callback];
284 | }
285 |
286 | if (! is_found()) {
287 | \http_response_code(404);
288 | $path = Request\path();
289 |
290 | foreach ($callback as $key => $_callback) {
291 | $starts = \str_starts_with($path, (string) $key);
292 | $match = \preg_match('@' . (string) $key . '@', $path);
293 |
294 | if (! empty($key) && ($starts || $match)) {
295 | if (! \is_callable($_callback)) {
296 | throw new ValidatorException("Error callback `$key` is not valid");
297 | }
298 |
299 | \call_user_func($_callback);
300 |
301 | return;
302 | }
303 | }
304 |
305 | if (isset($callback[0]) && \is_callable($callback[0])) {
306 | \call_user_func($callback[0]);
307 | }
308 | }
309 | }
310 |
311 | /**
312 | * Group routes
313 | *
314 | * {@inheritDoc} **Example:**
315 | * ```php
316 | * Route\group('/admin', function () {
317 | * Route\get('', 'Admin\HomeController::index');
318 | *
319 | * Route\group('/posts', function () {
320 | * Route\get('', 'Admin\PostController::index');
321 | * Route\get('/{id:num}', 'Admin\PostController::show');
322 | * });
323 | * });
324 | * Router\group('/prefix', fn() => [
325 | * Router\get('/home', 'home_callback')
326 | * ], $middlewareArray);
327 | * ```
328 | */
329 | function group(string $prefix, mixed $callback, array|string $middleware = []): void
330 | {
331 | global $mikro;
332 |
333 | if (\is_string($middleware)) {
334 | $middleware = \explode('|', $middleware);
335 | }
336 |
337 | if (! isset($mikro[PREFIX])) {
338 | $mikro[PREFIX] = '';
339 | }
340 |
341 | if (! isset($mikro[MIDDLEWARE])) {
342 | $mikro[MIDDLEWARE] = [];
343 | }
344 |
345 | $mikro[PREFIX] .= $prefix;
346 | $mikro[MIDDLEWARE] = \array_merge($mikro[MIDDLEWARE], $middleware);
347 |
348 | \call_user_func($callback);
349 |
350 | if (($pos = \strrpos($mikro[PREFIX], $prefix)) !== false) {
351 | $mikro[PREFIX] = \substr_replace($mikro[PREFIX], '', $pos, \strlen($prefix));
352 | }
353 |
354 | foreach ($middleware as $mw) {
355 | \array_pop($mikro[MIDDLEWARE]);
356 | }
357 | }
358 |
359 | /**
360 | * Get route parameter(s)
361 | *
362 | * {@inheritDoc} **Example:**
363 | * ```php
364 | * Route\get('/posts/{postTitle}', function () {
365 | * Router\parameters(); // ['postTitle' => 'value']
366 | * Router\parameters('postTitle') => 'value'
367 | * });
368 | *
369 | * Route\get('/posts/{postId:num}', function () {
370 | * Router\parameters(); // ['postId' => '5']
371 | * Router\parameters('postId') => '5'
372 | * });
373 | * ```
374 | *
375 | * Available options: num, str, any, all
376 | *
377 | * /posts/{post:num} => /posts/5
378 | * /posts/{post:str} => /posts/lorem-lipsum-dolor
379 | * /posts/{post:any} => /posts/lorem-lipsum_^54-any-char
380 | * /posts/{post:all} => /posts/any-char/any-slash
381 | * /posts/{post} => if no option, equals any option
382 | */
383 | function parameters(?string $name = null, mixed $default = null): mixed
384 | {
385 | global $mikro;
386 |
387 | return $name === null ?
388 | $mikro[PARAMETERS] ?? [] :
389 | ($mikro[PARAMETERS][$name] ?? $default);
390 | }
391 |
392 | /**
393 | * Create resource routes
394 | *
395 | * {@inheritDoc} **Example:**
396 | * ```php
397 | * Router\resource('/posts', 'Controllers\PostController');
398 | * Router\resource('/items', 'Controllers\ItemController:index|show'); // only index and show
399 | * Router\resource('/foo', FooController::class, ['middleware_one']);
400 | * ```
401 | */
402 | function resource(string $path, string $class, array|string $middleware = []): void
403 | {
404 | $only = null;
405 |
406 | if (\str_contains($class, ':')) {
407 | [$class, $only] = \explode(':', $class, 2);
408 | }
409 |
410 | $call = fn(string $method) =>
411 | (\is_callable("{$class}::{$method}") ?
412 | "{$class}::{$method}" : fn() => \call_user_func([new $class(), $method]));
413 |
414 | $methods = [
415 | 'index' => fn() => get($path, $call('index'), $middleware),
416 | 'store' => fn() => post($path, $call('store'), $middleware),
417 | 'create' => fn() => get("{$path}/create", $call('create'), $middleware),
418 | 'show' => fn() => get("{$path}/{id}", $call('show'), $middleware),
419 | 'edit' => fn() => get("{$path}/{id}/edit", $call('edit'), $middleware),
420 | 'update' => fn() => put("{$path}/{id}", $call('update'), $middleware),
421 | 'destroy' => fn() => delete("{$path}/{id}", $call('destroy'), $middleware),
422 | ];
423 |
424 | if ($only) {
425 | foreach (\explode('|', $only) as $method) {
426 | if (isset($methods[$method])) {
427 | $methods[$method]();
428 | }
429 | }
430 | } else {
431 | foreach ($methods as $method) {
432 | $method();
433 | }
434 | }
435 | }
436 |
437 | /**
438 | * Sync routes with files in specific directory
439 | *
440 | * {@inheritDoc} **Example:**
441 | * ```php
442 | * Router\files('/', __DIR__ . '/pages/front');
443 | * Router\files('/admin', __DIR__ . '/pages/back', ['AdminMiddleware::handle']);
444 | * ```
445 | */
446 | function files(string $routePath, string $filesPath, array|string $middleware = []): void
447 | {
448 | $iterator = new \RecursiveIteratorIterator(
449 | new \RecursiveDirectoryIterator($filesPath)
450 | );
451 |
452 | foreach ($iterator as $item) {
453 | if ($item->isDir() || ! \str_ends_with($item->getFilename(), '.php')) {
454 | continue;
455 | }
456 |
457 | $route = resolve_file($item, \realpath($filesPath));
458 |
459 | map(
460 | [$route['method']],
461 | '/' . \trim($routePath . $route['path'], '/'),
462 | fn() => require($item->getPathName()),
463 | $middleware
464 | );
465 | }
466 | }
467 |
468 | /**
469 | * Resolve file for route
470 | *
471 | * @internal
472 | */
473 | function resolve_file(\SplFileInfo $file, string $base): array
474 | {
475 | $prefix = \str_replace([$base, \DIRECTORY_SEPARATOR], ['', '/'], \dirname($file->getRealPath()));
476 | $method = 'GET';
477 | $path = $file->getBaseName('.php');
478 |
479 | foreach (['get', 'post', 'put', 'patch', 'delete', 'options'] as $item) {
480 | if (\str_ends_with($file->getBaseName('.php'), $item)) {
481 | $method = \strtoupper($item);
482 | $path = $file->getBaseName('.' . $item . '.php');
483 | }
484 | }
485 |
486 | $path = $path === 'index' ? '' : $path;
487 | $path = \rtrim($prefix . '/' . $path, '/') ?: '/';
488 |
489 | return \compact('method', 'path');
490 | }
491 |
492 | /**
493 | * Router found constant
494 | *
495 | * @internal
496 | */
497 | const FOUND = 'Router\FOUND';
498 |
499 | /**
500 | * Router prefix constant
501 | *
502 | * @internal
503 | */
504 | const PREFIX = 'Router\PREFIX';
505 |
506 | /**
507 | * Router middleware constant
508 | *
509 | * @internal
510 | */
511 | const MIDDLEWARE = 'Router\MIDDLEWARE';
512 |
513 | /**
514 | * Router parameters
515 | *
516 | * @internal
517 | */
518 | const PARAMETERS = 'Router\PARAMETERS';
519 | }
520 |
--------------------------------------------------------------------------------
/src/Validator.php:
--------------------------------------------------------------------------------
1 | isset($data['title']) && ! empty($value);
14 | * Validator\validate($_POST, 'title', [$callback]); // required and filled
15 | * Validator\valdiate(Request\all(), 'username', [$callback, 'ctype_alnum']);
16 | * Validator\validate(Request\all(), 'email', 'filter_var:' . FILTER_VALIDATE_EMAIL);
17 | * ```
18 | */
19 | function validate(array $data, string $key, string|array|callable $rules): bool
20 | {
21 | // Split $rules is a string
22 | if (\is_string($rules)) {
23 | $rules = \explode('|', $rules);
24 | }
25 |
26 | // Cast $rules to array if that is a callable
27 | if (\is_callable($rules)) {
28 | $rules = [$rules];
29 | }
30 |
31 | // Normalize rule
32 | $normalizeRule = function (string $rule) {
33 | $normalizedRule = [];
34 |
35 | // If data contains "!" character, set "not" key to true
36 | $normalizedRule['not'] = \str_contains($rule, '!') ? true : false;
37 |
38 | // If the rule contains the ":"
39 | if (\str_contains($rule, ':')) {
40 | // Split rule and parameters
41 | [$rule, $stringParameters] = \explode(':', $rule, 2);
42 | // Split parameters
43 | $parameters = \explode(',', $stringParameters);
44 |
45 | // Set splitted parameters
46 | $normalizedRule['parameters'] = $parameters;
47 | } else {
48 | // Parameter is not defined
49 | $normalizedRule['parameters'] = [];
50 | }
51 |
52 | // Normalize the rule. The rules can also have callback functions.
53 | $normalizedRule['rule'] = \str_replace('!', '', $rule);
54 |
55 | return $normalizedRule;
56 | };
57 |
58 | $pass = \array_map(function ($rule) use ($normalizeRule, $data, $key) {
59 | if (\is_string($rule)) {
60 | $rule = $normalizeRule($rule);
61 |
62 | // isset and empty functions are not callback in PHP
63 | $result = match ($rule['rule']) {
64 | 'isset' => isset($data[$key]),
65 | 'empty' => empty($data[$key] ?? ''),
66 | default => \call_user_func_array(
67 | $rule['rule'],
68 | \array_merge([$data[$key] ?? null], $rule['parameters'])
69 | )
70 | };
71 | } elseif (\is_callable($rule)) {
72 | $result = \call_user_func_array($rule, [$data[$key] ?? null, $data]);
73 | } else {
74 | return false;
75 | }
76 |
77 | return (\is_array($rule) && (isset($rule['not']) ? $rule['not'] : false)) ?
78 | ! $result : $result;
79 | }, $rules);
80 |
81 | return empty(\array_filter($pass, fn($item) => $item === false));
82 | }
83 |
84 | /**
85 | * Validate array and get all results in array
86 | *
87 | * {@inheritDoc} **Example:**
88 | * ```php
89 | * Validator\validate_all(
90 | * ['title' => 'foo', 'email' => 'bar'],
91 | * [
92 | * 'title' => 'isset|!empty|ctype_alnum',
93 | * 'email' => 'isset|!empty|filter_var:' . FILTER_VALIDATE_EMAIL
94 | * ]
95 | * ); // ['title' => true, 'email' => false]
96 | * ```
97 | */
98 | function validate_all(array $data, array $rules): array
99 | {
100 | $results = \array_map(function ($key, $rule) use ($data) {
101 | return validate($data, $key, $rule);
102 | }, \array_keys($rules), \array_values($rules));
103 |
104 | return \array_combine(\array_keys($rules), \array_values($results));
105 | }
106 |
107 | /**
108 | * Validate array and get result in boolean
109 | *
110 | * {@inheritDoc} **Example:**
111 | * ```php
112 | * Validator\is_validate_all(
113 | * ['title' => 'foo', 'email' => 'bar'],
114 | * [
115 | * 'title' => 'isset|!empty|ctype_alnum',
116 | * 'email' => 'isset|!empty|filter_var:' . FILTER_VALIDATE_EMAIL
117 | * ]
118 | * ); // true|false
119 | * ```
120 | */
121 | function is_validated_all(array $data, array $rules): bool
122 | {
123 | $results = \array_map(function ($key, $rule) use ($data) {
124 | return validate($data, $key, $rule);
125 | }, \array_keys($rules), \array_values($rules));
126 |
127 | return empty(\array_filter($results, fn($result) => $result === false));
128 | }
129 |
130 | /**
131 | * Validate request data and get all results in array
132 | *
133 | * {@inheritDoc} **Example:**
134 | * ```php
135 | * Validator\validate_request([
136 | * 'title' => 'isset|!empty|ctype_alnum',
137 | * 'email' => 'isset|!empty|filter_var:' . FILTER_VALIDATE_EMAIL
138 | * ]); // ['title' => true, 'email' => false]
139 | * ```
140 | */
141 | function validate_request(array $rules): array
142 | {
143 | return validate_all(Request\all(), $rules);
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/View.php:
--------------------------------------------------------------------------------
1 | 'bar']);
18 | * ```
19 | *
20 | * @throws MikroException If view path not set on global $mikro array
21 | * @throws ViewException If view file not found
22 | */
23 | function render(string $file, array $data = []): string
24 | {
25 | global $mikro;
26 |
27 | if (! isset($mikro[PATH])) {
28 | throw new MikroException('Please set the view path');
29 | }
30 |
31 | if (($path = exists($file)) === false) {
32 | throw new ViewException("View file ({$file}) not found in: {$path}");
33 | }
34 |
35 | \ob_start();
36 |
37 | if (! empty($data)) {
38 | \extract($data);
39 | }
40 |
41 | require cache($path);
42 |
43 | return \ltrim((string) \ob_get_clean());
44 | }
45 |
46 | /**
47 | * Escape string
48 | *
49 | * {@inheritDoc} **Example:**
50 | * ```php
51 | * echo View\e('