Spot

Spot is "Simple PHP ORM" that serves as a lightweight and simple Object-Relational Mapping (ORM) library for PHP applications. Designed as an alternative to the heavyweight nature of Doctrine ORM, it supports both Active Record and DataMapper patterns while providing an intuitive API with low learning costs. It realizes modern ORM features such as migration functionality, relationship management, and query builders in a lightweight package, making it an optimal choice for small to medium-scale PHP projects.

PHPORMDataMapperActive RecordLightweightMigration

GitHub Overview

spotorm/spot2

Spot v2.x DataMapper built on top of Doctrine's Database Abstraction Layer

Stars600
Watchers27
Forks100
Created:June 18, 2014
Language:PHP
License:BSD 3-Clause "New" or "Revised" License

Topics

None

Star History

spotorm/spot2 Star History
Data as of: 7/19/2025, 10:32 AM

Library

Spot

Overview

Spot is "Simple PHP ORM" that serves as a lightweight and simple Object-Relational Mapping (ORM) library for PHP applications. Designed as an alternative to the heavyweight nature of Doctrine ORM, it supports both Active Record and DataMapper patterns while providing an intuitive API with low learning costs. It realizes modern ORM features such as migration functionality, relationship management, and query builders in a lightweight package, making it an optimal choice for small to medium-scale PHP projects.

Details

Spot 2025 edition supports PHP 8.0 and later, providing safer and more expressive database programming by leveraging modern PHP features (type declarations, attributes, enums, etc.). It significantly improves developer productivity through simple Entity class definitions, flexible query builders, automatic schema migration, and relationship management. While not as feature-rich as Laravel's Eloquent or as complex as Doctrine, it provides a "just right" level of functionality set, enabling quick project startup with Composer management and minimal configuration.

Key Features

  • Lightweight Design: Minimal dependencies and footprint
  • Intuitive API: Simple interface with low learning costs
  • Migration: Automatic schema change management functionality
  • Relationships: Support for one-to-one, one-to-many, and many-to-many relationships
  • Query Builder: Flexible and readable query construction
  • Data Validation: Entity-level validation functionality

Pros and Cons

Pros

  • Lightweight with lower learning costs compared to Doctrine or Eloquent
  • Improved development efficiency through simple and intuitive API
  • Simplified schema management through automatic migration functionality
  • Flexibility through selective use of Active Record and DataMapper patterns
  • Quick project startup possible with minimal configuration
  • Natural integration with Composer ecosystem

Cons

  • Not as feature-rich as Laravel's Eloquent
  • Limited track record and ecosystem for large-scale projects
  • Complex queries and advanced ORM features are inferior to other libraries
  • Small community size with limited information and plugins
  • Enterprise-level features (caching, etc.) require separate implementation
  • Limited performance optimization options

Reference Pages

Code Examples

Setup

