Revision: Thu, 18 Apr 2024 09:24:13 GMT

Installing Cycle ORM

This guide is a beginner-friendly walkthrough on installing and configuring Cycle ORM. We'll also touch upon the basics of setting up an entity. More detailed use-cases are available in other sections of the documentation.

Requirements

Ensure your setup meets the following prerequisites:

  • PHP 8.0+
  • PHP-PDO
  • Necessary PDO drivers for your chosen databases

Package installation

Step 1: Install the ORM

Cycle ORM can be seamlessly integrated into your project with Composer. In your project's root directory, execute:

composer require cycle/orm

Step 2: Attributes

To define a schema for Cycle ORM using PHP attributes, you can use the package cycle/annotated. Its current version requires PHP 8.1. If you are using PHP 8.0, then version 3.x of the package will be installed. In this case, you will need to refer to the configuration for this version below.

To install the package - execute:

composer require cycle/annotated

Note:
This step is optional. If you prefer, you can define your mapping schema manually.

Step 3: Include the Autoloader

Before you get started, ensure your bootstrap includes the autoload vendor/autoload.php generated by Composer:

php
index.php
<?php declare(strict_types=1);
include 'vendor/autoload.php';

Configuration

First of all, you'll need to configure a database connection. The Cycle\Database\DatabaseManager service, a part of the cycle/database package, serves as your go-to solution.

Here's how you can set up your first database connection:

php
index.php
<?php declare(strict_types=1);
include 'vendor/autoload.php';

use Cycle\Database;
use Cycle\Database\Config;

$dbal = new Database\DatabaseManager(
    new Config\DatabaseConfig([
        'default' => 'default',
        'databases' => [
            'default' => ['connection' => 'sqlite']
        ],
        'connections' => [
            'sqlite' => new Config\SQLiteDriverConfig(
                connection: new Config\SQLite\MemoryConnectionConfig(),
                queryCache: true,
            ),
        ]
    ])
);

Note:
To explore more on connecting to other databases, visit Connect to Database section.

You can check your database connection using the following code:

php
index.php
// ...

print_r($dbal->database('default')->getTables());

And run the script:

php index.php

The output should contain an array of tables in your database.

Entity schema declaration

Mapping entities in Cycle ORM can be approached in multiple ways, ensuring flexibility based on your project's requirements. This section provides an overview of two predominant methods:

  • Entity attributes.
  • Custom schema definitions.

Start by defining your first entity within the src directory of your project. Next, ensure Composer recognizes your namespace:

json
composer.json
"autoload": {
  "psr-4": {
    "Example\\": "src/"
  }
}

Afterwards, refresh your autoload:

composer du

Now you can create your first entity. Let's call it User:

php
src/User.php
<?php

declare(strict_types=1);

namespace Example;

class User
{
    private int $id;

    public function __construct(
        private string $name,
    ) {
    }

    // getters and setters...
}

Entity Attributes

Attributes, introduced in PHP 8, provide a declarative way to specify metadata directly in the entity classes. They are a concise and expressive method for entity mapping. In most cases, they are the preferred way to define your entity schema.

Note:
Attributes won't degrade your application's performance. They are only used during the schema compilation process.

Let's add attributes to our User entity:

php
src/User.php
<?php

declare(strict_types=1);

namespace Example;

use Cycle\Annotated\Annotation\Entity;
use Cycle\Annotated\Annotation\Column;

#[Entity]
class User
{
    #[Column(type: "primary")]
    private int $id;

    public function __construct(
        #[Column(type: "string")]
        private string $name,
    ) {
    }

    // getters and setters...
}

In this example:

  • The #[Entity] attribute marks the class as an entity. It's required for all entities.
  • The #[Column] attribute specifies the column type for the respective properties.

Note:
Read more about available attributes in the Annotated Entities section.

After defining your entities using attributes, you'll usually combine this with a schema generator to translate these declarations into a usable ORM schema.

Schema Generator

In order to operate we need to generate an ORM Schema which will describe how our entities are configured. Though we can do it manually, we will use the schema compiler and generators provided by cycle/schema-builder package, and also generators from cycle/annotated package.

Initializing the Class Locator

First, we have to create instance of Spiral\Tokenizer\ClassLocator which will automatically find the required entities:

php
index.php
//...

$finder = (new \Symfony\Component\Finder\Finder())->files()->in([__DIR__]); // __DIR__ here is folder with entities
$classLocator = new \Spiral\Tokenizer\ClassLocator($finder);

To verify that your class is detectable (remember, ClassLocator statically indexes your code):

php
print_r($classLocator->getClasses());

Compiling the Schema

With the class locator set, you can piece together your schema compilation pipeline. This is the sequence through which your entities pass, undergoing transformations, validations, and finally, integration into the ORM schema.

php
index.php
use Cycle\Schema;
use Cycle\Annotated;
use Cycle\Annotated\Locator\TokenizerEmbeddingLocator;
use Cycle\Annotated\Locator\TokenizerEntityLocator;

$embeddingLocator = new TokenizerEmbeddingLocator($classLocator);
$entityLocator = new TokenizerEntityLocator($classLocator);

