PHP Logodoctrine/orm

Doctrine ORM (Object-Relational Mapper) is a powerful PHP library that provides an object-oriented way to interact with a relational database. It serves as a bridge between your object-oriented PHP application and the underlying database schema, allowing you to manipulate database records as plain PHP objects (called 'Entities') instead of writing raw SQL.

Key Concepts and Features:

1. Entities: These are regular PHP classes that represent database tables. Each instance of an entity class corresponds to a row in the table. Entities are typically annotated with metadata (using attributes, YAML, or XML) to map their properties to database columns, define relationships (one-to-one, one-to-many, many-to-many), and specify other database constraints.

2. EntityManager: This is the central API for interacting with your entities. It's responsible for managing the lifecycle of entities (persisting new ones, finding existing ones, updating changes, removing them). The EntityManager maintains a 'Unit of Work' to track changes to entities and efficiently synchronize them with the database.

3. Repositories: For each entity, Doctrine automatically provides a default `EntityRepository` class. This class offers methods to retrieve entities from the database (e.g., `find(id)`, `findAll()`, `findBy(criteria)`). You can also create custom repository classes to encapsulate more complex query logic specific to an entity.

4. Unit of Work: Doctrine's Unit of Work is a core feature that optimizes database interactions. Instead of executing an SQL query for every `persist()`, `update()`, or `remove()` call, the Unit of Work tracks all changes made to entities within a transaction. When `flush()` is called, all pending changes are grouped and executed efficiently as a single set of database operations.

5. DQL (Doctrine Query Language): DQL is an object-oriented query language similar to SQL but operates on entities and their properties rather than database tables and columns. It allows you to write complex queries in an object-oriented manner, which is then translated by Doctrine into the appropriate SQL for your specific database.

6. Schema Tool & Migrations: Doctrine includes tools to automatically generate database schemas from your entity definitions and to manage schema changes over time through migrations. This helps keep your database schema in sync with your application's data model.

Benefits of using Doctrine ORM:

* Abstraction: Hides the complexity of database interactions, allowing developers to focus on domain logic.
* Productivity: Reduces the amount of boilerplate code needed for CRUD operations.
* Maintainability: Centralizes data access logic and improves code organization.
* Portability: Allows switching between different database systems (e.g., MySQL, PostgreSQL, SQLite) with minimal code changes, thanks to `doctrine/dbal` (Database Abstraction Layer).
* Security: Helps prevent SQL injection attacks by using prepared statements.

Doctrine ORM is widely used in the PHP ecosystem, most notably as the default ORM in the Symfony framework.

Example Code

```php
<?php

// autoloader
require_once __DIR__ . '/vendor/autoload.php';

use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping as ORM;

// 1. Configure Doctrine
$paths = [__DIR__ . '/src']; // Path to your entity classes
$isDevMode = true; // Set to false in production

// Database connection configuration
$dbParams = [
    'driver'   => 'pdo_sqlite',
    'path'     => __DIR__ . '/db.sqlite',
];

$config = Setup::createAttributeMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);

// --- Entity Definition (src/Product.php) ---

// In a real application, this would be in a file like src/Product.php
// and its namespace would match the configuration path.

/
 * @ORM\Entity
 * @ORM\Table(name="products")
 */
#[ORM\Entity]
#[ORM\Table(name: 'products')]
class Product
{
    /
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    #[ORM\Id]
    #[ORM\Column(type: 'integer')]
    #[ORM\GeneratedValue]
    private int $id;

    / @ORM\Column(type="string", length=255) */
    #[ORM\Column(type: 'string', length: 255)]
    private string $name;

    / @ORM\Column(type="float") */
    #[ORM\Column(type: 'float')]
    private float $price;

    / @ORM\Column(type="text", nullable=true) */
    #[ORM\Column(type: 'text', nullable: true)]
    private ?string $description;

    public function getId(): int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getPrice(): float
    {
        return $this->price;
    }

    public function setPrice(float $price): void
    {
        $this->price = $price;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): void
    {
        $this->description = $description;
    }
}

// --- Database Schema Generation (Run this once or on schema changes) ---
// In a real project, you'd use Doctrine's CLI tools for this.
// For demonstration, we'll simulate it:
// This would typically be run via `vendor/bin/doctrine orm:schema-tool:update --force`
// For simplicity in this example, we assume the table exists or will be created.
try {
    $schemaTool = new \Doctrine\ORM\Tools\SchemaTool($entityManager);
    $metadatas = $entityManager->getMetadataFactory()->getAllMetadata();
    $schemaTool->updateSchema($metadatas);
    echo "Schema updated successfully.\n";
} catch (\Exception $e) {
    echo "Schema update error (might mean table already exists, which is fine for example): " . $e->getMessage() . "\n";
}

// --- CRUD Operations Example ---

// CREATE a new product
echo "\nCreating a new product...\n";
$product = new Product();
$product->setName('Wireless Mouse');
$product->setPrice(25.99);
$product->setDescription('Ergonomic wireless mouse with long battery life.');

$entityManager->persist($product); // Tells Doctrine to manage this object
$entityManager->flush();           // Executes the INSERT query

echo sprintf("Created Product with ID: %d and Name: %s\n", $product->getId(), $product->getName());

// READ (Find by ID)
echo "\nFinding product with ID: " . $product->getId() . "\n";
$foundProduct = $entityManager->find(Product::class, $product->getId());

if ($foundProduct !== null) {
    echo sprintf("Found Product: %s (Price: %.2f)\n", $foundProduct->getName(), $foundProduct->getPrice());

    // UPDATE the product
    echo "\nUpdating product price...\n";
    $foundProduct->setPrice(29.99); // Change a property
    $entityManager->flush();        // Doctrine detects the change and executes an UPDATE query

    echo sprintf("Updated Product: %s new price: %.2f\n", $foundProduct->getName(), $foundProduct->getPrice());

    // READ (Find all products)
    echo "\nFinding all products...\n";
    $productRepository = $entityManager->getRepository(Product::class);
    $allProducts = $productRepository->findAll();

    foreach ($allProducts as $p) {
        echo sprintf(" - ID: %d, Name: %s, Price: %.2f\n", $p->getId(), $p->getName(), $p->getPrice());
    }

    // DELETE the product
    echo "\nDeleting product with ID: " . $foundProduct->getId() . "\n";
    $entityManager->remove($foundProduct); // Tells Doctrine to mark for removal
    $entityManager->flush();               // Executes the DELETE query

    $deletedCheck = $entityManager->find(Product::class, $foundProduct->getId());
    if ($deletedCheck === null) {
        echo "Product deleted successfully.\n";
    }
} else {
    echo "Product not found!\n";
}

?>
```