PHPackages                             lark/framework - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. lark/framework

ActiveLibrary[Framework](/categories/framework)

lark/framework
==============

Lark Framework

0.27.0(2y ago)32613MITPHP

Since Jul 27Pushed 2y ago1 watchersCompare

[ Source](https://github.com/shayanderson/lark-framework)[ Packagist](https://packagist.org/packages/lark/framework)[ Docs](https://github.com/shayanderson/lark-framework)[ RSS](/packages/lark-framework/feed)WikiDiscussions 0.x Synced 1mo ago

READMEChangelog (10)Dependencies (1)Versions (31)Used By (3)

Lark Framework
==============

[](#lark-framework)

Lark is a modern, lightweight app framework designed specifically for developing REST APIs.

- [Installation](#installation)
- [Routing](#routing)
    - [Routes](#routes)
    - [Route Parameters](#route-parameters)
    - [Route Actions](#route-actions)
    - [Middleware](#middleware)
- [Logging](#logging)
- [Exception Handling](#exception-handling)
- [Debugger](#debugger)
- [Configuration &amp; Bindings](#configuration--bindings)
- [Environment Variables &amp; Configuration](#environment-variables--configuration)
- [Request](#request)
- [Response](#response)
- [Database](#database)
- [Schema](#schema)
- [Model](#model)
- [Validator](#validator)
    - [Validation Types &amp; Rules](#validation-types--rules)
- [Filter](#filter)
- [HTTP Client](#http-client)
- [CLI](#cli)
- [File](#file)
- [Timer](#timer)
- [Helpers](#helpers)
    - [`app()`](#helper-app), [`db()`](#helper-db), [`dbdatetime()`](#helper-dbdatetime), [`debug()`](#helper-debug), [`env()`](#helper-env), [`f()`](#helper-f), [`halt()`](#helper-halt), [`logger()`](#helper-logger), [`p()`](#helper-p), [`pa()`](#helper-pa), [`req()`](#helper-req), [`res()`](#helper-res), [`router()`](#helper-router), [`x()`](#helper-x)

Installation
------------

[](#installation)

Requirements:

- PHP 8
- PHP extensions
    - Required
    - \[...\]
    - Optional
    - [curl](https://www.php.net/manual/en/book.curl.php) - if using `Lark\Http\Client`

#### Composer Install

[](#composer-install)

```
composer require lark/framework

```

Routing
-------

[](#routing)

The router is used to dispatch route actions and middleware.

```
// bootstrap
// ...

// define routes
router()
    // get([route], [action])
    ->get('/', function() {});

// run app
app()->run();
```

### Routes

[](#routes)

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

[](#regular-expression-routes)

Regular expression routes use [PCRE](https://www.php.net/manual/en/book.pcre.php) patterns for matching routes.

```
// match all routes that begin with "/api"
router()->get('/api.*?', function(){});
```

#### Route Groups

[](#route-groups)

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 Group Loading

[](#route-group-loading)

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(){});
```

#### Route Controller

[](#route-controller)

A route controller object can be used with [Route Groups](#route-groups) and [Route Group Loading](#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

[](#route-actions)

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']);
```

### Route Not Found Action

[](#route-not-found-action)

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 Parameters

[](#route-parameters)

#### Named Parameters

[](#named-parameters)

Route named parameters are required parameters that do not use regular expressions. Multiple name parameters are allowed.

```
router()->get('/users/{id}', function($id){});
```

#### Optional Named Parameters

[](#optional-named-parameters)

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 Expression Parameters

[](#regular-expression-parameters)

Regular expressions can be used to define parameters using [PCRE](https://www.php.net/manual/en/book.pcre.php) 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

[](#middleware)

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](#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 Middleware

[](#route-middleware)

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 Execution Order

[](#middleware-execution-order)

Middleware is always executed in the following order:

1. Always execute (`router()->bind(...)`)
2. Execute mapped on matched route (`router()->map(...)`)
3. Execute on matched route (`router()->matched(...)`)
4. After middleware (`router()->after(...)`)

#### Route Group Middleware

[](#route-group-middleware)

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 Middleware

[](#after-middleware)

After middlware always runs after a route action has been called, even if the route does not exist.

```
router()->after(function(){}, [MyClass::class, 'myMethod']);
```

Logging
-------

[](#logging)

`Lark\Logger` is used for logging. The helper function [`logger()`](#helper-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]
```

Exception Handling
------------------

[](#exception-handling)

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;
        });
    }
}
```

Debugger
--------

[](#debugger)

`Lark\Debugger` can be used for debugging. The helper functions [`debug()`](#helper-debug) and [`x()`](#helper-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
```

Configuration &amp; Bindings
----------------------------

[](#configuration--bindings)

Framework configuration and bindings can be set with the `use()` method.

### Debugging

[](#debugging)

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

[](#database-connections)

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');
```

> Read more in [Database](#database) and helper function [`db()`](#helper-db).

### Database Global Options

[](#database-global-options)

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](https://www.mongodb.com/docs/manual/reference/write-concern/) and in the [PHP docs](https://www.php.net/manual/en/class.mongodb-driver-writeconcern.php).

### Database Sessions

[](#database-sessions)

Sessions can be stored in the database using a `Lark\Model` object.

```
app()->use('db.session', new App\Model\Session);
```

### Validator Custom Rules

[](#validator-custom-rules)

Custom validator rules can be registered using `validator.rule.[type].[ruleClassName]`.

```
app()->use('validator.rule.string.beginWithEndWith', App\Validator\BeginWithEndWith::class);
```

Environment Variables &amp; Configuration
-----------------------------------------

[](#environment-variables--configuration)

`Lark\Env` is used for app environment variables and configuration. The helper function [`env()`](#helper-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`.

Request
-------

[](#request)

`Lark\Request` provides HTTP request data with input sanitizing. The helper function [`req()`](#helper-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 code `400` 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() );
}
```

### Request Session

[](#request-session)

`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
```

### Request Methods

[](#request-methods)

- `body(bool $convertHtmlEntities = true): string` - request raw body data getter
- `contentType(): string` - content-type getter
- `cookie(string $key, $default = null): Lark\Request\Cookie` - cookie input object getter
- `hasHeader(string $key): bool` - check if header key exists
- `header(string $key): string` - header value getter
- `headers(): array` - get all headers
- `host(): string` - HTTP host value getter, like `www.example.com`
- `input(string $key, $default = null): Lark\Request\Input` - input object getter for `POST`
- `ipAddress(): string` - IP address getter
- `isContentType(string $contentType): bool` - validate request content-type
- `isMethod(string $method): bool` - validate request method
- `isSecure(): bool` - check if request is secure (HTTPS)
- `json()` - JSON request body getter
- `jsonArray(): array` - JSON request body getter, must be array or `400` HTTP status code response
- `jsonField(string $field, $default = null): Lark\Request\Json` - JSON request field object getter
- `jsonObject(): array` - JSON request body getter, must be object or `400` HTTP status code response
- `method(): string` - request method getter
- `path(): string` - path getter, like `/the/path`
- `pathWithQueryString(): string` - path with query string getter, like `/the/path?x=1`
- `port(): int` - port getter
- `query(string $key, $default = null): Lark\Request\Query` - query input object getter for `GET`
- `queryString(): string` - query string getter, like `x=1&y=2`
- `scheme(): string` - URI scheme getter, like `http`
- `session(): Lark\Request\Session` - session object getter
- `uri(): string` - URI getter, like `http://example.com/example?key=x`

### Request Input Methods

[](#request-input-methods)

Input methods include methods for request input objects: `Cookie`, `Input` and `Query`.

- `email(array $options = [])` - value getter, sanitize as email
- `float(array $options = ['flags' => FILTER_FLAG_ALLOW_FRACTION])` - value getter, sanitize as float
- `has(): bool` - check if key exists
- `integer(array $options = [])` - value getter, sanitize as integer
- `string(array $options = [])` - value getter, sanitize as string
- `url(array $options = [])` - value getter, sanitize as URL

### Session Methods

[](#session-methods)

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 key
- `static cookieOptions(array $options)` - set cookie options
    - default options are: `['lifetime' => 0, 'path' => '/', 'domain' => '', 'secure' => false, 'httponly' => false]`
- `destroy()` - destroy a session
- `static exists(): bool` - check if sessions are enabled and session exists
- `get(string $key)` - value getter
- `has(string $key): bool` - check if key exists
- `id(): ?string` - session ID getter
- `isSession(): bool` - check if session exists
- `set(string $key, $value)` - key/value setter
- `toArray(): array` - session array getter

Response
--------

[](#response)

`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}
```

### Response Methods

[](#response-methods)

- `cacheOff(): Lark\Response` - disable cache using cache-control
- `contentType(string $contentType): Lark\Response` - content-type setter
- `cookie($key, $value, $expires, $path, $domain, $secure, $httpOnly): bool` - cookie setter
- `cookieClear(string $key, string $path = '/'): bool` - remove cookie
- `header(string $key, $value): Lark\Response` - header setter
- `headerClear(string $key): Lark\Response` - remove header key
- `headers(array $headers): Lark\Response` - headers setter using array
- `json($data)` - respond with JSON payload (and content-type `application/json` in headers)
- `redirect(string $location, bool $statusCode301 = false)` - send redirect
- `send($data)` - respond with raw data payload
- `code(int $code): Lark\Response` - response status code setter

Database
========

[](#database)

`Lark\Database` is used to access MongoDB database and collection instances. The helper function [`db()`](#helper-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

[](#insert-documents)

```
// 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

[](#find-documents)

```
// 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

[](#update-documents)

```
// 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 Documents

[](#replace-documents)

```
// 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

[](#delete-documents)

```
// 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();
```

#### Collection Field Methods

[](#collection-field-methods)

```
// 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`.

### Database Methods

[](#database-methods)

- `collectionField(string $field): Database\Field` - collection field object getter
- `count(array $filter = [], array $options = []): int` - count documents matching filter
- `delete(array $filter, array $options = []): int` - delete documents matching filter
- `deleteAll(array $options = []): int` - delete all documents
- `deleteIds(array $ids, array $options = []): int` - delete documents by ID
- `deleteOne(array $filter, array $options = []): int` - delete single document matching filter
- `drop(): bool` - drop collection
- `exists(): bool` - check if collection exists
- `find(array $filter = [], array $options = []): array` - find documents matching filter
- `findId($id, array $options = []): ?array` - find document by ID
- `findIds(array $ids, array $options = []): array` - find documents by ID
- `findOne(array $filter = [], array $options = []): ?array` - find single document matching filter
- `has(array $filter, array $options = []): bool` - check if documents matching filter exist
- `hasIds(array $ids, array $options = []): bool` - check if documents with IDs exist
- `insert(array $documents, array $options = []): array` - insert documents
- `insertOne($document, array $options = []): ?string` - insert single document
- `ping(): bool` - ping command
- `replaceBulk(array $documents, array $options = []): int` - bulk replace
- `replaceId($id, $document, array $options = []): int` - replace single document
- `replaceOne(array $filter, $document, array $options = []): int` - replace single document
- `update(array $filter, $update, array $options = []): int` - update documents matching filter
- `updateBulk(array $documents, array $options = []): int` - bulk update
- `updateId($id, $update, array $options = []): int` - update document by ID
- `updateOne(array $filter, $update, array $options = []): int` - update single document matching filter

#### Database Field Methods

[](#database-field-methods)

- `create($defaultValue = null): int` - create field with default value
- `delete(): int` - delete collection field
- `exists(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 array
- `push(array $filter, $value, $unique = true): int` - append value to field array, if `$unique` will only append value if doesn't already exist in field array
- `rename(string $newName): int` - rename field

Schema
------

[](#schema)

`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 &amp; Rules](#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](https://www.mongodb.com/docs/manual/reference/method/db.collection.createIndex/#options).

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);
});
```

### Field Schema Imports

[](#field-schema-imports)

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`.

```