// composer.json
{
    "require": {
        "vlucas/spot2": "^2.3",
        "php": ">=8.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}
# Installation
composer install
<?php
// config/database.php
use Spot\Locator;
use Spot\Config;

// Database configuration
$cfg = new Config();

// SQLite configuration example
$cfg->addConnection('default', [
    'dbname' => __DIR__ . '/../database/app.sqlite',
    'driver' => 'pdo_sqlite'
]);

// MySQL configuration example
$cfg->addConnection('mysql', [
    'driver'   => 'pdo_mysql',
    'host'     => 'localhost',
    'dbname'   => 'spot_demo',
    'user'     => 'root',
    'password' => '',
    'charset'  => 'utf8mb4'
]);

// PostgreSQL configuration example
$cfg->addConnection('postgresql', [
    'driver'   => 'pdo_pgsql',
    'host'     => 'localhost',
    'dbname'   => 'spot_demo',
    'user'     => 'postgres',
    'password' => '',
    'charset'  => 'utf8'
]);

$spot = new Locator($cfg);

return $spot;

Basic Usage

<?php
namespace App\Entity;

use Spot\EntityInterface;
use Spot\MapperInterface;
use Spot\Entity;

// User entity
class User extends Entity
{
    protected static $table = 'users';
    
    public static function fields()
    {
        return [
            'id' => ['type' => 'integer', 'primary' => true, 'autoincrement' => true],
            'name' => ['type' => 'string', 'required' => true],
            'email' => ['type' => 'string', 'required' => true, 'unique' => true],
            'age' => ['type' => 'integer', 'default' => 0],
            'active' => ['type' => 'boolean', 'default' => true],
            'created_at' => ['type' => 'datetime', 'value' => new \DateTime()],
            'updated_at' => ['type' => 'datetime', 'value' => new \DateTime()]
        ];
    }
    
    public static function relations(MapperInterface $mapper, EntityInterface $entity)
    {
        return [
            'posts' => $mapper->hasMany($entity, 'App\Entity\Post', 'user_id'),
            'profile' => $mapper->hasOne($entity, 'App\Entity\UserProfile', 'user_id'),
        ];
    }
    
    // Custom method
    public function getFullInfo()
    {
        return sprintf('%s (%s) - Age: %d', $this->name, $this->email, $this->age);
    }
    
    // Validation
    public static function validate($data)
    {
        $errors = [];
        
        if (empty($data['name'])) {
            $errors['name'] = 'Name is required';
        }
        
        if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = 'Valid email is required';
        }
        
        if (isset($data['age']) && ($data['age'] < 0 || $data['age'] > 150)) {
            $errors['age'] = 'Age must be between 0 and 150';
        }
        
        return $errors;
    }
}

// Post entity
class Post extends Entity
{
    protected static $table = 'posts';
    
    public static function fields()
    {
        return [
            'id' => ['type' => 'integer', 'primary' => true, 'autoincrement' => true],
            'title' => ['type' => 'string', 'required' => true],
            'content' => ['type' => 'text'],
            'user_id' => ['type' => 'integer', 'required' => true],
            'published' => ['type' => 'boolean', 'default' => false],
            'published_at' => ['type' => 'datetime'],
            'created_at' => ['type' => 'datetime', 'value' => new \DateTime()],
            'updated_at' => ['type' => 'datetime', 'value' => new \DateTime()]
        ];
    }
    
    public static function relations(MapperInterface $mapper, EntityInterface $entity)
    {
        return [
            'user' => $mapper->belongsTo($entity, 'App\Entity\User', 'user_id'),
            'comments' => $mapper->hasMany($entity, 'App\Entity\Comment', 'post_id'),
        ];
    }
    
    // Scope method
    public static function published($mapper)
    {
        return $mapper->where(['published' => true]);
    }
    
    public function publish()
    {
        $this->published = true;
        $this->published_at = new \DateTime();
        $this->updated_at = new \DateTime();
    }
}

// User profile entity
class UserProfile extends Entity
{
    protected static $table = 'user_profiles';
    
    public static function fields()
    {
        return [
            'id' => ['type' => 'integer', 'primary' => true, 'autoincrement' => true],
            'user_id' => ['type' => 'integer', 'required' => true, 'unique' => true],
            'bio' => ['type' => 'text'],
            'website' => ['type' => 'string'],
            'location' => ['type' => 'string'],
            'birth_date' => ['type' => 'date'],
            'avatar_url' => ['type' => 'string'],
            'created_at' => ['type' => 'datetime', 'value' => new \DateTime()],
            'updated_at' => ['type' => 'datetime', 'value' => new \DateTime()]
        ];
    }
    
    public static function relations(MapperInterface $mapper, EntityInterface $entity)
    {
        return [
            'user' => $mapper->belongsTo($entity, 'App\Entity\User', 'user_id'),
        ];
    }
}

// Basic operations service
class UserService
{
    private $spot;
    
    public function __construct($spot)
    {
        $this->spot = $spot;
    }
    
    // Schema creation
    public function createSchema()
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        $postMapper = $this->spot->mapper('App\Entity\Post');
        $profileMapper = $this->spot->mapper('App\Entity\UserProfile');
        
        // Table creation
        $userMapper->migrate();
        $postMapper->migrate();
        $profileMapper->migrate();
        
        echo "Database schema created successfully\n";
    }
    
    // User creation
    public function createUser($name, $email, $age = null)
    {
        $data = compact('name', 'email', 'age');
        
        // Validation
        $errors = User::validate($data);
        if (!empty($errors)) {
            throw new \InvalidArgumentException('Validation failed: ' . implode(', ', $errors));
        }
        
        $userMapper = $this->spot->mapper('App\Entity\User');
        
        // Duplicate check
        $existingUser = $userMapper->first(['email' => $email]);
        if ($existingUser) {
            throw new \InvalidArgumentException('Email already exists');
        }
        
        $user = $userMapper->create($data);
        
        if ($user) {
            echo "User created: {$user->getFullInfo()}\n";
            return $user;
        }
        
        throw new \RuntimeException('Failed to create user');
    }
    
    // Get all users
    public function getAllUsers()
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        return $userMapper->all()->order(['name' => 'ASC']);
    }
    
    // Get user by ID
    public function getUserById($id)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        return $userMapper->get($id);
    }
    
    // Update user
    public function updateUser($id, $data)
    {
        $errors = User::validate($data);
        if (!empty($errors)) {
            throw new \InvalidArgumentException('Validation failed: ' . implode(', ', $errors));
        }
        
        $userMapper = $this->spot->mapper('App\Entity\User');
        $user = $userMapper->get($id);
        
        if (!$user) {
            throw new \InvalidArgumentException('User not found');
        }
        
        // Email duplicate check (excluding self)
        if (isset($data['email']) && $data['email'] !== $user->email) {
            $existingUser = $userMapper->first(['email' => $data['email']]);
            if ($existingUser) {
                throw new \InvalidArgumentException('Email already exists');
            }
        }
        
        $data['updated_at'] = new \DateTime();
        $result = $userMapper->update($user, $data);
        
        if ($result) {
            echo "User updated successfully\n";
            return $userMapper->get($id); // Return updated data
        }
        
        throw new \RuntimeException('Failed to update user');
    }
    
    // Delete user
    public function deleteUser($id)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        $user = $userMapper->get($id);
        
        if (!$user) {
            throw new \InvalidArgumentException('User not found');
        }
        
        $result = $userMapper->delete($user);
        
        if ($result) {
            echo "User deleted successfully\n";
            return true;
        }
        
        throw new \RuntimeException('Failed to delete user');
    }
    
    // Search functionality
    public function searchUsers($query)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        
        return $userMapper->where(['name :like' => "%{$query}%"])
                          ->orWhere(['email :like' => "%{$query}%"])
                          ->order(['name' => 'ASC']);
    }
    
    // Filter by age range
    public function getUsersByAgeRange($minAge, $maxAge)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        
        return $userMapper->where(['age :gte' => $minAge])
                          ->where(['age :lte' => $maxAge])
                          ->order(['age' => 'ASC', 'name' => 'ASC']);
    }
}

