1. Go to this page and download the library: Download reves/val library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
// in index.php
App::run(function() {}, "/custom/root/dir");
// Application entry point, both for View and API.
// Note: the anonymous function represents the View, has nothing to do with APIs.
App::run(function() {
echo 'Hello, World!';
});
App::isApiRequest(); // === isset($_GET['_api'])
App::isProd();
// true: only "config/env.php" exists
// false: only "config/env.dev.php" exists
// false: both "config/env.php" and"config/env.dev.php" exist
App::exit(); // closes the DB connection (if any) and terminates the script execution.
// e.g. in api/Books.php
use Val\Api;
Final Class Books Extends Api
{
public function __invoke()
{
$this->onlyGET();
// e.g. getting the list of books from database ...
$list = [
['title' => 'Bright Days', 'author' => 'John Doe', 'year' => 2005],
['title' => 'The Shadows', 'author' => 'Michael Smith', 'year' => 2006],
['title' => 'The Last Ember', 'author' => 'Jonathan Blake', 'year' => 2008],
];
// Sends a response with status "200" and JSON-encoded $list data.
// The "return" is optional, but recommended, especially when using
// Api::peek() internal calls.
return $this->success($list);
}
}
// e.g. in api/Books.php
Final Class Books Extends Api
{
public function __invoke() {/*...*/}
public function byAuthor()
{
$this->onlyGET()->, function($v) use ($author) {
return $v['author'] === $author;
});
return $this->success($result);
}
}
// e.g. in public/index.php
Val\App::run(function() {
echo '<pre>';
print_r(Val\Api::peek('/books')); // prints the returned $list from __invoke()
echo '</pre>';
});
public function byAuthor() {
$this->onlyGET();
// ...
}
public function subscribe() {
$this->onlyPOST();
// ...
}
public function update() {
$this->onlyPOST()->onlyAuthenticated();
// ...
}
public function claimFirstTimeDiscount() {
$this->onlyPOST()->onlyUnauthenticated();
// ...
}
public function list() {
$this->onlyGET()->
public function addComment() {
$this->onlyPOST()
->onlyAuthenticated()
->is->val('age'); // integer
// ...
}
// e.g. validation method, which is called automatically (if defined):
// Convention: protected function validate<Fieldname>($value)
protected function validateName(&$name) // `&` means that field value will be modified
{
$name = trim($name);
if (!$name) return 'EMPTY_VALUE';
if (mb_strlen($name) > 100) {
return ['TOO_LONG', ['max' => 100]];
// ... or manually:
// $this->setInvalid('name', 'TOO_LONG', ['max' => 100]);
}
}
// e.g. type conversion inside validation
protected function validateAge(&$age)
{
$age = intval(trim($age));
// ...
}
public function register() {
$this->onlyPOST()
->onlyUnauthenticated();
->
// ...
}
// e.g. validation method for the grouped fields 'firstName' and 'lastName':
// Convention: <Fieldname> is the name of the first field in the grouping array
protected function validateFirstName($name) {/*...*/} // same validator for both fields
public function register() {
$this->onlyPOST()->onlyUnauthenticated()->f defined):
// ...
}
protected function validatePassword($pw)
{
// e.g. getting the value of another field inside a validation method:
$email = $this->val('email');
if ($pw === $email) return 'PASSWORD_MATCHES_EMAIL';
}
public function register() {
$this->onlyPOST()->onlyUnauthenticated()-> $this->setInvalid('email', 'EMPTY_VALUE');
}
//...
}
public function list()
{
//...
return $this->success($list); // status 200 and JSON-encoded $list in the response body
}
public function addImage()
{
//...
return $this->success(); // status 200
}
public function addImage()
{
//...
if (!$savedOnDisk) return $this->error(); // status 500 by default
//...
}
public function addImage()
{
//...
if (!$savedInDatabase) return $this->error(500, 'CUSTOM_STATUS or a verbose message.');
//...
}
// e.g. in api/Account.php
public function signIn()
{
$this->onlyPOST()
->onlyUnauthenticated()
->
return Auth::initSession($accountId); // creates a new session in database and sets the session cookie
? $this->respondSuccess()
: $this->respondError();
}
// e.g. in api/Account.php
public function signOut()
{
$this->onlyPOST()
->onlyAuthenticated();
return Auth::revokeSession() // deletes the session from database and removes the cookie
? $this->respondSuccess()
: $this->respondError();
}
// e.g. in api/Account.php
public function signOutFromAllDevices()
{
$this->onlyPOST()
->onlyAuthenticated();
return Auth::revokeAllSessions()
? $this->respondSuccess()
: $this->respondError();
}
// e.g. in api/Account.php
public function signOutFromOtherDevices()
{
$this->onlyPOST()
->onlyAuthenticated();
return Auth::revokeOtherSessions()
? $this->respondSuccess()
: $this->respondError();
}
// in a cron job (e.g. for cases when the user never signed in again)
Auth::removeExpiredSessions($accountId);
// e.g. in api/Account.php
public function isAuthenticated()
{
$this->onlyGET();
return $this->respondData([
'isAuthenticated' => (Auth::getAccountId() !== null)
]);
}
if (Auth::getAccountId() !== null) {
$dateTime = Auth::getSignedInAt() // e.g. '2025-01-01 00:00:00'
}
// In config/app.php
'foo' => 'bar',
// ... then
$value = Config::app('foo'); // 'bar'
// Get env variable (automatically chooses from which one - env.php or env.dev.php)
$value = Config::env('test');
// In config/custom.php
return [
'something' => Config::env('test'), // get value from the current env
'foofoo' => Config::app('foo') . 'bar', // get value from another config
];
// ... then
$value = Config::custom('foofoo'); // 'barbar'
$isSet = Cookie::isSet('cookiename');
$value = Cookie::get('cookiename'); // may return empty string if the cookie is not set
$options = [ // these are also the default options values:
'expires' => 0,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
];
$result1 = Cookie::set('name1', 'value1'); // use default options
$result2 = Cookie::set('name2', 'value2', $options);
$result3 = Cookie::set('name3', 'value3', ['httponly' => false]); // change a specific option
$result = Cookie::unset('cookiename');
$result = Cookie::setForDays('cookiename', 'value'); // 1 day
$result = Cookie::setForDays('cookiename', 'value', 7, ['httponly' => false]); // 7 days, with a custom option
$encrypted = Crypt::encrypt('an important message'); // may return null
$decrypted = Crypt::decrypt($encrypted); // may return null
DB::beginTransaction();
// ...
DB::commit();
DB::beginTransaction();
// ...
if ($somethingHappened) {
DB::rollback(); // cancels the current transaction
}
DB::transactionIsActive(); // `false`
// ...
DB::commit(); // will commit only if the transacrion is still active, safe to use here
$count = DB::raw("DELETE FROM books");
$id = DB::lastInsertId();
$db = DB::prepare("SELECT title FROM books"); // returns the DB instance, for convenience
$db = DB::prepare("SELECT title FROM books WHERE author = :author AND published = :published")
->bind(':author', 'John Doe')
->bind(':published', true);
$db = DB::prepare("SELECT title FROM books WHERE author = ? AND published = ?")
->bind(1, 'John Doe')
->bind(2, true);
$db = DB::prepare("SELECT title FROM books WHERE author = ? AND published = ?")
->bindPlaceholder('John Doe') // autoindex to 1
->bindPlaceholder(true); // autoindex to 2
$db = DB::prepare("SELECT title FROM books WHERE author = :author AND published = :published")
->bindMultiple([
':author' => 'John Doe',
':published' => true,
]); // uses DB::bind() for each entry
$db = DB::prepare("SELECT title FROM books WHERE author = ? AND published = ?")
->bindMultiple([
1 => 'John Doe',
2 => true,
]); // uses DB::bind() for each entry
$db = DB::prepare("SELECT title FROM books WHERE author = ? AND published = ?")
->bindMultiple(['John Doe', true]); // uses DB::bindPlaceholder() for each entry,
// when detects an array with key `0`
DB::beginTransaction();
$result = DB::prepare("INSERT INTO books (title, author, published)
VALUES (:title, :author, :published)")
->bind('title', 'The Cool Title') // ->bind() still applicable
->execute([
':author' => 'John Doe',
':published' => false,
]); // passes optional $relations to DB::bindMultiple() and then executes
if ($result) {
$bookId = DB::lastInsertId();
// ...
} else {
DB::rollback();
// handle the error ...
}
DB::commit();
$result = DB::prepare("DELETE FROM books WHERE title = ?")
->execute(['The Cool Title']);
if (DB::rowCount()) {
// Successfully deleted...
} else {
// No rows affected...
}
$result = DB::prepare("SELECT title FROM books WHERE author = ? AND published = ?")
->single(['John Doe', true]);
if ($result) {
$title = $result['title'];
// ...
} else {
// No result, or error ...
}
$rows = DB::prepare("SELECT title FROM books WHERE author = ? AND published = ?")
->resultset(['John Doe', true]);
if (count($rows)) {
// ...
} else {
// No result
}
$data = JSON::decode($json); // may return null in case of error
$lang = Lang::get(); // 'en'
// Unsets the language, or if the supported languages list is specified, sets
// to the first one in the list.
$result = Lang::set('');
$lang = Lang::get(); // 'fr'
// Set the language to "English (United States)".
$result = Lang::set('en-US');
// Example setting the path to a custom templates directory.
// By default, the path is `App::$DIR_VIEW` (meaning the "view/" directory).
Renderer::setPath(App::$DIR_ROOT . '/mytemplates');
// Generating a UUID Version 7 (RFC 9562).
$uuid = UUID::generate(); // may return `null`
/**
* Generating a TOTP (Time-based one-time password) secret key for the user.
*/
$secretKey = TwoFactorAuth::generateSecretKey();
/**
* Storing user's TOTP secret key securely in the database.
*/
$encryptedSecretKey = Crypt::encrypt($secretKey);
// [...]
/**
* Generating an URI that may be further sent to the frontend and encoded into
* a QR code, so the user can scan it with his favorite Authenticator app.
*/
$appName = 'My App'; // your app's name, e.g. 'Example.com', 'app', ...
$accountName = '[email protected]'; // user's account name, e.g. 'john_doe', 'John Doe', ...
$URI = TwoFactorAuth::createURI($secretKey, $appName, $accountName);
/**
* Later on, the user enters the code generated by his Authenticator app.
* Getting the user's secret key from the database and veryfing the code.
*/
// [...]
$secretKey = Crypt::decrypt($encryptedSecretKey); // may return `null`
$code = $this->val('code');
$result = TwoFactorAuth::verify($secretKey, $code); // returns `true` if the code is correct
$secret = '<SECRET>'; // your Turnstile secret key
$response = '<CLIENT-RESPONSE>'; // response token from the frontend
$result = Captcha::Turnstile($secret, $response); // makes an HTTP request, may return `null`
// In config/env.php ...
'hcaptcha_secret' => '<SECRET>',
// In config/app.php ...
'hcaptcha_secret' => Config::env('hcaptcha_secret'),
// Somewhere in your API's method ...
$secret = Config::app('hcaptcha_secret');
$response = $this->val('hcaptcha_response');
$sitekey = 'optional-site-key';
$result = Captcha::hCaptcha($secret, $response, $sitekey);