$schema = (new Schema\Compiler())->compile(new Schema\Registry($dbal), [
    new Schema\Generator\ResetTables(),             // Reconfigure table schemas (deletes columns if necessary)
    new Annotated\Embeddings($embeddingLocator),    // Recognize embeddable entities
    new Annotated\Entities($entityLocator),         // Identify attributed entities
    new Annotated\TableInheritance(),               // Setup Single Table or Joined Table Inheritance
    new Annotated\MergeColumns(),                   // Integrate table #[Column] attributes
    new Schema\Generator\GenerateRelations(),       // Define entity relationships
    new Schema\Generator\GenerateModifiers(),       // Apply schema modifications
    new Schema\Generator\ValidateEntities(),        // Ensure entity schemas adhere to conventions
    new Schema\Generator\RenderTables(),            // Create table schemas
    new Schema\Generator\RenderRelations(),         // Establish keys and indexes for relationships
    new Schema\Generator\RenderModifiers(),         // Implement schema modifications
    new Schema\Generator\ForeignKeys(),             // Define foreign key constraints
    new Annotated\MergeIndexes(),                   // Merge table index attributes
    new Schema\Generator\SyncTables(),              // Align table changes with the database
    new Schema\Generator\GenerateTypecast(),        // Typecast non-string columns
]);

In our example, we use SyncTables generator to automatically update the database schema. Read more about schema synchronization in the Synchronizing Database Schema section.

Warning:
The SyncTables generator will modify your database structure in line with your schema. Do not execute this on a production database without understanding its implications!

With the schema compiled, it can be integrated into the ORM:

php
index.php
use Cycle\ORM;

$schema = /** ... */;

$orm = new ORM\ORM(new ORM\Factory($dbal), new ORM\Schema($schema));

It's best practice to cache the generated schema in production and regenerate it only when required, ensuring optimal performance.

For debugging or review, you can dump the $schema variable, giving you a detailed look into your entity schema's internal representation. For more human-friendly rendering, consider using the cycle/schema-renderer package.

Updating Entity Schema

As your application grows, there may be times when you need to adjust your entity schema, such as adding new columns or changing existing ones. To do so, simply modify your entity and re-run the script.

php
#[Column(type: "int", nullable: true)]
protected ?int $age = null;

Upon your next script execution, the SyncTables generator will automatically update the schema to reflect these changes.

Custom Definition of Entity Schema

For projects that require a more dynamic or programmatic approach to entity mapping, Cycle ORM provides the ability to define entity schema programmatically.

Warning:
Remember, automatic database migrations aren't available in this mode.

The mapping for User entity can be explicitly defined as:

php
index.php
use Cycle\ORM;
use Cycle\ORM\Mapper\Mapper;

$dbal = /** ... */;

$orm = new ORM\ORM(new ORM\Factory($dbal), new ORM\Schema([
    'user' => [
        ORM\Schema::MAPPER => Mapper::class, // default POPO mapper
        ORM\Schema::ENTITY => User::class,
        ORM\Schema::DATABASE => 'default',
        ORM\Schema::TABLE => 'users',
        ORM\Schema::PRIMARY_KEY => 'id',

        // property => column
        ORM\Schema::COLUMNS => [
            'id' => 'id',
            'name' => 'name',
        ],

        ORM\Schema::TYPECAST => [
            'id' => 'int',
        ],

        ORM\Schema::RELATIONS => [],
    ]
]));

This method offers more control as it allows for dynamic and complex mappings. However, it can also be more verbose and might abstract away some of the clarity that attributes provide.

Note:
Read more about available schema options in the Manually defined Mapping Schema section.

For a deeper dive into various mapping techniques, consult the dedicated sections in the ORM documentation, such as dynamic mapping.

Working with Entities

Cycle ORM offers an intuitive and seamless way to interact with your entities, handling persistence, retrieval, updating, and deletion tasks with ease. Once you've properly configured your ORM, the Cycle\ORM\EntityManager will become the main tool for these operations. Below, we provide a walkthrough of common entity tasks.

Note:
Read more about Crud operations in the Create, Update and Delete section.

Setting up Entity Manager

Before you delve into operations, ensure you've instantiated the EntityManager:

php
index.php
use Cycle\ORM\EntityManager;

$orm = /** ... */;

$em = new EntityManager($orm);

Creating and Persisting Entities

To persist an entity, simply create a new instance and register it with the EntityManager:

php
index.php
// ...

$user = new \Example\User("Antony");
$em->persist($user)->run();

You can immediately dump the object to see newly generated primary key:

php
index.php
// ...

print_r($user);

Fetching Entities

Entities can be easily fetched from the database using their primary key through the appropriate repository:

php
index.php
// ...

$user = $orm->getRepository(\Example\User::class)->findByPK(1);
print_r($user);

Note:
Read more about using repository in the Select Entity section.

Warning:
Remove the code from the section above to avoid fetching the entity from memory.

Updating Entities

Updating an entity's data is straightforward: modify the desired properties, then persist the changes using the EntityManager:

php
index.php
// ...

$user = /**  */;
$user->setName("John");

$em->persist($user)->run();
print_r($user);

Deleting Entities

Removing an entity from the database is executed through the delete method of the EntityManager:

php
index.php
// ...

$user = /**  */;
$em->delete($user)->run();

Conclusion

Cycle ORM stands out as a robust and flexible tool for managing database operations in PHP applications. Its straightforward configuration process makes it approachable for both newcomers and experienced developers alike. Beyond its simplicity, its modular design and extensible architecture ensure that as your application grows and evolves, Cycle ORM can adapt and scale alongside it.

Edit this page