// Usage example
function demonstrateBasicOperations()
{
    $spot = require __DIR__ . '/config/database.php';
    $userService = new UserService($spot);
    
    try {
        // Schema creation
        $userService->createSchema();
        
        // User creation
        $user1 = $userService->createUser('Alice Smith', '[email protected]', 25);
        $user2 = $userService->createUser('Bob Johnson', '[email protected]', 30);
        $user3 = $userService->createUser('Charlie Brown', '[email protected]', 35);
        
        // Get all users
        echo "\n=== All Users ===\n";
        $users = $userService->getAllUsers();
        foreach ($users as $user) {
            echo $user->getFullInfo() . "\n";
        }
        
        // Get specific user
        $user = $userService->getUserById($user1->id);
        if ($user) {
            echo "\n=== User by ID ===\n";
            echo "Found: " . $user->getFullInfo() . "\n";
        }
        
        // Update user
        $updatedUser = $userService->updateUser($user1->id, [
            'name' => 'Alice Johnson',
            'email' => '[email protected]',
            'age' => 26
        ]);
        echo "\n=== Updated User ===\n";
        echo $updatedUser->getFullInfo() . "\n";
        
        // Search
        echo "\n=== Search Results ===\n";
        $searchResults = $userService->searchUsers('Alice');
        foreach ($searchResults as $user) {
            echo "Found: " . $user->getFullInfo() . "\n";
        }
        
        // Age range filter
        echo "\n=== Users by Age Range ===\n";
        $ageFilteredUsers = $userService->getUsersByAgeRange(25, 35);
        foreach ($ageFilteredUsers as $user) {
            echo $user->getFullInfo() . "\n";
        }
        
    } catch (\Exception $e) {
        echo "Error: " . $e->getMessage() . "\n";
    }
}

