Application code
- Configuration
- Routes
- Controllers
- Views
- Localization
- Models
- BaseModel
- Query builder
- Migrations
- Seeders
- CLI commands
- Tests
The application specific code is located in the directories app
, resources
and tests
.
The configuration of the application is done in the file.env
.
You can use the file .env.example
as template.
If you run the application as a container, you can also set them as environment variables (e.g. --env DB_PASSWORD=myPassword
).
APP_URL="http://localhost" # URL the application is listening on
APP_ENV="dev" # 'dev' or 'prod'
APP_LANG="en" # Default/fallback language
DB_CONNECTION="mariadb"
DB_HOST="127.0.0.1"
DB_PORT=3306
DB_DATABASE="bioman"
DB_USERNAME="user"
DB_PASSWORD="secret"
# Or SQLite
DB_CONNECTION="sqlite"
DB_FILE="path/bioman.db"
The available routes are configured in the app/routes.php
file.
There are multiple ways for configuration:
/** Render the view 'about' (/resources/Views/about.php) */
Router::addFn('about', fn() => view('about'));
Hint: You can use the bioman CLI to create a new empty controller:
make:controller
The controller class must implement the Framework\Routing\ControllerInterface
.
/** The function 'execute()' is called in the AboutController class */
Router::addController('about', new AboutController());
Hint: You can use the bioman CLI to create a new empty controller:
make:controller
The controller must implement the Framework\Routing\ModelControllerInterface
.
/** Routes: user, user/show, user/create, user/store, etc. */
Router::addResource('user', new UserController());
If the corresponding function exists in the controller class, the associated route gets registered:
Function | Route | Methode | Description |
---|---|---|---|
public function index(): void |
<base route> |
GET |
Show list of all models |
public function show(): void |
<base route>/show?id=... |
GET |
Show the details of one model |
public function create(): void |
<base route>/create |
GET |
Show form to create one model |
public function store(): void |
<base route>/store |
POST |
Create a new model with the informations from the create form |
public function edit(): void |
<base route>/edit?id=... |
GET |
Show form to edit one model |
public function update(): void |
<base route>/update |
POST |
Update one model with the informations from the edit form |
public function destroy(): void |
<base route>/destroy |
POST |
Delete one model |
Hint: You can use the bioman CLI to create a new empty controller:
make:controller
A custom controller must implement the ControllerInterface
or the ModelControllerInterface
depending on the kind of route registration used. The BaseController
can be used as base class to get access to helper functions.
The default directory is app/Http/Controllers
.
Example
use Framework\Routing\BaseController;
class AboutController extends BaseController
The views must be PHP files. The default directory is resources/Views
. The location for components is resources/Views/Components
.
If a view is placed inside a subdirectory the dot is used as seprator.
For example: resources/Views/user/index.php
=> user.index
A view can be rendered with the global available function view(string $name, ...)
. For components the global available function component(string $name, ...)
can be used.
The localization system provides the global available translation function __(string $labelname)
.
The labels are stored in PHP arrays (<language code>.php
e.g. de.php
) in resources/Lang
.
Example en.php
<?php
return [
'AddProduct' => 'Produkt hinzufügen',
...
'WelcomeToMemberArea' => 'Willkommen im Mitgliederbereich',
];
Hint: You can use the bioman CLI to create a new empty command:
make:model
A model can be used as a wrapper for a database table or to encapsulte application logic.
If you extend your class from the BaseModel
, you get access to helper functions.
These already implement deletion, finding multiple or single instances, etc.
Your table scheme should use the column id
as primary key in order to use the BaseModel.
See BaseModel
The BaseModel
provides logic to simplify the usage of a model that is stored in the database.
The BaseModel assumes that the identifier column is named id
.
/** Get an empty query builder */
public static function getQueryBuilder(): WhereQueryBuilder;
/** Get an array of models */
public static function all(WhereQueryBuilder $query = null): array;
/** Get the first model that matches the given query */
public static function find(WhereQueryBuilder $query): self;
/** Get the model with the given id */
public static function findById(int $id): self;
/** Delete the model with the given id */
public static function delete(int $id): void;
/** Set the given fields from the the eponymous HTTP parameters */
public function setFromHttpParams(array $fields): self;
/** Set the given field from the the eponymous HTTP parameter */
public function setFromHttpParam(string $field, string $param = null): self;
Example model
use Framework\Database\BaseModel;
use Framework\Database\Database;
class Role extends BaseModel
A model class is a wrapper for a database table. The name of the table must be the plural of the class name so that the model can resolve the table name.
Examples:
Model: User
=> Table: users
Model: Product
=> Table: products
To access the fields of a model the magic get- and set-functions can be used: <get/set><field name>
Examples:
Field: name
=> Getter: getName(): mixed
, Setter: setName(mixed $value): self
Field: isLocked
=> Getter: getIsLocked(): mixed
, Setter: setIsLocked(mixed $value): self
Recommendation: Add explicit functions for type safety. You can use the helper getter and setter like
getDataString
,getDataIntOrNull
,setDataFloat
,setDataBoolOrNull
, etc.
Example explicit getter and setter
private const USERNAME = 'username';
/* Getter & Setter */
public function setUsername(string $value): self
{
return $this->setDataString(self::USERNAME, $value);
}
public function getPassword(): ?string
{
return $this->getDataStringOrNull(self::PASSWORD);
}
The allow functions are optional functions that can be added to a model for doing checks before the edit or delete operation. Possible checks could be permission checks to ensure that the current user is allowed to delete that entity. Another useful check is a dependency checks. Here you can check if a invoice is aleady paid and prevent the edit and delete operation in that case.
Signatures
/* Gets executed if the `save()` function is called */
public function allowEdit(): bool;
/** Gets executed if the `delete($id)` function is called */
public function allowDelete(): bool;
Examples
public function allowEdit(): bool
{
return match (true) {
$this->getIsPaid() => false,
// More checks...
default => true,
};
}
public function allowDelete(): bool
{
if ($this->getIsPaid()) {
return false;
}
// More checks...
return true;
}
You can use the (Where)QueryBuilder to set the sort order when calling Model::all(...)
. This way you must set that order possibly on multiple locations in your source code.
You can use the static property orderBy
to set a default order.
It will be used if no other sort order is passed via QueryBuilder->orderBy(...)
.
Example
class Plot extends BaseModel
{
public static array $orderBy = ['isLocked' => 'asc', 'nr' => 'asc'];
The query builder can be used to create SQL select statements.
By default all columns are selected (*
).
If you want to some specific columns, you can add them with the function select
.
Available functions on query builder
/** Add a column to the select `SELECT` part */
public function select(string $column): self;
/** Add a condition to the `WHERE` part */
public function where(ColType $type, string $column, Condition $condition, mixed $value, WhereCombine $combine = WhereCombine::And): self;
/** Add a column to the `ORDER BY` part */
public function orderBy(string $column, SortOrder $sortOrder = SortOrder::Asc): self;
If you use the query builder for a model, you can use the return value from the function getTableName
as constructor parameter.
Example
$dataSet = Database::executeBuilder(
QueryBuilder::new(self::getTableName())
->select('MAX(nr) + 1 AS nextId')
->where(ColType::Int, 'year', Condition::Equal, $year)
);
The WhereQueryBuilder is used as (optional) parameter for the base model functions all
and find
. It only implements the functions where
and orderBy
.
The creation of the WhereQueryBuilder is simplified with the function getQueryBuilder
provided by the base model.
Example
self::find(self::getQueryBuilder()->where(ColType::Str, 'username', Condition::Equal, $username));
Hint: You can use the bioman CLI to create a new migration:
make:migration
The migration system has the purpose to get rid of manual changes on the database schema. A migration consists of one or more SQL statement that e.g. creates, deletes or alters a table.
A migration is only executed once. To execute every newly added migration, run the migrate
command in the bioman CLI.
Example
use Framework\Database\Database;
use Framework\Database\Migration\Migration;
return new class extends Migration
{
public function run(): void
{
// code
...
Each DBSM has its own specific syntax.
The blueprint class CreateTableBlueprint
can be used for the creation of a new table in MariaDB and SQLite.
Each supported database driver implements the CreateTableBlueprintInterface
.
Example
use Framework\Database\CreateTableBlueprint;
use Framework\Database\Database;
use Framework\Database\Migration\Migration;
return new class extends Migration
{
public function run(): void
{
Database::executeBlueprint((new CreateTableBlueprint('users'))
->id()
->string('name', 100)
->date('lastLogin')
->int('languageId', foreignKey: ['languages' => 'id'])
->timestamps()
);
}
};
Hint: You can use the bioman CLI to create a new empty seeder:
make:seeder
A seeder can be used to fill a table with random or fixed values.
The seeder class can be called in DatabaseSeeder.php
.
The default seeders in DatabaseSeeder.php
are executed when executing the bioman CLI command db:seed
.
Another way to use a seeder is to call it inside a migration, for example after the user table creation to insert a default user.
Example
use Framework\Database\Database;
use Framework\Database\Seeder\Seeder;
use Framework\Database\Seeder\SeederInterface;
class UserSeeder extends Seeder implements SeederInterface
{
public function run(): void
{
// code
...
Hint: You can use the bioman CLI to create a new empty command:
make:command
The bioman
CLI can be extended with custom commands.
The registration of a command must be done in app/registerCli.php
.
For example: Cli::registerCommand(new TestRun());
The command class must implement the CommandInterface
.
The BaseCommand
class can be used as base class to get access to helper functions.
Example
use Framework\Cli\BaseCommand;
use Framework\Cli\CommandInterface;
use Framework\Test\TestRunner;
class TestRun extends BaseCommand implements CommandInterface
Hint: You can use the bioman CLI to create a new test case:
make:testcase
You can create unit tests to ensure that the application behaves like expected, even after changing some functions behaviour.
To execute the test suite run the test:run
command in the bioman CLI.
Example
This test case checks if the boolToInt
convertion function always returns the value 1
for the input value true
.
use Framework\Facades\Convert;
use Framework\Test\TestCase;
class TestConvert extends TestCase
{
public function testBoolToInt(): void
{
$this->assertEquals(1, Convert::boolToInt(true));
$this->assertEquals(0, Convert::boolToInt(false));
}
}