free-tech

Getting Started With Doctrine ORM


This tutorial will teach you how to do the following from scratch (no frameworks, just raw files/code):

  1. Setup and install Doctrine.
  2. Configure Doctrine for a database connection.
  3. Create our first "entity".
  4. Use the Doctrine CLI tools to initialize the database.
  5. Use doctrine to create and return records in that database.


Setup and Install Doctrine

To get started, lets first create a structure for our project.
mkdir -p my-project/public_html
mkdir -p my-project/entities
mkdir -p my-project/config
mkdir -p my-project/data

touch my-project/public_html/index.php
touch my-project/bootstrap.php
touch my-project/entities/User.php
touch my-project/config/cli-config.php


Install Doctrine

Now lets install doctrine through composer
cd my-project
composer require doctrine/orm

For this tutorial, I am using an autoloader package I created years ago, for automatically loading classes by name. You can include it by running:
composer require irap/autoloader


Configure Doctrine for a Database Connection.

Now let's fill in our bootstrap.php file with the following content to tell Doctrine what kind of a database we are using, and what the relevant connection details are. In this tutorial, we are configuring Doctrine to use an SQLite database to make it easy to follow along, but you could easily swap out the driver for MySQL or PostgreSQL etc..

Information about the Doctrine drivers and what configuration details they need can be found here.

<?php

// include the composer autoloader for autoloading packages
require_once(__DIR__ . '/vendor/autoload.php');

// set up an autoloader for loading classes that aren't in /vendor
// $classDirs is an array of all folders to load from
$classDirs = array(
    __DIR__,
    __DIR__ . '/entities',
);

new \iRAP\Autoloader\Autoloader($classDirs);

function getEntityManager() : \Doctrine\ORM\EntityManager
{
    $entityManager = null;

    if ($entityManager === null)
    {
        $paths = array(__DIR__ . '/entities');
        $config = \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration($paths);

        # set up configuration parameters for doctrine.
        # Make sure you have installed the php7.0-sqlite package.
        $connectionParams = array(
            'driver' => 'pdo_sqlite',
            'path'   => __DIR__ . '/data/my-database.db',
        );

        $entityManager = \Doctrine\ORM\EntityManager::create($connectionParams, $config);
    }

    return $entityManager;
}

If you were going to bootstrap a PostgreSQL database, it would be something like below:
$dbParams = array(
    'driver'         => 'pdo_pgsql',
    'user'           => 'user1',
    'password'       => 'my-awesome-password',
    'host'           => 'postgresql.mydomain.com',
    'port'           => 5432,
    'dbname'         => 'myDbName',
    'charset'        => 'UTF-8',
);
The main bit you care about is this part which has the configuration variables required for the database and sets up the entity manager which can be used later.
function getEntityManager() : \Doctrine\ORM\EntityManager
{
    $entityManager = null;

    if ($entityManager === null)
    {
        $paths = array(__DIR__ . '/entities');
        $config = \Doctrine\ORM\Tools\Setup::createAnnotationMetadataConfiguration($paths);

        # set up configuration parameters for doctrine.
        # Make sure you have installed the php7.0-sqlite package.
        $connectionParams = array(
            'driver' => 'pdo_sqlite',
            'path'   => __DIR__ . '/data/my-database.db',
        );

        $entityManager = \Doctrine\ORM\EntityManager::create($connectionParams, $config);
    }
    return $entityManager;
}
I put the creation of the entity manager in a function so that you can call that function from outside bootstrap.php and you know what you are getting. E.g.
$em = getEntityManager();
I feel that this is cleaner than just leaving $entityManager "hanging loose" and wondering where that came from when used in other scripts. You also don't have to type-hint $entityManager to tell your IDE what it is this way either. Also, you can call that function as many times as you like from anywhere within your stack and you will still get the same single instance of the entity manager.

Create Our First Entity
The docs state that entities are PHP Objects that can be identified over many requests by a unique identifier.
Entities don't have to be a direct 1:1 mapping/relationship to the database tables, but it's easiest to think of them that way when getting started, as they will be 99% of the time.
Almost every project has a user table to represent its users, so lets create a User entity.
<?php

/**
 * @Entity @Table(name="user")
 **/

class User
{
    /** @Id @Column(type="integer") @GeneratedValue **/
    protected $id;
    /** @Column(type="string") **/
    protected $name;
    /** @Column(type="string") **/
    protected $email;
    public function __construct(string $name, sring $email)
    {
        $this->name = $name;
        $this->email = $email;
    }
    # Accessors
    public function getId() : int { return $this->id; }
    public function getName() : string { return $this->name; }
    public function getEmail() : string { return $this->email; }
}


The annotations such as @Entity @Table(name="user") are what tell Doctrine later how to set up the database so don't remove them!
Initialize The Database
Setup CLI Config

We will use the Doctrine CLI tools to setup our database. Unfortunately, they will not work straight out of the box, so we need to fill in our cli-config.php file we created earlier.

Copy/paste the following content into the file before saving.
<?php
require_once(__DIR__ . '/../bootstrap.php');

$entityManager = getEntityManager();
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);

We have already done most of the heavy lifting in the bootstrap.php file. All we really needed was to get the entity manager and pass it to the console runner.

Use the CLI!

Now we've set up the configuration file for the CLI tools, lets use them to set up our database for us. Executethe following command:
php vendor/bin/doctrine orm:schema-tool:create
You should get the following output:
ATTENTION: This operation should not be executed in a production environment.

Creating database schema...
Database schema created successfully!


Obviously we wouldn't want to use that to create our database in production, but we will cover that in another tutorial.


Use Doctrine to Create and Return Records

After that has completed, you should be able to create users. Lets finally fill in our index.php file such that we will use doctrine to create a user, before then outputting all of the users in our system.
<?php

require_once(__DIR__ . '/../bootstrap.php');

// create a user
$entityManager = getEntityManager();
$user = new User("Programster", "[email protected]");
$entityManager->persist($user);
$entityManager->flush();

echo "Created User with ID " . $user->getId() . PHP_EOL;

// List all users:
$users = $entityManager->getRepository("User")->findAll();
print "Users: " . print_r($users, true) . PHP_EOL;

Below is an example of the output you will get if you call the index.php file 4 times.
Created User with ID 4
Users: Array
(
    [0] => User Object
        (
            [id:protected] => 1
            [name:protected] => Programster
            [email:protected] => [email protected]
        )

    [1] => User Object
        (
            [id:protected] => 2
            [name:protected] => Programster
            [email:protected] => [email protected]
        )

    [2] => User Object
        (
            [id:protected] => 3
            [name:protected] => Programster
            [email:protected] => [email protected]
        )

    [3] => User Object
        (
            [id:protected] => 4
            [name:protected] => Programster
            [email:protected] => [email protected]
        )

)


Conclusion

Hopefully that's given you a taste for using Doctrine and the benefits it can provide. However, there is still much more to learn. For now, please refer to the references below for more information.