Relationships and Queries

<?php

// Comment entity
class Comment extends Entity
{
    protected static $table = 'comments';
    
    public static function fields()
    {
        return [
            'id' => ['type' => 'integer', 'primary' => true, 'autoincrement' => true],
            'post_id' => ['type' => 'integer', 'required' => true],
            'user_id' => ['type' => 'integer', 'required' => true],
            'content' => ['type' => 'text', 'required' => true],
            'approved' => ['type' => 'boolean', 'default' => false],
            'created_at' => ['type' => 'datetime', 'value' => new \DateTime()],
            'updated_at' => ['type' => 'datetime', 'value' => new \DateTime()]
        ];
    }
    
    public static function relations(MapperInterface $mapper, EntityInterface $entity)
    {
        return [
            'post' => $mapper->belongsTo($entity, 'App\Entity\Post', 'post_id'),
            'user' => $mapper->belongsTo($entity, 'App\Entity\User', 'user_id'),
        ];
    }
}

// Blog service (relationship usage example)
class BlogService
{
    private $spot;
    
    public function __construct($spot)
    {
        $this->spot = $spot;
    }
    
    // Create post
    public function createPost($userId, $title, $content)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        $user = $userMapper->get($userId);
        
        if (!$user) {
            throw new \InvalidArgumentException('User not found');
        }
        
        $postMapper = $this->spot->mapper('App\Entity\Post');
        
        $post = $postMapper->create([
            'title' => $title,
            'content' => $content,
            'user_id' => $userId,
            'published' => false
        ]);
        
        if ($post) {
            echo "Post created: {$post->title} by {$user->name}\n";
            return $post;
        }
        
