To utilize the ORM and database functionality in your application, Spiral offers the spiral/cycle-bridge component.
This component is automatically included in the spiral/app
and can also be installed into existing projects through
the use of Composer by executing the following command:
composer require spiral/cycle-bridge
After successful installation of the package, it is necessary to add the Spiral\Cycle\Bootloader\BridgeBootloader
bootloader to the Kernel:
public function defineBootloaders(): array
{
return [
// ...
\Spiral\Cycle\Bootloader\BridgeBootloader::class,
// ...
];
}
Read more about bootloaders in the Framework — Bootloaders section.
Alternatively, for more granular control, the BridgeBootloader
can be excluded and selected the only needed
bootloaders.
The relevant code for this in Kernel would look like this:
use Spiral\Cycle\Bootloader as CycleBridge;
public function defineBootloaders(): array
{
return [
// ...
// Database
CycleBridge\DatabaseBootloader::class,
CycleBridge\MigrationsBootloader::class,
// Close the database connection after every request automatically (Optional)
// CycleBridge\DisconnectsBootloader::class,
// ORM
CycleBridge\SchemaBootloader::class,
CycleBridge\CycleOrmBootloader::class,
CycleBridge\AnnotatedBootloader::class,
CycleBridge\CommandBootloader::class,
// Validation (Optional)
// CycleBridge\ValidationBootloader::class,
// DataGrid (Optional)
// CycleBridge\DataGridBootloader::class,
// Database Token Storage (Optional)
CycleBridge\AuthTokensBootloader::class,
// Migrations and Cycle Scaffolders (Optional)
CycleBridge\ScaffolderBootloader::class,
// Prototyping (Optional)
CycleBridge\PrototypeBootloader::class,
];
}
Read more about bootloaders in the Framework — Bootloaders section.
The bootloader serves the purpose of automatically closing the database connection after every request in long-running applications. This is an optional bootloader and can be included or excluded as per the requirements of the specific application.
The configuration for database services is located in app/config/database.php
configuration file. In this file, you
may define all of your database connections, as well as specify which connection should be used by default. Most of the
configuration options within this file are driven by the values of your
application's environment variables.
Here is an example configuration file that defines a database connection:
use Cycle\Database\Config;
return [
'logger' => [
'default' => env('DB_LOGGER_DEFAULT'),
'drivers' => [
// 'runtime' => 'stdout'
],
],
'default' => env('DB_DEFAULT', 'default'),
/**
* The Spiral/Database module provides support to manage multiple databases
* in one application, use read/write connections and logically separate
* multiple databases within one connection using prefixes.
*
* To register a new database simply add a new one into
* "databases" section below.
*/
'databases' => [
'default' => [
'driver' => 'runtime',
],
],
/**
* Each database instance must have an associated connection object.
* Connections used to provide low-level functionality and wrap different
* database drivers. To register a new connection you have to specify
* the driver class and its connection options.
*/
'drivers' => [
'runtime' => new Config\MySQLDriverConfig(
connection: new Config\MySQL\TcpConnectionConfig(
database: 'homestead',
host: '127.0.0.1',
port: 3307,
user: 'root',
password: 'secret',
),
queryCache: true
),
// ...
],
];
Warning
Please note that using SQLite as the database backend with multiple workers can present significant challenges and limitations. It is important to be aware of these limitations to ensure proper functionality and performance of your application. Rad more about this in the SQLite limitations section.
See more
Read more about the configuration of the database in the Database - Installation and Configuration on the official site.
The configuration for Spiral framework's ORM services is located in your application's app/config/cycle.php
use Cycle\ORM\SchemaInterface;
return [
'schema' => [
/**
* true (Default) - Schema will be stored in a cache after compilation.
* It won't be changed after entity modification. Use `php app.php cycle` to update schema.
*
* false - Schema won't be stored in a cache after compilation.
* It will be automatically changed after entity modification. (Development mode)
*/
'cache' => false,
/**
* The CycleORM provides the ability to manage default settings for
* every schema with not defined segments
*/
'defaults' => [
SchemaInterface::MAPPER => \Cycle\ORM\Mapper\Mapper::class,
SchemaInterface::REPOSITORY => \Cycle\ORM\Select\Repository::class,
SchemaInterface::SCOPE => null,
SchemaInterface::TYPECAST_HANDLER => [
\Cycle\ORM\Parser\Typecast::class
],
],
'collections' => [
'default' => 'array',
'factories' => [
'array' => new \Cycle\ORM\Collection\ArrayCollectionFactory(),
// 'doctrine' => new \Cycle\ORM\Collection\DoctrineCollectionFactory(),
// 'illuminate' => new \Cycle\ORM\Collection\IlluminateCollectionFactory(),
],
],
/**
* Schema generators (Optional)
* null (default) - Will be used schema generators defined in bootloaders
*/
'generators' => null,
// 'generators' => [
// \Cycle\Schema\Generator\ResetTables::class,
// \Cycle\Annotated\Embeddings::class,
// \Cycle\Annotated\Entities::class,
// \Cycle\Annotated\TableInheritance::class,
// \Cycle\Annotated\MergeColumns::class,
// \Cycle\Schema\Generator\GenerateRelations::class,
// \Cycle\Schema\Generator\GenerateModifiers::class,
// \Cycle\Schema\Generator\ValidateEntities::class,
// \Cycle\Schema\Generator\RenderTables::class,
// \Cycle\Schema\Generator\RenderRelations::class,
// \Cycle\Schema\Generator\RenderModifiers::class,
// \Cycle\Annotated\MergeIndexes::class,
// \Cycle\Schema\Generator\GenerateTypecast::class,
// ],
],
/**
* Prepare all internal ORM services (mappers, repositories, typecasters...)
*/
'warmup' => false,
];
CycleORM is a powerful and flexible Object-Relational Mapping (ORM) tool for PHP that allows developers to interact with databases in an object-oriented way. It provides a range of features that make it easy to work with data including a flexible configuration options, a powerful query builder and support for dynamic mapping of schemas.
It supports a variety of popular relational databases such as MySQL, MariaDB, PostgresSQL, SQLServer, and SQLite.
Note
Full documentation is available on the official site CycleORM.
You can access the ORM instance from the container by using the Cycle\ORM\ORMInterface
interface.
Let's imagine that we have a User
entity
use Cycle\Annotated\Annotation as Cycle;
#[Cycle\Entity(repository: UserRepository::class)]
class User
{
// ...
}
with a UserRepository
repository.
class UserRepository extends \Cycle\ORM\Select\Repository
{
public function findByEmail(string $email): ?User
{
return $this->findOne(['email' => $email]);
}
}
You can request a repository from the ORM instance by yourself, by providing the entity or role name.
use Cycle\ORM\ORMInterface;
use Cycle\ORM\RepositoryInterface;
class UserService
{
private readonly RepositoryInterface $repository;
public function __construct(
Cycle\ORM\ORMInterface $orm
) {
$this->repository = $orm->getRepository(User::class);
}
public function getProfile(string $email): User
{
$user = $this->repository->findOne(['email' => $email]);
// ...
}
}
You can also request a repository from the container. The framework uses IoC injections to
inject repositories into your code that implement Cycle\ORM\RepositoryInterface
.
class UserService
{
public function __construct(
private readonly UserRepository $repository
) {
}
public function getProfile(string $email): User
{
$user = $this->repository->findByEmail($email);
// ...
}
}
When you request a repository from the container, Spiral will automatically request the repository from the ORM and associate it with the correct Entity.
To persist entity changes, your application services and controllers will require Cycle\ORM\EntityManagerInterface
.
By default, the framework will automatically create a transaction on-demand from the container. Considering that
transactions always clean after the operation run
, you can request it as a constructor parameter.
See more
You can read more about transactions in the CycleORM documentation.
Here is an example of a service that uses the EntityManagerInterface
:
use Cycle\ORM\EntityManagerInterface;
class UserService
{
public function __construct(
private readonly EntityManagerInterface $entityManager
) {
}
public function create(string $name, string $email): User
{
$user = new User($name, $email);
$this->entityManager->persist($user);
$this->entityManager->run();
return $user;
}
}
Note:
Make sure that thepersist/delete
and run methods are always called within the same method scope while using service-specific transactions.
The Cycle bridge provides a CycleBridge\ValidationBootloader bootloader that registers additional checkers for the spiral/validator package. This bootloader includes two additional validation rules, which enhance the functionality of the validator and allow for more efficient and effective data validation within the application.
Check if an entity with a given role and primary key exists.
By default, a rule will check if an entity exists by the primary key.
use Spiral\Filters\Attribute\Input\Post;
use Spiral\Filters\Attribute\Setter;
use Spiral\Filters\Model\Filter;
use Spiral\Filters\Model\FilterDefinitionInterface;
use Spiral\Filters\Model\HasFilterDefinition;
final class StoreUser extends Filter implements HasFilterDefinition
{
#[Post]
#[Setter(filter: 'intval')]
public int $id;
public function filterDefinition(): FilterDefinitionInterface
{
return new FilterDefinition([
'username' => [
[
'entity::exists',
\App\Entity\User::class // Entity role
]
]
]);
}
}
You can also specify the field name and the value, which will be used to check if the entity exists.
use Spiral\Filters\Model\Filter;
use Spiral\Filters\Model\FilterDefinitionInterface;
use Spiral\Filters\Model\HasFilterDefinition;
final class UpdateUser extends Filter implements HasFilterDefinition
{
#[Post]
public string $username;
public function filterDefinition(): FilterDefinitionInterface
{
return new FilterDefinition([
'username' => [
[
'entity::exists',
\App\Entity\User::class, // Entity role
'username', // Field name
],
],
]);
}
}
Check if an entity with a given role is unique.
use Spiral\Filters\Attribute\Input\Post;
use Spiral\Filters\Attribute\Setter;
use Spiral\Filters\Model\Filter;
use Spiral\Filters\Model\FilterDefinitionInterface;
use Spiral\Filters\Model\HasFilterDefinition;
final class StoreUser extends Filter implements HasFilterDefinition
{
#[Post]
public string $username;
public function filterDefinition(): FilterDefinitionInterface
{
return new FilterDefinition([
'username' => [
[
'entity::unique',
\App\Entity\User::class, // Entity role
'username', // Field name
]
]
]);
}
}
If you want to use cycle/entity-behavior package in your application, you need to install it first:
composer require cycle/entity-behavior
After that, you need to bind Cycle\ORM\Transaction\CommandGeneratorInterface
with \Cycle\ORM\Entity\Behavior\EventDrivenCommandGenerator
in the application container:
namespace App\Application\Bootloader;
use Cycle\ORM\Transaction\CommandGeneratorInterface;
use Cycle\ORM\Entity\Behavior\EventDrivenCommandGenerator;
use Spiral\Boot\Bootloader\Bootloader;
final class EntityBehaviorBootloader extends Bootloader
{
protected const BINDINGS = [
CommandGeneratorInterface::class => \Cycle\ORM\Entity\Behavior\EventDrivenCommandGenerator::class,
];
}
And finally, you need to register the App\Application\Bootloader\EntityBehaviorBootloader
in the application kernel:
public function defineBootloaders(): array
{
return [
// ...
\App\Application\Bootloader\EntityBehaviorBootloader::class,
// ...
];
}
Read more about bootloaders in the Framework — Bootloaders section.
That's it! Now you can use entity behaviors in your application.
The Cycle ORM integration provides a Spiral\Cycle\Interceptor\CycleInterceptor
interceptor that allows you to
automatically resolve entities in controller methods by their primary key.
Note:
Read more about using interceptors in the HTTP — Interceptors section.
To activate interceptor:
namespace App\Application\Bootloader;
use Spiral\Cycle\Interceptor\CycleInterceptor;
use Spiral\Bootloader\DomainBootloader;
use Spiral\Core\CoreInterface;
class AppBootloader extends DomainBootloader
{
protected const SINGLETONS = [
CoreInterface::class => [self::class, 'domainCore']
];
protected const INTERCEPTORS = [
CycleInterceptor::class,
// ...
];
}
After that you can use a cycle entity injection in your controller methods:
namespace App\Endpoint\Web;
use App\Entity\User;
use Spiral\Router\Annotation\Route;
final class HomeController
{
#[Route('/users/<user>')]
public function index(User $user)
{
dump($user);
}
}
Note:
If an entity can't be found the 404 exception will be thrown.
Cycle ORM aims to make the usage of the library in daemonized applications, such as PHP workers running under RoadRunner or Swoole, simpler. The ORM provides multiple options to avoid memory leaks, which can also be applied to batch operations. This helps ensure the stability and efficiency of the application when executing long-running processes.
The package will automatically clean the heap after each request. If you need to clean the heap manually, you can use the following methods:
use Cycle\ORM\ORMInterface;
class UserService
{
public function __construct(
private readonly ORMInterface $orm
) {
}
public function create(string $name, string $email): User
{
// Create a new user
$this->orm->getHeap()->clean();
}
}
The Cycle ORM integration provides multiple commands for easier control. You can get help for any of the commands using
php app.php help cycle...
Note
Make sure to enableSpiral\Cycle\Bootloader\CommandBootloader
after the cycle bootloaders to activate helper commands.
Command | Description |
---|---|
db:list [db] |
Gets a list of available databases, their tables and records count.db database name. |
db:table <table> |
Describes a table schema of a specific database.table A table name (required).--database A source database. |
Command | Description |
---|---|
cycle |
Updates (init) the cycle schema from the database and annotated classes. |
cycle:migrate |
Generates the ORM schema migrations.--run Automatically runs a generated migration. |
cycle:render |
Renders the available CycleORM schemas.--no-color Displays output without colors. |
Note
You can run any cycle command with the-vv
flag to see a list of modified tables.
You can access your databases in controllers and services in several ways:
use Cycle\Database\DatabaseProviderInterface;
final class UserService
{
public function __construct(
private readonly DatabaseProviderInterface $dbal
) {}
public function store(): void
{
// Default database
dump($this->dbal->database());
// Using alias default which points to primary database
dump($this->dbal->database('default'));
// Secondary
dump($this->dbal->database('slave'));
}
}
The DBAL component fully supports IoC injections based on the database name and their aliases:
use Cycle\Database\DatabaseInterface;
public function store(
DatabaseInterface $database,
DatabaseInterface $primary,
DatabaseInterface $slave
): void {
// Database is an alias for "primary"
dump($database === $primary);
dump($primary);
dump($slave);
}
Access Cycle\Database\DatabaseProviderInterface
and default database instance using PrototypeTrait
:
final class UserService
{
use PrototypeTrait;
public function store(): void
{
dump($this->dbal);
dump($this->db); // default db
}
}
To run the database query, use the method query
:
dump(
$db->query('SELECT * FROM users WHERE id > ?', [
1
])->fetchAll()
);
To execute an update or delete statement, use the alternative method execute
:
dump(
$db->execute('DELETE FROM users WHERE id > ?', [
1,
]) // number of affected rows
);
Note
Read how to use query builders here.
Spiral provides the capability to log database queries through the use of the spiral/logger
component.
This component uses Monolog as its default logging driver.
See more
Read more about the logger in the The Basics — Logging section.
The database logging drivers can be configured in the logger
section of app/config/database.php
configuration file.
return [
'logger' => [
'default' => null,
'drivers' => [],
],
// ...
];
If no logger driver is defined, the channel with the current database driver name will be used. If the SQLite driver is
used to execute queries, the monolog channel name will automatically be set
to Cycle\Database\Driver\SQLite\SQLiteDriver
.
In this case, you can configure a monolog handler for the channel Cycle\Database\Driver\SQLite\SQLiteDriver
in order
to log all the database queries.
This can be done by adding the following code in the app/config/monolog.php
file:
return [
'handlers' => [
// ...
\Cycle\Database\Driver\SQLite\SQLiteDriver::class => [
[
'class' => 'log.rotate',
'options' => [
'filename' => directory('runtime') . 'logs/db.log',
'level' => Logger::DEBUG,
],
],
],
],
// ...
];
The drivers
section of the logger
is used to specify which database driver should use the log channel specified
by the key.
Let's consider the following database configuration:
return [
'logger' => [
'drivers' => [
'runtime' => 'console'
],
],
'databases' => [
'default' => [
'driver' => 'runtime',
],
],
'drivers' => [
'runtime' => new Config\SQLiteDriverConfig(...),
// ...
],
];
We can use the logger configuration array to map the runtime
database driver to the console
log channel.
And the following monolog config:
return [
'handlers' => [
//...
'console' => [
\Monolog\Handler\ErrorLogHandler::class,
],
],
];
With these configurations, every time you use the runtime
database driver, its logs will be sent to
the console
channel.
You can also point a specific database driver to a specific log channel. For example, you can have a separate log channel for your SQLite database and another for your MySQL database. This way, you can monitor each database's logs individually and troubleshoot any issues specific to each database. This helps you identify and fix issues quickly, without having to sift through a large, complex log file.
return [
'logger' => [
'drivers' => [
\Cycle\Database\Driver\MySQL\MySQLDriver::class => 'db_logs',
\Cycle\Database\Driver\SQLite\SQLiteDriver::class => 'console'
],
],
];
In this case every time when you will use SQLiteDriver
database driver it will send logs to the console
log channel.
You can also set the default
key to a specific log channel. This will be used as the default log channel for all
queries
executed by the database drivers that are not specified in the drivers
section.
return [
'logger' => [
'default' => 'console',
],
];
By setting a default log channel, it ensures that even if no specific log channel has been defined for a particular database driver, there is still a channel available for logging.
For example, if a developer sets the default log channel as console
, any database driver without a specific log
channel assigned to it will have its logs directed to the console
channel. This provides a fallback for situations
where a log channel has not been defined, ensuring that all logs are captured.
The default Web and GRPC bundles include a set of console commands to view the database schema.
Activate the bootloader Spiral\Cycle\Bootloader\CommandBootloader
in your application:
public function defineBootloaders(): array
{
return [
// ...
\Spiral\Cycle\Bootloader\CommandBootloader::class,
// ...
];
}
Read more about bootloaders in the Framework — Bootloaders section.
To view available databases, drivers, and tables:
php app.php db:list
The output:
+------------+------------+---------+---------+-----------+---------+----------------+| Name (ID): | Database: | Driver: | Prefix: | Status: | Tables: | Count Records: |+------------+------------+---------+---------+-----------+---------+----------------+| default | runtime.db | SQLite | --- | connected | users | 0 || | | | | | posts | 0 |+------------+------------+---------+---------+-----------+---------+----------------+
To view the details about a particular table:
php app.php db:table posts
The output:
Columns of default.posts:+---------+----------------+----------------+-----------+----------------+| Column: | Database Type: | Abstract Type: | PHP Type: | Default Value: |+---------+----------------+----------------+-----------+----------------+| id | int | primary | int | --- || title | string (255) | text | string | --- || user_id | int | integer | int | --- |+---------+----------------+----------------+-----------+----------------+Indexes of default.posts:+-----------------------------------+-------+----------+| Name: | Type: | Columns: |+-----------------------------------+-------+----------+| posts_index_user_id_5e32b9642a0ff | INDEX | user_id |+-----------------------------------+-------+----------+Foreign Keys of default.posts:+------------------+---------+----------------+-----------------+------------+------------+| Name: | Column: | Foreign Table: | Foreign Column: | On Delete: | On Update: |+------------------+---------+----------------+-----------------+------------+------------+| posts_user_id_fk | user_id | users | id | CASCADE | CASCADE |+------------------+---------+----------------+-----------------+------------+------------+
When you're working with Cycle ORM and need to adapt your database to changes in your application's entities, the
cycle/migrations
package provides a convenient way to generate and manage your migrations. This process compares
your current database schema with your entity changes and creates the necessary migration files.
After you've made changes to your entities or created new ones, you'll need to generate migration files that reflect
those changes. To do this, run the following command in your console:
php app.php cycle:migrate
The new migration files will be created in the app/migrations directory.
Warning
Before executing the above command, make sure to apply any existing migrations by runningphp app.php migrate
. This ensures your database schema is up-to-date.
After you generate migration files, they need to be applied to your database to make the actual changes.
php app.php migrate
This command applies the latest generated migrations. When you run it, Cycle ORM updates your database schema according to the changes described in your migration files.
Cycle ORM provides several commands to manage migrations at different stages and for different purposes:
php app.php migrate:replay
This command is useful for re-running migrations. It first rolls back (or "downs") migrations, then runs them again (or "ups"). It can be handy when you want to quickly test changes made in your migrations.
Options:
--all
: This option will replay all migrations, not just the last one.php app.php migrate:rollback
If you need to undo migrations, this is the command you'll use. It reverses the changes made by the migrations. By default, it rolls back the last migration.
Options:
--all
: This option will rollback all migrations, not just the last one.php app.php migrate:status
Want to see what's been done and what's pending? This command shows you a list of all migrations along with their statuses—whether they've been applied or not.
Here's an example of the output:
+-----------------------------------------------------------+---------------------+---------------------+| Migration | Created at | Executed at |+-----------------------------------------------------------+---------------------+---------------------+| 0_default_create_auth_tokens | 2023-09-25 16:45:13 | 2023-10-04 10:46:11 || 0_default_create_user_role_create_user_roles_create_users | 2023-09-26 21:22:16 | 2023-10-04 10:46:11 || 0_default_change_user_roles_add_read_only | 2023-09-27 13:53:19 | 2023-10-04 10:46:11 |+-----------------------------------------------------------+---------------------+---------------------+
php app.php migrate:init
Before you can start using migrations, the migrations component needs a place to record which migrations have been run. This command sets up that tracking by creating a migrations table in your database.
Note
This command is run automatically when you first runphp app.php migrate
.
If you want to customize how Cycle ORM handles migrations, you can create a configuration file
at app/config/migration.php.
This file allows you to set various options, such as the location of migration files or
the name of the migrations table in the database.
Here's what you can set in the configuration file:
Here is an example of config file
use Cycle\Schema\Generator\Migrations\Strategy\SingleFileStrategy;
use Cycle\Schema\Generator\Migrations\NameBasedOnChangesGenerator;
return [
/**
* Directory to store migration files
*/
'directory' => directory('app').'migrations/',
/**
* Table name to store information about migrations status (per database)
*/
'table' => 'migrations',
/**
* Migration file generator strategy
*/
'strategy' => SingleFileStrategy::class,
/**
* Migration file name generator
*/
'nameGenerator' => NameBasedOnChangesGenerator::class,
/**
* When set to true no confirmation will be requested on migration run.
*/
'safe' => env('APP_ENV') === 'production',
];
With version 2.6.0 of spiral/cycle-bridge
, you can choose how the migration files are generated:
This strategy combines all changes into a single migration file each time you run the command. This is useful if you prefer to group all changes in one place.
<?php
declare(strict_types=1);
namespace Migration;
use Cycle\Migrations\Migration;
class OrmDefaultA42b7e366d78543ca8c5a4b60d305083 extends Migration
{
protected const DATABASE = 'default';
public function up(): void
{
$this->table('user_roles')
->addColumn('created_at', 'datetime', ['nullable' => false, 'default' => 'CURRENT_TIMESTAMP'])
// ...
->setPrimaryKeys(['uuid'])
->create();
$this->table('users')
->addColumn('created_at', 'datetime', ['nullable' => false, 'default' => 'CURRENT_TIMESTAMP'])
//...
->setPrimaryKeys(['uuid'])
->create();
}
public function down(): void
{
$this->table('users')->drop();
$this->table('user_roles')->drop();
}
}
With this approach, changes are separated into different files based on the table name. This means if you have changes for different tables, each table's changes go into its own file.
<?php
declare(strict_types=1);
namespace Migration;
use Cycle\Migrations\Migration;
class OrmDefaultA42b7e366d78543ca8c5a4b60d305083 extends Migration
{
protected const DATABASE = 'default';
public function up(): void
{
$this->table('user_roles')
->addColumn('created_at', 'datetime', ['nullable' => false, 'default' => 'CURRENT_TIMESTAMP'])
// ...
->setPrimaryKeys(['uuid'])
->create();
}
public function down(): void
{
$this->table('user_roles')->drop();
}
}
<?php
declare(strict_types=1);
namespace Migration;
use Cycle\Migrations\Migration;
class OrmDefaultA42b7e366d78543ca8c5a4b60d305043 extends Migration
{
protected const DATABASE = 'default';
public function up(): void
{
$this->table('users')
->addColumn('created_at', 'datetime', ['nullable' => false, 'default' => 'CURRENT_TIMESTAMP'])
//...
->setPrimaryKeys(['uuid'])
->create();
}
public function down(): void
{
$this->table('users')->drop();
}
}
You also have the option to create a custom migration strategy by implementing
the Cycle\Schema\Generator\Migrations\Strategy\GeneratorStrategyInterface
.
With version 2.6.0 of spiral/cycle-bridge
, you can choose configure the default naming strategy for migration
files.
By default, Spiral uses Cycle\Schema\Generator\Migrations\NameBasedOnChangesGenerator
that takes into account all the
changes in the migration file to create a unique name. But you can create a custom filename generation strategy by
implementing the Cycle\Schema\Generator\Migrations\NameGeneratorInterface
.
When utilizing multiple workers in a web application, it is important to consider how concurrent access to resources such as databases is managed. SQLite, while a reliable and lightweight database solution, poses limitations when it comes to concurrent access by multiple workers.
Firstly, SQLite databases are file-based, meaning they are stored as a single file on disk. This design choice can create issues when multiple workers attempt to access the same SQLite database simultaneously. SQLite utilizes file-level locks to maintain data integrity, allowing only one writer to modify the database file at any given time. As a result, if multiple workers attempt to write to the database concurrently, they will experience contention and potential locking issues, leading to reduced performance and potential data corruption.
Additionally, SQLite does not provide a built-in mechanism to share an in-memory database between multiple processes or workers. In-memory databases are often used for performance optimization, as they eliminate disk I/O operations. However, because SQLite cannot share an in-memory database between processes, each worker would have its own separate copy of the database in memory. This means that any updates made by one worker would not be visible to the other workers, leading to inconsistencies and incorrect results.
Considering these limitations, when using Spiral Framework or any other framework that employs multiple workers, it is advisable to explore alternative database solutions that better support concurrent access. Popular options include client-server databases like MySQL or PostgreSQL, which are designed to handle concurrent connections and provide better scalability in multi-worker environments.