Lark is a modern, lightweight app framework designed specifically for developing REST APIs.
- Installation
- Routing
- Logging
- Exception Handling
- Debugger
- Configuration & Bindings
- Environment Variables & Configuration
- Request
- Response
- Database
- Schema
- Model
- Validator
- Filter
- HTTP Client
- CLI
- File
- Timer
- Helpers
Requirements:
- PHP 8
- PHP extensions
- Required
- [...]
- Optional
- curl - if using
Lark\Http\Client
composer require lark/framework
The router is used to dispatch route actions and middleware.
// bootstrap
// ...
// define routes
router()
// get([route], [action])
->get('/', function() {});
// run app
app()->run();
There are multiple ways to define routes.
// routes for HTTP specific methods:
router()->delete('/route', function(){});
router()->get('/route', function(){});
router()->head('/route', function(){});
router()->options('/route', function(){});
router()->patch('/route', function(){});
router()->post('/route', function(){});
router()->put('/route', function(){});
// route for all HTTP methods
router()->all('/route', function(){});
// route for multiple HTTP methods
router()->route(['GET', 'POST'], '/route', function(){});
// a wildcard route "*" can be used to match any route
router()->get('*', function(){}); // all HTTP GET methods
router()->all('*', function(){}); // all HTTP methods (all requests)
router()->route(['GET', 'POST'], '*', function(){}); // all HTTP GET and POST methods
Regular expression routes use PCRE patterns for matching routes.
// match all routes that begin with "/api"
router()->get('/api.*?', function(){});
Route groups can be used to simplify defining similar routes.
router()
->group('/api/users') // group([base-route])
->get('/', function(){}) // "/api/users"
->get('/active', function(){}); // "/api/users/active"
Route groups can be defined in route files which are loaded during routing (lazy load routes).
// bootstrap routes directory
// ...
router()->load([
// [base-route] => [file]
'/api/users' => 'users'
]);
// in routes directory file "users.php" defines routes
// the group('/api/users') method does not need to be called (handled by load() method)
router()
->get('/', function(){}) // "/api/users"
->get('/active', function(){}); // "/api/users/active"
Inside route files router()
should only be called once to avoid false route no match errors.
// incorrect:
router()->bind(function(){});
router()->get('/', function(){});
// correct:
router()
->bind(function(){})
->get('/', function(){});
A route controller object can be used with Route Groups and Route Group Loading.
class MyController implements Lark\Router\RouteControllerInterface
{
public function bind(Router $router): void
{
$router->get('/users', function(){}); // "/api/users"
}
}
// in routes file
router()
->group('/api')
->controller(new MyController);
Route actions are executed when a route is matched. Route actions can be a callable function (Closure
) or array with [class, method]
. The first route matched is the only route action that will be executed.
// function will be called on route match
router()->get('/example-html', function(): string {
return 'hello'; // return string to output as html
});
router()->get('/example-json', function(): array {
return ['message' => 'hello']; // return array|stdClass to output as JSON
// will auto add header "Content-Type: application/json"
// and response body will be:
// {"message": "hello"}
});
// class method "App\Controller\ExampleController::hello()" will be called on route match
router()->get('/example2', [App\Controller\ExampleController::class, 'hello']);
If no route match is found a not found action can be defined. The HTTP response status code is auto set to 404
.
router()->notFound(function(string $requestMethod, string $requestPath){});
If a not found action is not defined a Lark\Router\NotFoundException
will be thrown.
Route named parameters are required parameters that do not use regular expressions. Multiple name parameters are allowed.
router()->get('/users/{id}', function($id){});
Route optional named parameters are optional parameters that do not use regular expressions. Optional named parameters can only be used at the end of the route. Multiple optional named parameters are allowed.
router()->get('/users/{id}/{groupId?}', function($id, $groupId = null){});
In this example the groupId
parameter is optional, so route /users/5
and /users/5/10
would both match.
Regular expressions can be used to define parameters using PCRE patterns. Multiple regular expression parameters are allowed.
// match digits
router()->get('/users/(\d+)', function(int $id){});
// or match alphanumeric with length of 8
router()->get('/users/([a-z0-9]{8})', function(string $id) {});
Middleware is a single or multiple actions that are executed before a route action is called. Middleware actions can be executed always or only when a route is matched. Middleware must be defined before routes are defined. Middleware actions follow the same structure as Route Actions. The arguments Lark\Request $req
and Lark\Response $res
are passed to all middleware actions.
// executed always
router()->bind(function(Lark\Request $req, Lark\Response $res){});
// executed if any route is matched
router()->matched(function(Lark\Request $req, Lark\Response $res){});
// define routes
// ...
Multiple middleware actions can be set.
// single action
router()->bind(function(){});
// multiple actions
router()->bind(function(){}, [MyClass::class, 'myMethod']);
// array of actions
router()->bind([
function(){},
function(){}
]);
Route specific middleware actions are only executed if the route is matched.
// method([methods], [route], [...actions])
router()->map(['GET'], '/api.*?', function(){});
router()->get('/api/users', function(){});
If the HTTP request is /api/users
then both the middleware action and route action would be executed.
Middleware is always executed in the following order:
- Always execute (
router()->bind(...)
) - Execute mapped on matched route (
router()->map(...)
) - Execute on matched route (
router()->matched(...)
) - After middleware (
router()->after(...)
)
Middleware can be defined to be used only on a specific route group. Route group middleware actions are only executed if a group route is matched.
router()
// group([base-route], [...actions])
->group('/api/users', function(){})
->get('/', function(){}) // "/api/users"
->get('/{id}', function($id){}) // "/api/users/{id}"
After middlware always runs after a route action has been called, even if the route does not exist.
router()->after(function(){}, [MyClass::class, 'myMethod']);
Lark\Logger
is used for logging. The helper function logger()
is available.
logger('channel')->critical('message', [context]);
logger('channel')->debug('message', [context]);
logger('channel')->error('message', [context]);
logger('channel')->info('message', [context]);
logger('channel')->warning('message', [context]);
Logging info level record example.
// bootstrap log handler
app()->logHandler = new App\LogHandler;
Lark\Logger::handler(app()->logHandler);
// ...
// log info level record
logger('user')->info('User has been authorized', ['userId' => $user->id]);
// ...
// output log example
print_r( app()->logHandler->close() );
Global context can be added to all context sent in log record.
Lark\Logger::globalContext(['sessionId' => $session->id]);
// ...
logger('user')->info('User has signed out', ['userId' => $user->id]);
// context is: ['sessionId' => x, 'userId' => y]
Exceptions can be handled using the exception handler.
// bootstrap
// ...
// define routes
// ...
try
{
// run app
app()->run();
}
catch (Throwable $th)
{
new App\ExceptionHandler($th);
}
Example App\ExceptionHandler
class.
namespace App;
use Throwable;
class ExceptionHandler
{
public function __construct(Throwable $th)
{
\Lark\Exception::handle($th, function (array $info) use ($th)
{
$code = $th->getCode();
if (!$code)
{
$code = 500;
}
// log error
// ...
// respond with error
res()
->code($code)
->json($info);
// --or-- continue to throw exception
throw $th;
});
}
}
Lark\Debugger
can be used for debugging. The helper functions debug()
and x()
are available.
use Lark\Debugger;
// append debugger info
Debugger::append(['some' => 'info'])
->name('Test info') // this will be displayed as title (optional)
->group('test'); // this will group info together (optional)
Debugger::append(['more' => 'info'])
->name('More test info')
->group('test');
Debugger::dump(); // dump all debugger info and exit
// or use:
// x(); // dump all debugger info and exit
Framework configuration and bindings can be set with the use()
method.
Enable Lark internal append debugger info for debugger dump.
app()->use('debug.dump', true);
Enable Lark internal debug logging.
app()->use('debug.log', true);
Database connections are registered using the syntax db.connection.[connectionId]
and accessed using the syntax [connectionId]$[database]$[collection]
.
// setup default MongoDB database connection with connectionId "default"
// the first registered connection is always the default connection
// regardless of connectionId
app()->use('db.connection.default', [
'hosts' => ['127.0.0.1'],
'username' => 'test',
'password' => 'secret',
'replicaSet' => 'rsNameHere', // (optional)
// options can override any global database options
// (optional, see "Database Global Options" below)
'options' => []
]);
// register second connection with connectionId "myconn"
app()->use('db.connection.myconn', [...]);
// ...
// use default connection (no connectionId required):
$db = db('dbName$collectionName');
// or: $db = db('dbName', 'collectionName');
// use non-default connection (connectionId required):
$db2 = db('myconn$dbName$collectionName');
// or: $db = db('myConn2', 'dbName', 'collectionName');
Database global options can be set using db.options
. All default option values are listed below.
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\WriteConcern;
app()->use('db.options', [
'db.allow' => [], // allow access to only specific databases
'db.deny' => ['admin', 'config', 'local'], // restrict access to databases
'debug.dump' => false, // will include all database calls/context in debugger dumper
'debug.log' => false, // add debug level database messages to logger
'find.limit' => 1_000, // find "limit" for find options
'read.concern' => new ReadConcern, // MongoDB read concern
'write.concern' => new WriteConcern(WriteConcern::MAJORITY) // MongoDB write concern
]);
Read more about Write Concern in the MongoDB docs and in the PHP docs.
Sessions can be stored in the database using a Lark\Model
object.
app()->use('db.session', new App\Model\Session);
Custom validator rules can be registered using validator.rule.[type].[ruleClassName]
.
app()->use('validator.rule.string.beginWithEndWith', App\Validator\BeginWithEndWith::class);
Lark\Env
is used for app environment variables and configuration. The helper function env()
is available.
Example read PATH
environment variable.
$path = env('PATH');
// or use default value "/my/path" if environment variable does not exist
$path2 = env('PATH2', '/my/path');
// for required environment vars do not use a default value argument
// which will throw exception if the environment var does not exist
$path2 = env('PATH2');
// Lark\Exception exception thrown: Invalid env variable key "PATH2"
Example .env
file.
DB_USER=myuser
DB_PWD=secret
Example .env
file usage.
// load from file (bootstrap)
Lark\Env::getInstance()->load(DIR_ROOT . '/.env');
$dbUser = env('DB_USER'); // myuser
$dbPassword = env('DB_PWD'); // secret
Other Lark\Env
methods: fromArray(array $array)
, has(string $key): bool
and toArray(): array
.
Lark\Request
provides HTTP request data with input sanitizing. The helper function req()
is available.
// example request:
// POST /example
// Content-Type: application/json
// {"name": "Test", "contact": {"email": "test@example.com"}}
$data = req()->json(); // get all as object/array (no auto sanitizing)
// request JSON must be an array or 400 response is sent
$data = req()->jsonArray();
// request JSON must be an object or 400 response is sent
$data = req()->jsonObject();
If HTTP header
Content-Type: application/json
does not exist for any JSON methods, an automatic response with HTTP status code400
and JSON body{"message": "Invalid JSON: [reason]"}
will be sent.
Individual JSON fields can also be accessed with sanitizing.
// get individual field
$name = req()->jsonField('name')->string();
if(req()->jsonField('contact.email')->has())
{
$email = req()->jsonField('contact.email')->email();
}
POST
request ( Content-Type: application/x-www-form-urlencoded
) example.
if(req()->isMethod('POST'))
{
$name = req()->input('name')->string();
if(req()->input('email')->has())
{
$email = req()->input('email')->email();
}
}
GET
request example.
// request "/?id=5&name=Shay"
print_r([
'id' => req()->query('id')->integer(),
// use "default" as value if query "name" does not exist
'name' => req()->query('name', 'default')->string()
]); // Array ( [id] => 5 [name] => Shay )
Request cookie example.
if(req()->cookie('myCookie')->has())
{
var_dump( req()->cookie('myCookie')->string() );
}
Lark\Request\Session
is used to manage sessions.
app()->session->set('user.id', 5); // creates session data: [user => [id => 5]]
// ...
if(app()->session->has('user.id'))
{
$userId = app()->session->get('user.id');
}
Sessions can be stored in the database by using
Lark\Database\Session::handler()
.
Lark\Request\SessionFlash
can be used to store short-term data where the data is available from when set through the following request, example:
app()->session()->flash()->set('userError', 'Invalid session');
// redirect, then use message
echo app()->session()->flash()->get('userError');
// message is no longer available on next request
body(bool $convertHtmlEntities = true): string
- request raw body data gettercontentType(): string
- content-type gettercookie(string $key, $default = null): Lark\Request\Cookie
- cookie input object getterhasHeader(string $key): bool
- check if header key existsheader(string $key): string
- header value getterheaders(): array
- get all headershost(): string
- HTTP host value getter, likewww.example.com
input(string $key, $default = null): Lark\Request\Input
- input object getter forPOST
ipAddress(): string
- IP address getterisContentType(string $contentType): bool
- validate request content-typeisMethod(string $method): bool
- validate request methodisSecure(): bool
- check if request is secure (HTTPS)json()
- JSON request body getterjsonArray(): array
- JSON request body getter, must be array or400
HTTP status code responsejsonField(string $field, $default = null): Lark\Request\Json
- JSON request field object getterjsonObject(): array
- JSON request body getter, must be object or400
HTTP status code responsemethod(): string
- request method getterpath(): string
- path getter, like/the/path
pathWithQueryString(): string
- path with query string getter, like/the/path?x=1
port(): int
- port getterquery(string $key, $default = null): Lark\Request\Query
- query input object getter forGET
queryString(): string
- query string getter, likex=1&y=2
scheme(): string
- URI scheme getter, likehttp
session(): Lark\Request\Session
- session object getteruri(): string
- URI getter, likehttp://example.com/example?key=x
Input methods include methods for request input objects: Cookie
, Input
and Query
.
email(array $options = [])
- value getter, sanitize as emailfloat(array $options = ['flags' => FILTER_FLAG_ALLOW_FRACTION])
- value getter, sanitize as floathas(): bool
- check if key existsinteger(array $options = [])
- value getter, sanitize as integerstring(array $options = [])
- value getter, sanitize as stringurl(array $options = [])
- value getter, sanitize as URL
Session methods clear()
, get()
, has()
and set()
all use dot notation for keys, for example: set('user.isActive', 1) equals: [user => [isActive => 1]]
.
clear(string $key)
- clear a keystatic cookieOptions(array $options)
- set cookie options- default options are:
['lifetime' => 0, 'path' => '/', 'domain' => '', 'secure' => false, 'httponly' => false]
- default options are:
destroy()
- destroy a sessionstatic exists(): bool
- check if sessions are enabled and session existsget(string $key)
- value getterhas(string $key): bool
- check if key existsid(): ?string
- session ID getterisSession(): bool
- check if session existsset(string $key, $value)
- key/value settertoArray(): array
- session array getter
Lark\Response
is used to control the HTTP response.
// set header, status code 200, content-type and send JSON response
res()
->header('X-Test', 'value')
->code(Lark\Response::HTTP_OK)
->contentType('application/json') // not required when using json()
->json(['ok' => true]);
// {"ok": true}
cacheOff(): Lark\Response
- disable cache using cache-controlcontentType(string $contentType): Lark\Response
- content-type settercookie($key, $value, $expires, $path, $domain, $secure, $httpOnly): bool
- cookie settercookieClear(string $key, string $path = '/'): bool
- remove cookieheader(string $key, $value): Lark\Response
- header setterheaderClear(string $key): Lark\Response
- remove header keyheaders(array $headers): Lark\Response
- headers setter using arrayjson($data)
- respond with JSON payload (and content-typeapplication/json
in headers)redirect(string $location, bool $statusCode301 = false)
- send redirectsend($data)
- respond with raw data payloadcode(int $code): Lark\Response
- response status code setter
Lark\Database
is used to access MongoDB database and collection instances. The helper function db()
is available.
// bootstrap
// setup default MongoDB database connection with connectionId "default"
app()->use('db.connection.default', [...]);
// register second connection with connectionId "myconn"
app()->use('db.connection.myconn', [...]);
// ...
// get Database object instance
$db = db('myDb$users');
// insert documents
$docIds = $db->insert([
['name' => 'Test', 'role' => 'admin'],
['name' => 'Test2', 'role' => 'admin']
]);
// Array ( [0] => 62ba4fd034faaf6fc132ef54 [1] => 62ba4fd034faaf6fc132ef55 )
// insert single document
$docId = $db->insertOne(['name' => 'Test3', 'role' => 'readonly']);
// find documents
$docs = $db->find(['role' => 'admin']);
// Array ( [0] => Array ( [id] => 62ba4fd034faaf6fc132ef54 [name] => Test [role] => admin )
// [1] => Array ( [id] => 62ba4fd034faaf6fc132ef55 [name] => Test2 [role] => admin ) )
// find documents with "name" staring with "Test"
$docs = $db->find(['name' => ['$regex' => '^Test']]);
// find documents by IDs
$docs = $db->findIds(['62ba4fd034faaf6fc132ef54', '62ba4fd034faaf6fc132ef55']);
// find single document
$doc = $db->findOne(['name' => 'Test2']);
// find single document by ID
$doc = $db->findId('62ba4fd034faaf6fc132ef54');
// update documents
$affected = $db->update(['role' => 'admin'], ['role' => 'admin2']);
// update bulk
$docIds = $db->updateBulk([
['id' => '62ba4fd034faaf6fc132ef55', 'role' => 'admin'],
[...]
]);
// Array ( [0] => 62ba4fd034faaf6fc132ef55 [1] => ... )
// update single document by ID
$newDoc = $db->updateId('62ba4fd034faaf6fc132ef55', ['role' => 'admin2']);
// update single document
$newDoc = $db->updateOne(['name' => 'Test2'], ['role' => 'admin']);
By default update methods used the $set
operator for updates, like ['$set' => ['role' => 'admin]]
. This operator can be changed, for example:
// increment visits by 1
$newDoc = $db->updateOne(['name' => 'Test2'], ['visits' => 1], operator: '$inc');
// replace bulk
$docIds = $db->replaceBulk([
['id' => '62ba4fd034faaf6fc132ef55', 'name' => 'Test222'],
[...]
]);
// Array ( [0] => 62ba4fd034faaf6fc132ef55 [1] => ... )
// replace single document by ID
$newDoc = $db->replaceId('62ba4fd034faaf6fc132ef55',
['name' => 'Test2222', 'role' => 'admin']);
// replace single document
$newDoc = $db->replaceOne(['name' => 'Test2222'], ['name' => 'Test2', 'role' => 'admin']);
// delete documents (note: filter cannot be empty)
$affected = $db->delete(['role' => 'admin']);
// delete documents by IDs
$affected = $db->deleteIds(['62ba4fd034faaf6fc132ef54', '62ba4fd034faaf6fc132ef55']);
// delete single document
$affected = $db->deleteOne(['name' => 'Test2']);
// delete all documents in collection
$affected = $db->deleteAll();
// create a new field
// set default value to empty array
$affected = $db->collectionField('tags')->create([]);
// delete a field
$affected = $db->collectionField('tags')->delete();
// check if a field exists on all documents
$exists = $db->collectionField('tags')->exists();
// check if a field exists on any document
$exists = $db->collectionField('tags')->exists(false);
// remove value "mytag" from field "tags" array
$affected = $db->collectionField('tags')->pull(
['id' => '62ba4fd034faaf6fc132ef54'],
'mytag'
);
// append values "mytag1" and "mytag2" to field "tags" array
// these values will only be appended if they
// don't already exists in the array
// use $unique=false to always append
$affected = $db->collectionField('tags')->push(
['id' => '62ba4fd034faaf6fc132ef54'],
['mytag1', 'mytag2']
);
// rename a field
$affected = $db->collectionField('tags')->rename('tagsNew');
Use dot notation for nested field names like
field1.field2
.
collectionField(string $field): Database\Field
- collection field object gettercount(array $filter = [], array $options = []): int
- count documents matching filterdelete(array $filter, array $options = []): int
- delete documents matching filterdeleteAll(array $options = []): int
- delete all documentsdeleteIds(array $ids, array $options = []): int
- delete documents by IDdeleteOne(array $filter, array $options = []): int
- delete single document matching filterdrop(): bool
- drop collectionexists(): bool
- check if collection existsfind(array $filter = [], array $options = []): array
- find documents matching filterfindId($id, array $options = []): ?array
- find document by IDfindIds(array $ids, array $options = []): array
- find documents by IDfindOne(array $filter = [], array $options = []): ?array
- find single document matching filterhas(array $filter, array $options = []): bool
- check if documents matching filter existhasIds(array $ids, array $options = []): bool
- check if documents with IDs existinsert(array $documents, array $options = []): array
- insert documentsinsertOne($document, array $options = []): ?string
- insert single documentping(): bool
- ping commandreplaceBulk(array $documents, array $options = []): int
- bulk replacereplaceId($id, $document, array $options = []): int
- replace single documentreplaceOne(array $filter, $document, array $options = []): int
- replace single documentupdate(array $filter, $update, array $options = []): int
- update documents matching filterupdateBulk(array $documents, array $options = []): int
- bulk updateupdateId($id, $update, array $options = []): int
- update document by IDupdateOne(array $filter, $update, array $options = []): int
- update single document matching filter
create($defaultValue = null): int
- create field with default valuedelete(): int
- delete collection fieldexists(bool $allDocs = true): bool
- check if field exists or checks if field exists on any document if!$allDocs
pull(array $filter, $value): int
- remove value from field arraypush(array $filter, $value, $unique = true): int
- append value to field array, if$unique
will only append value if doesn't already exist in field arrayrename(string $newName): int
- rename field
Lark\Schema
is used to create schemas for creating entities, entity validation and database collection creation.
use Lark\Schema;
$schema = new Schema([
// create an index when creating a database collection
'$index' => [
'name' => 1, 'age' => 1, '$name' => 'idxNameAge'
],
// or create multiple indexes
// '$indexes' => [
// ['username' => 1, '$name' => 'idxUsername', '$unique' => true],
// ['name' => 1, 'age' => 1, '$name' => 'idxNameAge']
// ],
// auto database projection (filter password by default)
'$filter' => ['password' => 0],
// schema fields
'name' => ['string', 'notEmpty'],
'username' => ['string', 'notEmpty'],
'password' => ['string', 'notEmpty'],
'age' => ['int', 'notEmpty'],
'isAdmin' => ['bool', 'notNull', ['default' => false]]
]);
Schema uses Validation Types & Rules for field definitions.
Options for in
$index
and$indexes
are any field starting with$
, like$unique
, and more options can be found in the MongoDB docs.
Default field values can also be set dynamically. For nested fields use dot notation like field.nestedfield
.
$schema->default('isAdmin', false);
Field value callbacks can be used. For nested fields use dot notation like field.nestedfield
.
$schema->apply('name', function($name): string {
return strtoupper($name);
});
A schema file can be imported as a schema for a schema field. First, create a partial schema in a schema file, for example: [DIR_SCHEMAS]/partials/users.info.php
.
<?php
return [
'object',
[
'fields' => [
'age' => 'int',
'tags' => 'array'
]
]
];
Next, add the import using a field name and file.
$schema = new Schema([
'$import' => [
// field => file (in schemas directory)
'info' => 'partials/users.info'
],
'name' => ['string', 'notEmpty'],
// field for schema import (optional, does not need to be set here)
'info' => null
]);
Printing $schema->toArray()
will output:
Array
(
[name] => Array
(
[0] => string
[1] => notEmpty
)
[info] => Array
(
[0] => object
[1] => Array
(
[fields] => Array
(
[age] => int
[tags] => array
)
)
)
)
Nested fields (using dot notation) can also be used.
$schema = new Schema([
'$import' => [
// field => file (in schemas directory)
'info.1.fields' => 'partials/users.info'
],
'name' => ['string', 'notEmpty'],
'info' => [
'object',
['fields' => null]
]
]);
Example partial schema in: [DIR_SCHEMAS]/partials/users.info.php
:
<?php
return [
'age' => 'int',
'tags' => 'array'
];
Lark\Model
is a model: a way to simplify database calls and creating/validating entities.
namespace App\Model;
use App\Model;
use Lark\Schema;
class User extends Model
{
const DBS = 'default$app$users';
public static function &schema(): Schema
{
return parent::schema([
'name' => ['string', 'notEmpty'],
'age' => ['int', 'notEmpty'],
'isAdmin' => ['bool', 'notNull', ['default' => false]]
]);
}
}
The App\Model\User
class can be used for creating an entity and validation.
$user = (new App\Model\User)->make([
'name' => 'Bob',
'age' => 25
]);
var_dump($user);
// array(3) { ["name"]=> string(3) "Bob" ["age"]=> int(25) ["isAdmin"]=> bool(false) }
// or an array can be used
$user = (new App\Model\User)->makeArray([
['name' => 'Bob', 'age' => 25],
['name' => 'Jane', 'age' => 21]
]);
The $mode
argument can be used to change the validator mode, for example requiring document IDs with replace+id
or update+id
:
// schema: ['id' => ['string', 'id'], 'name' => ['string', 'notEmpty']]
$user = (new App\Model\User)->make([
'name' => 'Bob'
], 'update+id');
// throws Lark\Validator\ValidatorException:
// Validation failed: "User.id" must be a string
The $mode
argument can be used to allow missing fields that can be used for partial documents with update
or update+id
:
$user = (new App\Model\User)->make([
'name' => 'Bob'
], 'update');
var_dump($user); // array(1) { ["name"]=> string(3) "Bob" }
The Model::db()
method can be used to access the Model database collection (Model::DBS
must be set).
// ...
class Model extends Model
{
const DBS = 'default$app$users';
public function get(string $id): ?array
{
return $this->db()->findId($id);
}
}
// get user document
$user = (new App\Model\User)->get('62ba4fd034faaf6fc132ef55');
// external calls: get documents
$docs = (new \App\Model\User)->db()->find(['role' => 'admin']);
Important: Model classes shouldn't have any required parameters in their
Model::__construct()
method, because the Models are automatically instantiated when using model/database binding, and any required parameters will not be present.
The Model::schema()
method can be used in multiple ways. By default the method will use the Model::SCHEMA
schema file constant to load the schema from file.
Another way to create a schema is overriding the parent method and passing the schema array:
class ExampleModel extends Model
{
public static function &schema(): Schema
{
return parent::schema([
'id' => ['string', 'id'],
// ...
]);
}
}
The above method caches the schema object, so when the schema method is called again it returns the referenced Schema
object.
A callback can also be passed to access the Schema
object created by the parent method, example:
class ExampleModel extends Model
{
const SCHEMA = 'users.php';
public static function &schema(): Schema
{
return parent::schema(function(Schema &$schema)
{
$schema->apply('name', function($name)
{
return strtoupper($name);
});
});
}
}
The model Lark\Database\Query
class can be used for input query parameters.
use Lark\Database\Query;
use App\Model\User;
$query = new Query(new User, [
'name' => 'test'
]);
// Database::find()
$results = $query->find();
// Database::count()
$count = $query->count();
Query selectors can be used as query parameters. Match a field with field value:
$query = [
'name' => 'test'
];
MongoDB comparison selectors $eq
, $gt
, $gte
, $in
, $lt
, $lte
, $ne
and $nin
can be used like:
$query = [
'age' => ['$gte' => 18]
];
With the $in
selector:
$query = [
'name' => ['$in' => ['test', 'test2', 'test3']]
];
With multiple selectors:
$query = [
'age' => ['$gt' => 20, '$lt' => 100]
];
By default queries with multiple selectors will perform a logical AND
operation. A logical OR
operation can be used with the $or
option:
$query = [
// age is greater than 20 OR less than 100
'age' => ['$gt' => 20, '$lt' => 100],
'$or' => true
];
The $filter
(or $projection
) option can be used to filter the document fields returned from the database:
$query = [
// only include fields "id" and "name" for each document
'$filter' => ['id' => 1, 'name' => 1],
'name' => 'test',
'age' => ['$gte' => 18]
];
// or fields can be excluded for each document
$query = [
// exclude fields "age" and "info" for each document
'$filter' => ['age' => 0, 'info' => 0]
];
The $page
option can be used for pagination.
// fetch first page
$query = [
'$page' => 1
];
// fetch second page
$query = [
'$page' => 2
];
By default the limit of documents per page is determined by the database option
find.limit
.
The default sort order of documents for the
$page
option is["id" => 1]
, this can be overridden using the$sort
option.
The $limit
option can be used to set the number of documents returned or to override the default documents per page when using the $page
option.
$query = [
'$limit' => 100
];
The
$limit
option value cannot exceed the database optionfind.limit
value.
The $sort
option can be used to set the sort order or documents.
// sort by "name" ASC and "age" DESC
$query = [
'$sort' => ['name' => 1, 'age' => -1]
];
The $skip
option can be used to set the query skip value.
$query = [
'$skip' => 10
];
The
$skip
option will always be overridden when used with the$page
option.
Created and updated field values can be used to auto set fields with created and updated date/times. Example schema:
[
'$created' => 'createdAt',
'$updated' => 'updatedAt',
'name' => ['string', 'notNull'],
'createdAt' => ['timestamp', 'notNull'],
'updatedAt' => ['timestamp', 'notNull']
]
Now the createdAt
and updatedAt
fields with be auto set to the current timestamp (time()
). The values can be set to timestamp
by default, or can be set to datetime
for DateTime
or dbdatetime
for MongoDB\BSON\UTCDateTime
, example:
[
'$created' => [
'createdAt' => 'dbdatetime'
],
// ...
]
In the above examples the createdAt
field will be set once (using schema default value) and the updatedAt
field will be set each time the document is made.
Database model schema constraints can be used as database constraints on references like verifying foreign keys and deleting documents by references.
The $refs.fk
constraint verifies foreign keys, can be set in any model schema and is used with the Database
methods: insert()
, insertOne()
, replaceBulk()
, replaceId()
, replaceOne()
, update()
, updateBulk()
, updateId()
and updateOne()
.
class UserLog extends Model
{
const DBS = 'default$app$users.log';
public static function &schema(): Schema
{
return parent::schema([
'$refs' => [
'fk' => [
// collection => [localField => foreignField, ...]
'users' => ['userId' => 'id']
]
],
'id' => ['string', 'id'],
'userId' => ['string', 'notEmpty'],
'message' => ['string', 'notEmpty']
]);
}
}
Example document in users.log
:
{
"id": "abc",
"userId": "62ba4fd034faaf6fc132ef54",
"message": "test"
}
Now when a model database insert/replace/update method is called the $refs.fk
constraint above will verify the collection users.log
field userId
value exists as a foreign key in the users
collection field id
(_id
).
If foreign key constraint verification fails a
Lark\Database\Constraint\DatabaseConstraintException
exception will be thrown with a message likeFailed to insert or update document(s), foreign key constraint failed for "userId"
.
The
$refs.fk
foreign fields (foreignField
) must always be a MongoDBObjectId
and foreign key verification on any other type will fail.
The
$refs.fk
constraint will always verify a foreign key, even when the local field value isnull
, but this can be disabled by using thenullable$
prefix on the local field name, likenullable$userId
, which means all local field null values will not have the foreign key verified.
The $refs.fk
constraint can also be used on an array of foreign keys in an array:
// class UserGroup (model)
$schema = new Schema([
'$refs' => [
'fk' => [
// collection => [localField => foreignField, ...]
'users' => ['users.$' => 'id']
]
],
// ...
]);
Example document in users.groups
:
{
"id": "abc",
"name": "group name",
"users": ["62ba4fd034faaf6fc132ef54", "62ba4fd034faaf6fc132ef55"]
}
Now when a model database insert/replace/update method is called the $refs.fk
constraint above will verify each value in the collection users.groups
field users
array exists as a foreign key in the users
collection field id
(_id
).
The $refs.fk
constraint can also be used on objects in an array that have foreign keys:
// class UserAllowed (model)
$schema = new Schema([
'$refs' => [
'fk' => [
// collection => [localField => foreignField, ...]
'users' => ['users.$.id' => 'id']
]
],
// ...
]);
Example document in users.allowed
{
"id": "abc",
"role": "admin"
"users": [
{"id": "62ba4fd034faaf6fc132ef54", "name": "test"}
{"id": "62ba4fd034faaf6fc132ef55", "name": "test2"}
]
}
Now when a model database insert/replace/update method is called the $refs.fk
constraint above will verify the collection users.allowed
field users
array to ensure each object field id
value exists as a foreign key in the users
collection field id
(_id
).
The $refs.fk
constraint can be used with multiple collections and fields:
$schema = new Schema([
'$refs' => [
'fk' => [
// collection => [localField => foreignField, ...]
'users' => [
'userId' => 'id',
'users.$' => 'id',
'usersAllowed.$.id' => 'id'
]
]
],
// ...
]);
The $refs.fk
constraint can also be used with the same model:
// class User (model)
$schema = new Schema([
'$refs' => [
'fk' => [
// allow managerId to be null (no manager)
// verify FK users.id exists when users.managerId exists
'users' => ['nullable$managerId' => 'id']
]
],
'id' => ['string', 'id'],
'managerId' => 'string'
]);
The $refs.clear
constraint allows clearing field values, can be set in any model schema and is used with the Database::deleteIds()
method.
class User extends Model
{
const DBS = 'default$app$users';
public static function &schema(): Schema
{
return parent::schema([
'$refs' => [
'clear' => [
// collection => [foreign fields]
'users.log' => ['userId']
]
],
'id' => ['string', 'id'],
'name' => ['string', 'notEmpty']
]);
}
}
Example document in users.log
:
{
"id": "abc",
"userId": "62ba4fd034faaf6fc132ef54",
"message": "test"
}
Now when the model database method deleteIds()
is called the $refs.clear
constraint above will trigger a database clear (update operation) to clear all document userId
fields in the users.log
collection with userId: {$in: [ids]}
.
The equivalent in MongoDB shell would be:
db.users.delete( { _id: { $in: [ids] } } ) db.users.log.updateMany( { userId: { $in: [ids] } }, { $set: { userId: null } } )
The $refs.delete
constraint allows deleting documents, can be set in any model schema and is used with the Database::deleteIds()
method.
class User extends Model
{
const DBS = 'default$app$users';
public static function &schema(): Schema
{
return parent::schema([
'$refs' => [
'delete' => [
// collection => [foreign fields]
'users.log' => ['userId']
]
],
'id' => ['string', 'id'],
'name' => ['string', 'notEmpty']
]);
}
}
Example document in users.log
:
{
"id": "abc",
"userId": "62ba4fd034faaf6fc132ef54",
"message": "test"
}
Now when the model database method deleteIds()
is called the $refs.delete
constraint above will trigger a database delete operation to delete all documents in the users.log
collection with userId: {$in: [ids]}
.
The equivalent in MongoDB shell would be:
db.users.delete( { _id: { $in: [ids] } } ) db.users.log.delete( { userId: { $in: [ids] } } )
The $refs.delete
constraint can also be used to pull ($pullAll
) IDs from an array:
$schema = new Schema([
'$refs' => [
'delete' => [
// collection => [foreign fields]
'users.groups' => ['users.$']
]
],
// ...
]);
Example document in users.groups
:
{
"id": "abc",
"name": "group name",
"users": ["62ba4fd034faaf6fc132ef54", "62ba4fd034faaf6fc132ef55"]
}
Now when the model database method deleteIds()
is called the $refs.delete
constraint above will trigger a database update operation to $pullAll
IDs in the collection users.groups
field users
.
The equivalent in MongoDB shell would be:
db.users.delete( { _id: { $in: [ids] } } ) db.users.groups.updateMany( { users: { $in: [ids] } }, { $pullAll: { users: [ids] } }, { multi:true } )
Note: even when multiple values are pulled from an array on a single document field MongoDB will still return
modifiedCount: 1
The $refs.delete
constraint can also be used to pull ($pull
) objects from an array based on an object field value:
$schema = new Schema([
'$refs' => [
'delete' => [
// collection => [foreign fields]
'users.allowed' => ['users.$.id']
]
],
// ...
]);
Example document in users.allowed
{
"id": "abc",
"role": "admin"
"users": [
{"id": "62ba4fd034faaf6fc132ef54", "name": "test"}
{"id": "62ba4fd034faaf6fc132ef55", "name": "test2"}
]
}
Now when the model database method deleteIds()
is called the $refs.delete
constraint above will trigger a database update operation to $pull
all objects in collection users.groups
field users
based on object field id
value.
The equivalent in MongoDB shell would be:
db.users.delete( { _id: { $in: [ids] } } ) db.users.allowed.updateMany( { users.id: { $in: [ids] } }, { $pull: { users: { id: { $in: [ids] } } } }, { multi:true } )
Note: even when multiple objects are pulled from an array on a single document field MongoDB will still return
modifiedCount: 1
The $refs.delete
constraint can be used with multiple collections and fields:
$schema = new Schema([
'$refs' => [
'delete' => [
// collection => [foreign fields]
'users.log' => ['userId', 'userId2'],
'users.groups' => ['users.$'],
'users.allowed' => ['users.$.id']
]
],
// ...
]);
Lark\Validator
is used for validation and making entities.
use Lark\Validator;
$isValid = (new Validator([
// data
'name' => 'Bob',
'age' => 25
], [
// schema
'name' => ['string', 'notEmpty'],
'age' => ['int', 'notNull'],
'phone' => null, // no type (any type allowed), optional
'title' => 'string' // string, optional
]))->validate(); // true
Assertion can be used during validation.
(new Validator([
'name' => null
], [
'name' => ['string', 'notNull']
]))->assert();
// throws Lark\Validator\ValidatorException:
// Validation failed: "name" must be a string
Make entities with validation.
// validation will pass because no field is required
var_dump(
(new Validator([], [
'name' => ['string'],
'age' => ['int']
]))->make()
);
// array(2) { ["name"]=> NULL ["age"]=> NULL }
Rules notNull
and notEmpty
, and sometimes id
, are rules for all types that do not allow the value to be null
.
The rule voidable
can be used for any fields that can be missing.
- any type (default) - any type allowed
notNull
- value cannot benull
array
(orarr
) - value can bearray
ornull
allowed
- array values must be allowed[allowed => [...]]
length
- number of array items must be[length => x]
max
- array values cannot exceed maximum value of[max => x]
min
- array values cannot be lower than minimum value of[min => x]
notEmpty
- must be a non-emptyarray
notNull
- must be anarray
unique
- array values must be unique
boolean
(orbool
) - must beboolean
ornull
notNull
- must beboolean
datetime
- must be an instance ofDateTime
ornull
notNull
- must be instance ofDateTime
dbdatetime
- must be an instance ofMongoDB\BSON\UTCDateTime
ornull
notNull
- must be instance ofMongoDB\BSON\UTCDateTime
float
- must be afloat
ornull
between
- must be between both values[between => [x, y]]
max
- must be a maximum value of[max => x]
min
- must be a minimum value of[min => x]
notEmpty
- must be afloat
greater than zeronotNull
- must be afloat
integer
(orint
) - must be aninteger
ornull
between
- must be between both values[between => [x, y]]
id
- must be aninteger
whenENTITY_FLAG_ID
flag is setmax
- must be a maximum value of[max => x]
min
- must be a minimum value of[min => x]
notEmpty
- must be aninteger
greater than zeronotNull
- must be aninteger
number
(ornum
) - must be a number ornull
between
- must be between both values[between => [x, y]]
id
- must be a number whenENTITY_FLAG_ID
flag is setmax
- must be a maximum value of[max => x]
min
- must be a minimum value of[min => x]
notEmpty
- must be a number greater than zeronotNull
- must be a number
object
(orobj
) - must be anobject
ornull
notEmpty
- must be a non-emptyobject
notNull
- must be anobject
string
(orstr
) - must be astring
ornull
allowed
- value must be allowed[allowed => [...]]
alnum
- must only contain alphanumeric characters- or, must only contain alphanumeric characters and whitespaces
[alnum => true]
alpha
- must only contain alphabetic characters- or, must only contain alphabetic characters and whitespaces
[alpha => true]
contains
- must contain value[contains => x]
- or, must contain value (case-insensitive)
[contains => [x, true]]
email
- must be a valid email addresshash
- hashes must be equal (timing attack safe)[hash => x]
id
- must be anstring
whenENTITY_FLAG_ID
flag is setipv4
- must be valid IPv4 addressipv6
- must be valid IPv6 addressjson
- must be a valid JSONlength
- length must be number of characters[length => x]
regex
- value must be a regular expression match[regex => x]
max
- length must be a maximum number of characters[max => x]
min
- length must be a minimum number of characters[min => x]
notAllowed
- value must be allowed[notAllowed => [...]]
notEmpty
- must be a non-emptystring
notNull
- must be astring
password
- passwords must match[password => x]
url
- must be a valid URL
timestamp
- must be a timestamp ornull
notNull
- must be a timestamp
Nested fields can be defined using the fields
property.
$isValid = (new Validator([
// data
'name' => 'Bob',
'contact' => [
'email' => 'bob@example.com',
'phone' => [
'cell' => '555-5555',
'office' => '555-6666'
]
]
], [
// schema
'name' => ['string', 'notEmpty'],
'contact' => [
'array',
[
'fields' => [
'email' => ['string', 'email'],
'phone' => [
'array',
[
'fields' => [
'cell' => 'string',
'office' => 'string'
]
]
]
]
]
]
]))->validate(); // true
Nested schemas can be defined for an array of arrays or objects using the schema:array
or schema:object
property.
$isValid = (new Validator([
'name' => 'test',
'tags' => [
// these must be arrays because "schema:array" is used
// if "schema:object" is used these must be objects
['id' => '1', 'name' => 'test2'],
['id' => 2, 'name' => 'test3'],
]
], [
'name' => ['string', 'notEmpty'],
'tags' => [
'array', 'notEmpty',
[
'schema:array' => [
'id' => ['int', 'notNull'],
'name' => 'string'
]
]
]
]))->assert();
// throws Lark\Validator\ValidatorException:
// Validation failed: "tags.0.id" must be an integer or null
In the example above if the schema rule
notEmpty
is not used before theschema:array
orschema:object
property, and the array of arrays or objects is empty, no rules will be validated/asserted.
Partial documents are not allowed inside nested schema objects or arrays.
A callback can be used with the assert()
method.
(new Validator([
'name' => null
], [
'name' => ['string', 'notNull']
]))->assert(function(string $field, string $message, string $name = null){
// handle error
//...
// return true to halt
// return false to continue to throw validation exception
return true;
});
Custom validation rules can be created.
// validator.rule.[type].[name]
app()->use('validator.rule.string.beginWithEndWith', App\Validator\BeginWithEndWith::class);
// App\Validator\MyRule class:
namespace App\Validator;
class BeginWithEndWith extends \Lark\Validator\Rule
{
private string $beginWith;
private string $endWith;
protected string $message = 'must begin with value and end with value';
public function __construct(string $beginWith, string $endWith)
{
$this->beginWith = $beginWith;
$this->endWith = $endWith;
}
public function validate($value): bool
{
$beginsWith = substr($value, 0, strlen($this->beginWith));
$endsWith = substr($value, -(strlen($this->endWith)));
return $beginsWith === $this->beginWith && $endsWith === $this->endWith;
}
}
// validation example
(new Validator([
'alias' => '123testXYZ'
], [
'alias' => ['string', ['beginWithEndWith' => ['123', 'XYZ']]]
]))->validate(); // true
It is also possible to override existing rules.
// validator.rule.[type].[name]
// overwrite existing string rule "email"
app()->use('validator.rule.string.email', 'App\\Validator\\Email');
// App\Validator\Email class:
namespace App\Validator;
class Email extends \Lark\Validator\TypeString\Email
{
public function validate($value): bool
{
// must be valid email and domain "example.com"
return parent::validate($value)
&& preg_match('/@example\.com$/i', $value) === 1;
}
}
// validation example
(new Validator([
'email' => 'test@example.com'
], [
'email' => ['string', 'email']
]))->validate(); // true
Lark\Filter
is used for filtering values.
$cleanStr = filter()->string($str);
Filter by array keys.
$arr = ["one" => 1, "two" => 2, "three" => 3];
// exclude filter
print_r(
filter()->keys($arr, ["two" => 0])
); // Array ( [one] => 1 [three] => 3 )
// include filter
print_r(
filter()->keys($arr, ["one" => 1, "two" => 1])
); // Array ( [one] => 1 [two] => 2 )
email($value, array $options = []): string
- sanitize value with email filterfloat($value, array $options = ['flags' => FILTER_FLAG_ALLOW_FRACTION]): float
- sanitize value with float filterinteger($value, array $options = []): int
- sanitize value with integer filterkeys(array $array, array $filter): array
- filters keys based on include or exclude filterstring($value, array $options = ['flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH]): string
- sanitize value with string filterurl($value, array $options = []): string
- sanitize value with url filter
Lark\Http\Client
is an HTTP client.
use Lark\Http\Client;
$client = new Client;
try
{
$res = $client->get('http://example.com');
$headers = $client->headers();
$statusCode = $client->statusCode();
if($statusCode === 200)
{
// ok
}
else
{
// handle
}
}
catch (Lark\Http\HttpException $ex)
{
// handle request/curl error
}
Various HTTP methods are available.
// DELETE request
$client->delete('http://example.com', ['field1' => 'value']);
// GET request
$client->get('http://example.com', ['param' => 1]); // http://example.com?param=1
// HEAD request
$client->head('http://example.com');
// OPTIONS request
$client->options('http://example.com');
// PATCH request
$client->patch('http://example.com', ['field1' => 'value']);
// POST request
$client->post('http://example.com', ['field1' => 'value']);
// PUT request
$client->put('http://example.com', ['field1' => 'value']);
Strings can also be used to send JSON.
$client = new Client([
'headers' => ['content-type' => 'application/json']
]);
// POST request with JSON string
$client->post('http://example.com', json_encode(['field1' => 'value']));
Options can be set for all methods (will override default options).
use Lark\Http\Client;
$client = new Client(['url' => 'http://example.com', 'timeout' => 8]);
$res = $client->get('/api/items'); // http://example.com/api/items
$res2 = $client->post('/api/items', ['name' => 'My Item']);
Options can be set for individual methods (will override default options and options for all methods).
$res = $client->get('/api/items', ['timeout' => 5]);
Options for curl
can be set.
use Lark\Http\Client;
$client = new Client([
'curl' => [
CURLOPT_RESOLVE => ['test.loc:127.0.0.1']
]
]);
curl
- set options forcurl
usingCURLOPT_[...]
optionsheaders
- set HTTP headers, which can be set using two methods['headers' => ['My-Header' => 'value']]
['headers' => ['My-Header: value']]
port
- set a custom port numberproxy
- use an HTTP proxyredirects
- allow redirectstimeout
- timeout in seconds for connection and executionurl
- base URL for request methodsverify
- verify peer's certificate and common name
Lark\Cli
is used to create CLI apps.
// bootstrap
// ...
$cli = Lark\Cli::getInstance();
// add command
$cli->command('files', 'Print files in directory')
->arg('dir', 'Read directory')
->action(function(string $dir) {
// print files in directory $dir
// optional, exit with any code by returning an int
// return 1; // same as $cli->exit(1);
});
// or use class/method:
// ->action([MyClass::class, 'methodName'])
// run app
$cli->run($_SERVER['argv']);
Arguments and options can be set for a command, and each argument and option has optional settings.
// set global option (separate from command options)
$cli->option('-d, --debug', 'Enable debug mode', function() {
// enable here
});
$cli->command('files', 'Print files in directory')
->arg('dir', 'Read directory') // required by default
// set another optional argument that can have multiple values (array)
->arg('subdirs', 'Read subdirectories', ['optional', 'array'])
// add option for output file
->option('-o, --outputfile', 'Output to file')
// option test
->option('-t, --test', 'Run test', ['optional'])
// add command action
->action(function(string $dir, ?array $subdirs, ?string $outputfile, ?bool $isTest) {
var_dump($dir, $subdirs, $outputfile, $isTest);
});
// $ php ./app/cli.php files mydir subdir1 subdir2 --outputfile=/my/file -t
// string(5) "mydir"
// array(2) { [0] => string(7) "subdir1" [1] => string(7) "subdir2" }
// string(8) "/my/file"
// bool(true)
The CLI Lark\Cli\Output
class is used for output and styling output.
$o = $cli->output();
// output green text
$o->colorGreen->echo('This is green text');
// use multiple styles
$o->colorBlue->styleUnderline->echo('More text');
// style methods for common styles
$o->error('Error'); // red background
$o->info('Info'); // blue text
$o->ok('Success'); // green text
$o->warn('Warning'); // yellow text
$o->dim('Muted'); // dim text
// custom style methods can be registered
$o::register('blink', function ($text, $end = PHP_EOL) use ($out) {
$out->styleBlink;
$out->echo($text, $end);
});
$o->bink('Blinking text'); // blinking text
// override existing style methods
$o::register('error', function ($text, $end = PHP_EOL) use ($out) {
$out->colorRed; // text color red (instead of bg red)
$out->stderr($text, $end); // send to stderr
});
$o->error('Oops'); // red text
The output grid()
method can be used to evenly space columns.
$data = [
[1, "one"],
[2, "two"],
[100, "one hundred"],
[3, "three"],
];
$out->grid($data, ['indent' => 2]);
Above example would output:
1 one
2 two
100 one hundred
3 three
Use confirm()
for prompting.
// "Continue? (y/N)"
if($cli->confirm("Continue?")) // ...
// or yes by default: "Continue? (Y/n)"
if($cli->confirm("Continue?", true)) // ...
Use input()
for input.
// "Enter value [DEFAULT]:"
$val = $cli->input("Enter value:", "DEFAULT");
// if no value is entered the value would be "DEFAULT"
abort($status = 0)
- display command aborted message and exit appcommand(string $name, string $description, array $aliases = []): Command
- register a commandconfirm(string $question, bool $isDefaultYes = false)
- confirm yes/noexit($status = 0)
- exit appheader(callable $callback)
- register a header callback used inthelp()
methodhelp()
- display help (auto invoked byCli
)input(string $text, $default = null)
- inputoption(string $option, string $description, callable $action)
- set global optionoutput()
- CLIOutput
object getterrun()
- run CLI app
action($callbackOrClassArray): Command
- set command actionarg(string $arg, string $description, array $options = []): Command
- set argument- Options:
array
- argument with multiple values (must be last in arguments list)default
- default value, like:['default' => 'the value']
optional
- argument is optional
- Options:
option(string $option, string $description = '', array $options = []): Command
-set option- Options:
default
- default value, like:['default' => 'the value']
- Options:
bgBlack
- style background blackbgBlue
- style background bluebgCyan
- style background cyanbgGray
- style background graybgGreen
- style background greenbgPurple
- style background purplebgRed
- style background redbgWhite
- style background whitebgYellow
- style background yellowbgLigthBlue
- style background light bluebgLightCyan
- style background light cyanbgLightGray
- style background light graybgLightGreen
- style background light greenbgLightPurple
- style background light purplebgLightRed
- style background light redbgLightYellow
- style background light yellowcolorBlack
- style color blackcolorBlue
- style color bluecolorCyan
- style color cyancolorGray
- style color graycolorGreen
- style color greencolorPurple
- style color purplecolorRed
- style color redcolorWhite
- style color whitecolorYellow
- style color yellowcolorLigthBlue
- style color light bluecolorLightCyan
- style color light cyancolorLightGray
- style color light graycolorLightGreen
- style color light greencolorLightPurple
- style color light purplecolorLightRed
- style color light redcolorLightYellow
- style color light yellowstyleBlink
- style blinkingstyleBold
- style boldstyleDim
- style dimstyleHidden
- style hiddenstyleInvert
- style invertstyleUnderline
- style underline
dim(string $text, string $end = PHP_EOL): Output
- print dim style textecho(string $text = '', string $end = PHP_EOL): Output
- Print text to stdouterror(string $text, string $end = PHP_EOL): Output
- print error textgrid(array $data, array $options = []): Output
- print grid- Options:
indent
- number of spaces to indentpadding
- column padding (default:4
)style
- apply style to column, like['style' => ['name' => 'colorBlue']]
- Options:
info(string $text, string $end = PHP_EOL): Output
- print info textok(string $text, string $end = PHP_EOL): Output
- print success textwarn(string $text, string $end = PHP_EOL): Output
- print warning textstatic register(string $name, callable $callback)
- register style methodstderr(string $text, string $end = PHP_EOL): Output
- output to stderrstdout(string $text = '', string $end = PHP_EOL): Output
- output to stdoutstyleIndent(int $number): Output
- indent style
Lark\File
is used to handle files.
use Lark\File;
$file = new File('./my-file.txt');
if($file->write('contents'))
{
// ...
}
$contents = $file->read();
Lark\Json\File
is used for JSON files.
use Lark\Exception as LarkException;
use Lark\Json\File as JsonFile;
$file = new JsonFile('./my-file.json');
$file->write(['name' => 'test']);
try
{
$value = $file->read();
}
catch(LarkException $ex)
{
// exception is throw on JSON decode error
echo 'Failed to decode JSON file: ' . $ex->getMessage();
}
delete(): bool
- delete a fileexists(): bool
- check if file existsexistsOrException()
- if file does not exist throw exceptionpath(): string
- file path getterread()
- read file contentswrite($data, $append = false, $lock = true): bool
- write file contents
Lark\Timer
works as a timer.
$timer = new Lark\Timer;
usleep(500000);
echo $timer->elapsed(); // 0.5001s
sleep(1);
echo $timer->elapsed(); // 1.5014s
sleep(2);
// get elapsed since last Timer::elapsed()
// or Timer::elapsedSinceLast() was invoked
echo $timer->elapsedSinceLast(); // 2.0003s
echo $timer->elapsed(); // 3.5018s
Helpers are global helper functions.
Access the main App
instance using the app()
function.
app()->use('[...]');
The db()
function is a database collection instance helper.
// when using default connection ID
// "[database]$[collection]"
$db = db('app$users');
// or "database", "collection"
$db = db('app', 'users');
// when using non-default connection ID
// "[connectionId].[database].[collection]"
$db = db('myDb$app$users')
// or "connectionId", "database", "collection"
$db = db('myDb', 'app', 'users');
// when using a App\Model class with DBS (database string)
$db = db(App\Model\User::class);
Read more in Database Connections.
The dbdatetime()
function returns a MongoDB\BSON\UTCDateTime
object.
$dbDt = dbdatetime();
$dt = $dbDt->toDateTime(); // DateTime object
// with milliseconds
$dbDt = dbdatetime(strtotime('-1 day') * 1000);
The debug()
function is a debugger and logging helper. When called the debug()
function will append the debugger info and send to logger (Logger::debug()
).
debug('test');
// same as:
// Debugger::append('test');
// (new Logger)->debug('test');
// title/name can be used:
debug('test', ['info' => 'here');
// same as:
// Debugger::append(['info' => 'here'])->name('test');
// (new Logger)->debug('test', ['info' => 'here']);
// title/name and group/channel can be used:
debug('test', ['info' => 'here'], 'name');
// same as:
// Debugger::append(['info' => 'here'])->name('test')->group('name');
// (new Logger('name'))->debug('test', ['info' => 'here']);
Also see
x()
helper function.
The env()
function is an environment variables helper.
$dbName = env('DB_NAME');
Read more in Environment Variables & Configuration.
The f()
function returns a formatted string.
echo f('First value: {}, second value: {}', 'one', 'two');
// First value: one, second value: two
// placeholder names can be used
echo f('Name: {name}, age: {age}', 'Test', 25);
// Name: Test, age: 25
// array keys and placeholder names can be used
// regardless of key/value order
echo f('Name: {name}, age: {age}', ['age' => 25, 'name' => 'Test']);
// Name: Test, age: 25
The halt()
function can be used to immediately return an HTTP response status code and optional JSON message.
halt(404, 'Resource not found');
// returns HTTP response status code 404
// with JSON body {"message": "Resource not found"}
// use custom JSON response
halt(500, ['error' => 'message', 'context' => ['test']]);
// halt without message
halt(500);
Access a Logger
instance using the logger()
function.
logger('channel')->info('message', ['context']);
Read more in Logging.
The p()
function outputs formatted (HTML/CLI) variables.
p('test', 'this');
p(['my' => 'array']);
The pa()
function is a variable printer.
pa('test', 'this', ['and' => 'this'], 'end');
// test this
// Array
// (
// [and] => this
// )
// end
Access the Lark\Request
instance using the req()
function.
var_dump(
req()->path()
);
// string(1) "/"
Read more in Request.
Access the Lark\Response
instance using the res()
function.
res()->contentType('application/json');
Read more in Response.
Access the Lark\Router
instance using the router()
function.
router()->get('/', function() {
return 'home';
});
Read more in Routing.
The x()
function is a debugger and dumper helper. When called the x()
function (or Lark\Debugger::dump()
) will dump all debugger info objects and stop execution.
x('value', ['test' => 'this']);
Also see
debug()
helper function.
Read more in Debugger.