        throw new \RuntimeException('Failed to create post');
    }
    
    // Get user posts
    public function getUserPosts($userId)
    {
        $postMapper = $this->spot->mapper('App\Entity\Post');
        
        return $postMapper->where(['user_id' => $userId])
                          ->order(['created_at' => 'DESC']);
    }
    
    // Get posts with user information join
    public function getPostsWithAuthors()
    {
        $postMapper = $this->spot->mapper('App\Entity\Post');
        
        return $postMapper->all()
                          ->with('user')
                          ->order(['created_at' => 'DESC']);
    }
    
    // Get published posts
    public function getPublishedPosts()
    {
        $postMapper = $this->spot->mapper('App\Entity\Post');
        
        return $postMapper->where(['published' => true])
                          ->with('user')
                          ->order(['published_at' => 'DESC']);
    }
    
    // Publish post
    public function publishPost($postId)
    {
        $postMapper = $this->spot->mapper('App\Entity\Post');
        $post = $postMapper->get($postId);
        
        if (!$post) {
            throw new \InvalidArgumentException('Post not found');
        }
        
        $post->publish();
        
        $result = $postMapper->save($post);
        
        if ($result) {
            echo "Post published: {$post->title}\n";
            return $post;
        }
        
        throw new \RuntimeException('Failed to publish post');
    }
    
    // Add comment
    public function addComment($postId, $userId, $content)
    {
        $postMapper = $this->spot->mapper('App\Entity\Post');
        $userMapper = $this->spot->mapper('App\Entity\User');
        
        $post = $postMapper->get($postId);
        $user = $userMapper->get($userId);
        
        if (!$post) {
            throw new \InvalidArgumentException('Post not found');
        }
        
        if (!$user) {
            throw new \InvalidArgumentException('User not found');
        }
        
        $commentMapper = $this->spot->mapper('App\Entity\Comment');
        
        $comment = $commentMapper->create([
            'post_id' => $postId,
            'user_id' => $userId,
            'content' => $content,
            'approved' => false
        ]);
        
        if ($comment) {
            echo "Comment added by {$user->name} on post: {$post->title}\n";
            return $comment;
        }
        
        throw new \RuntimeException('Failed to add comment');
    }
    
    // Get post comments
    public function getPostComments($postId)
    {
        $commentMapper = $this->spot->mapper('App\Entity\Comment');
        
        return $commentMapper->where(['post_id' => $postId])
                             ->with('user')
                             ->order(['created_at' => 'ASC']);
    }
    
    // Complex query: Popular users (by post count)
    public function getPopularUsers($limit = 10)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        
        // Execute JOIN query with Spot
        $query = $userMapper->query("
            SELECT u.*, COUNT(p.id) as post_count 
            FROM users u 
            LEFT JOIN posts p ON u.id = p.user_id 
            GROUP BY u.id 
            ORDER BY post_count DESC 
            LIMIT {$limit}
        ");
        
        return $query;
    }
    
    // Get statistics
    public function getBlogStatistics()
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        $postMapper = $this->spot->mapper('App\Entity\Post');
        $commentMapper = $this->spot->mapper('App\Entity\Comment');
        
        $stats = [
            'total_users' => $userMapper->all()->count(),
            'total_posts' => $postMapper->all()->count(),
            'published_posts' => $postMapper->where(['published' => true])->count(),
            'total_comments' => $commentMapper->all()->count(),
            'approved_comments' => $commentMapper->where(['approved' => true])->count(),
        ];
        
        return $stats;
    }
}

// Usage example
function demonstrateRelationships()
{
    $spot = require __DIR__ . '/config/database.php';
    $userService = new UserService($spot);
    $blogService = new BlogService($spot);
    
    try {
        // Comment table creation
        $commentMapper = $spot->mapper('App\Entity\Comment');
        $commentMapper->migrate();
        
        // User creation
        $user = $userService->createUser('John Doe', '[email protected]', 30);
        
        // Post creation
        $post = $blogService->createPost($user->id, 'My First Post', 'This is the content of my first post.');
        
        // Publish post
        $publishedPost = $blogService->publishPost($post->id);
        
        // Add comment
        $comment = $blogService->addComment($post->id, $user->id, 'Great post!');
        
        // Get published posts with user information
        echo "\n=== Published Posts with Authors ===\n";
        $postsWithAuthors = $blogService->getPublishedPosts();
        foreach ($postsWithAuthors as $post) {
            echo "Post: {$post->title} by {$post->user->name}\n";
        }
        
        // Get post comments
        echo "\n=== Post Comments ===\n";
        $comments = $blogService->getPostComments($post->id);
        foreach ($comments as $comment) {
            echo "Comment by {$comment->user->name}: {$comment->content}\n";
        }
        
        // Statistics
        echo "\n=== Blog Statistics ===\n";
        $stats = $blogService->getBlogStatistics();
        foreach ($stats as $key => $value) {
            echo ucfirst(str_replace('_', ' ', $key)) . ": {$value}\n";
        }
        
    } catch (\Exception $e) {
        echo "Error: " . $e->getMessage() . "\n";
    }
}

Advanced Features and Customization

<?php

// Custom mapper
class UserMapper extends \Spot\Mapper
{
    // Custom scope
    public function active()
    {
        return $this->where(['active' => true]);
    }
    
    public function byAge($age)
    {
        return $this->where(['age' => $age]);
    }
    
    public function adults()
    {
        return $this->where(['age :gte' => 18]);
    }
    
    // Custom query method
    public function findByEmailDomain($domain)
    {
        return $this->where(['email :like' => "%@{$domain}"]);
    }
    
    public function getUsersWithPostCount()
    {
        return $this->query("
            SELECT u.*, COUNT(p.id) as post_count 
            FROM users u 
            LEFT JOIN posts p ON u.id = p.user_id 
            GROUP BY u.id 
            ORDER BY u.name
        ");
    }
    
    // Event hooks
    public function beforeSave($entity, $options)
    {
        if ($entity->isNew()) {
            $entity->created_at = new \DateTime();
        }
        $entity->updated_at = new \DateTime();
        
        return true;
    }
    
    public function afterSave($entity, $options)
    {
        // Post-save processing (logging, cache clearing, etc.)
        echo "User saved: {$entity->name}\n";
        
        return true;
    }
}

// Specify custom mapper for User entity
class User extends Entity
{
    protected static $table = 'users';
    protected static $mapper = 'UserMapper';
    
    // ... Other methods remain the same
}

// Advanced service class
class AdvancedUserService
{
    private $spot;
    
    public function __construct($spot)
    {
        $this->spot = $spot;
    }
    
    // Transaction processing
    public function transferUserData($fromUserId, $toUserId)
    {
        $connection = $this->spot->connection();
        
        try {
            $connection->beginTransaction();
            
            $userMapper = $this->spot->mapper('App\Entity\User');
            $postMapper = $this->spot->mapper('App\Entity\Post');
            
            $fromUser = $userMapper->get($fromUserId);
            $toUser = $userMapper->get($toUserId);
            
            if (!$fromUser || !$toUser) {
                throw new \InvalidArgumentException('One or both users not found');
            }
            
            // Transfer posts
            $posts = $postMapper->where(['user_id' => $fromUserId]);
            foreach ($posts as $post) {
                $postMapper->update($post, ['user_id' => $toUserId]);
            }
            
            // Delete original user
            $userMapper->delete($fromUser);
            
            $connection->commit();
            
            echo "User data transferred successfully from User {$fromUserId} to User {$toUserId}\n";
            return true;
            
        } catch (\Exception $e) {
            $connection->rollback();
            throw new \RuntimeException("Transfer failed: " . $e->getMessage());
        }
    }
    
    // Batch processing
    public function batchCreateUsers($usersData)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        $createdUsers = [];
        $errors = [];
        
        foreach ($usersData as $index => $userData) {
            try {
                $validationErrors = User::validate($userData);
                if (!empty($validationErrors)) {
                    $errors[$index] = $validationErrors;
                    continue;
                }
                
                $user = $userMapper->create($userData);
                if ($user) {
                    $createdUsers[] = $user;
                } else {
                    $errors[$index] = 'Failed to create user';
                }
                
            } catch (\Exception $e) {
                $errors[$index] = $e->getMessage();
            }
        }
        
        return [
            'created' => $createdUsers,
            'errors' => $errors,
            'success_count' => count($createdUsers),
            'error_count' => count($errors)
        ];
    }
    
    // Conditional update
    public function updateUsersByCondition($conditions, $updates)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        
        $users = $userMapper->where($conditions);
        $updatedCount = 0;
        $errors = [];
        
        foreach ($users as $user) {
            try {
                $result = $userMapper->update($user, $updates);
                if ($result) {
                    $updatedCount++;
                }
            } catch (\Exception $e) {
                $errors[] = "Failed to update user {$user->id}: " . $e->getMessage();
            }
        }
        
        return [
            'updated_count' => $updatedCount,
            'errors' => $errors
        ];
    }
    
    // Data export
    public function exportUsersToJson($conditions = [])
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        
        $query = empty($conditions) ? $userMapper->all() : $userMapper->where($conditions);
        $users = $query->with('posts', 'profile');
        
        $exportData = [];
        
        foreach ($users as $user) {
            $userData = [
                'id' => $user->id,
                'name' => $user->name,
                'email' => $user->email,
                'age' => $user->age,
                'active' => $user->active,
                'created_at' => $user->created_at->format('Y-m-d H:i:s'),
                'posts' => []
            ];
            
            if ($user->posts) {
                foreach ($user->posts as $post) {
                    $userData['posts'][] = [
                        'id' => $post->id,
                        'title' => $post->title,
                        'published' => $post->published,
                        'created_at' => $post->created_at->format('Y-m-d H:i:s')
                    ];
                }
            }
            
            if ($user->profile) {
                $userData['profile'] = [
                    'bio' => $user->profile->bio,
                    'website' => $user->profile->website,
                    'location' => $user->profile->location
                ];
            }
            
            $exportData[] = $userData;
        }
        
        return json_encode($exportData, JSON_PRETTY_PRINT);
    }
    
    // Cache functionality (simple version)
    private $cache = [];
    
    public function getUserWithCache($id)
    {
        if (isset($this->cache["user_{$id}"])) {
            echo "Retrieved from cache: User {$id}\n";
            return $this->cache["user_{$id}"];
        }
        
        $userMapper = $this->spot->mapper('App\Entity\User');
        $user = $userMapper->get($id);
        
        if ($user) {
            $this->cache["user_{$id}"] = $user;
            echo "Cached: User {$id}\n";
        }
        
        return $user;
    }
    
    public function clearUserCache($id = null)
    {
        if ($id) {
            unset($this->cache["user_{$id}"]);
        } else {
            $this->cache = [];
        }
    }
}

// Usage example
function demonstrateAdvancedFeatures()
{
    $spot = require __DIR__ . '/config/database.php';
    $advancedService = new AdvancedUserService($spot);
    
    try {
        // Batch user creation
        $usersData = [
            ['name' => 'User 1', 'email' => '[email protected]', 'age' => 25],
            ['name' => 'User 2', 'email' => '[email protected]', 'age' => 30],
            ['name' => '', 'email' => 'invalid', 'age' => -5], // Validation error
            ['name' => 'User 4', 'email' => '[email protected]', 'age' => 35],
        ];
        
        echo "=== Batch User Creation ===\n";
        $batchResult = $advancedService->batchCreateUsers($usersData);
        echo "Created: {$batchResult['success_count']}, Errors: {$batchResult['error_count']}\n";
        
        if (!empty($batchResult['errors'])) {
            foreach ($batchResult['errors'] as $index => $error) {
                echo "Error at index {$index}: " . (is_array($error) ? implode(', ', $error) : $error) . "\n";
            }
        }
        
        // Conditional update
        echo "\n=== Conditional Update ===\n";
        $updateResult = $advancedService->updateUsersByCondition(
            ['age :gte' => 30],
            ['active' => false]
        );
        echo "Updated {$updateResult['updated_count']} users\n";
        
        // Data export
        echo "\n=== Data Export ===\n";
        $jsonData = $advancedService->exportUsersToJson(['active' => true]);
        echo "Export data (first 200 chars): " . substr($jsonData, 0, 200) . "...\n";
        
        // Cache test
        echo "\n=== Cache Test ===\n";
        $user1 = $advancedService->getUserWithCache(1);
        $user2 = $advancedService->getUserWithCache(1); // Retrieved from cache
        
    } catch (\Exception $e) {
        echo "Error: " . $e->getMessage() . "\n";
    }
}

// Main execution
function main()
{
    echo "=== Spot ORM Demo ===\n\n";
    
    demonstrateBasicOperations();
    echo "\n" . str_repeat("=", 50) . "\n\n";
    
    demonstrateRelationships();
    echo "\n" . str_repeat("=", 50) . "\n\n";
    
    demonstrateAdvancedFeatures();
}

if (php_sapi_name() === 'cli') {
    main();
}