Spot

Spotは「Simple PHP ORM」として、PHPアプリケーションのための軽量でシンプルなObject-Relational Mapping(ORM)ライブラリです。Doctrine ORMの重厚さに対するオルタナティブとして設計され、Active RecordとDataMapperパターンの両方をサポートしながら、学習コストの低い直感的なAPIを提供します。マイグレーション機能、関係性管理、クエリビルダー等のモダンなORM機能を軽量なパッケージで実現し、中小規模のPHPプロジェクトに最適な選択肢となっています。

PHPORMDataMapperActive Recordライトウェイトマイグレーション

GitHub概要

spotorm/spot2

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

スター600
ウォッチ27
フォーク100
作成日:2014年6月18日
言語:PHP
ライセンス:BSD 3-Clause "New" or "Revised" License

トピックス

なし

スター履歴

spotorm/spot2 Star History
データ取得日時: 2025/7/19 10:32

ライブラリ

Spot

概要

Spotは「Simple PHP ORM」として、PHPアプリケーションのための軽量でシンプルなObject-Relational Mapping(ORM)ライブラリです。Doctrine ORMの重厚さに対するオルタナティブとして設計され、Active RecordとDataMapperパターンの両方をサポートしながら、学習コストの低い直感的なAPIを提供します。マイグレーション機能、関係性管理、クエリビルダー等のモダンなORM機能を軽量なパッケージで実現し、中小規模のPHPプロジェクトに最適な選択肢となっています。

詳細

Spot 2025年版はPHP 8.0以降に対応し、モダンPHPの機能(型宣言、属性、enum等)を活用したより安全で表現力豊かなデータベースプログラミングを提供します。シンプルなEntityクラス定義、柔軟なクエリビルダー、自動的なスキーママイグレーション、リレーションシップ管理により、開発者の生産性を大幅に向上させます。LaravelのEloquentほど高機能ではありませんが、DoctrineほどWeb複雑でもない、「ちょうど良い」レベルの機能セットを提供し、Composer管理による簡単な導入と最小限の設定で利用開始できます。

主な特徴

  • 軽量設計: 最小限の依存関係とフットプリント
  • 直感的API: 学習コストの低いシンプルなインターフェース
  • マイグレーション: スキーマ変更の自動管理機能
  • リレーションシップ: 一対一、一対多、多対多の関係サポート
  • クエリビルダー: 柔軟で読みやすいクエリ構築
  • データ検証: エンティティレベルでのバリデーション機能

メリット・デメリット

メリット

  • DoctrineやEloquentに比べて軽量で学習コストが低い
  • シンプルで直感的なAPIによる開発効率の向上
  • 自動マイグレーション機能によるスキーマ管理の簡素化
  • Active RecordとDataMapperの使い分けによる柔軟性
  • 最小限の設定で素早いプロジェクト開始が可能
  • Composerエコシステムとの自然な統合

デメリット

  • LaravelのEloquentほど高機能ではない
  • 大規模プロジェクトでの実績やエコシステムが限定的
  • 複雑なクエリや高度なORM機能は他のライブラリに劣る
  • コミュニティサイズが小さく情報やプラグインが少ない
  • エンタープライズレベルの機能(キャッシュ等)は別途実装が必要
  • パフォーマンス最適化の選択肢が限定的

参考ページ

書き方の例

セットアップ

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

// データベース設定
$cfg = new Config();

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

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

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

$spot = new Locator($cfg);

return $spot;

基本的な使い方

<?php
namespace App\Entity;

use Spot\EntityInterface;
use Spot\MapperInterface;
use Spot\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'),
        ];
    }
    
    // カスタムメソッド
    public function getFullInfo()
    {
        return sprintf('%s (%s) - Age: %d', $this->name, $this->email, $this->age);
    }
    
    // バリデーション
    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;
    }
}

// 投稿エンティティ
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'),
        ];
    }
    
    // スコープメソッド
    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();
    }
}

// ユーザープロフィールエンティティ
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'),
        ];
    }
}

// 基本操作サービス
class UserService
{
    private $spot;
    
    public function __construct($spot)
    {
        $this->spot = $spot;
    }
    
    // スキーマ作成
    public function createSchema()
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        $postMapper = $this->spot->mapper('App\Entity\Post');
        $profileMapper = $this->spot->mapper('App\Entity\UserProfile');
        
        // テーブル作成
        $userMapper->migrate();
        $postMapper->migrate();
        $profileMapper->migrate();
        
        echo "Database schema created successfully\n";
    }
    
    // ユーザー作成
    public function createUser($name, $email, $age = null)
    {
        $data = compact('name', 'email', 'age');
        
        // バリデーション
        $errors = User::validate($data);
        if (!empty($errors)) {
            throw new \InvalidArgumentException('Validation failed: ' . implode(', ', $errors));
        }
        
        $userMapper = $this->spot->mapper('App\Entity\User');
        
        // 重複チェック
        $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');
    }
    
    // 全ユーザー取得
    public function getAllUsers()
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        return $userMapper->all()->order(['name' => 'ASC']);
    }
    
    // ID指定でユーザー取得
    public function getUserById($id)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        return $userMapper->get($id);
    }
    
    // ユーザー更新
    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');
        }
        
        // メール重複チェック(自分以外)
        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); // 更新後のデータを返す
        }
        
        throw new \RuntimeException('Failed to update 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');
    }
    
    // 検索機能
    public function searchUsers($query)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        
        return $userMapper->where(['name :like' => "%{$query}%"])
                          ->orWhere(['email :like' => "%{$query}%"])
                          ->order(['name' => 'ASC']);
    }
    
    // 年齢範囲でフィルタ
    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']);
    }
}

// 使用例
function demonstrateBasicOperations()
{
    $spot = require __DIR__ . '/config/database.php';
    $userService = new UserService($spot);
    
    try {
        // スキーマ作成
        $userService->createSchema();
        
        // ユーザー作成
        $user1 = $userService->createUser('Alice Smith', '[email protected]', 25);
        $user2 = $userService->createUser('Bob Johnson', '[email protected]', 30);
        $user3 = $userService->createUser('Charlie Brown', '[email protected]', 35);
        
        // 全ユーザー取得
        echo "\n=== All Users ===\n";
        $users = $userService->getAllUsers();
        foreach ($users as $user) {
            echo $user->getFullInfo() . "\n";
        }
        
        // 特定ユーザー取得
        $user = $userService->getUserById($user1->id);
        if ($user) {
            echo "\n=== User by ID ===\n";
            echo "Found: " . $user->getFullInfo() . "\n";
        }
        
        // ユーザー更新
        $updatedUser = $userService->updateUser($user1->id, [
            'name' => 'Alice Johnson',
            'email' => '[email protected]',
            'age' => 26
        ]);
        echo "\n=== Updated User ===\n";
        echo $updatedUser->getFullInfo() . "\n";
        
        // 検索
        echo "\n=== Search Results ===\n";
        $searchResults = $userService->searchUsers('Alice');
        foreach ($searchResults as $user) {
            echo "Found: " . $user->getFullInfo() . "\n";
        }
        
        // 年齢範囲フィルタ
        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";
    }
}

リレーションシップとクエリ

<?php

// コメントエンティティ
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'),
        ];
    }
}

// ブログサービス(リレーション使用例)
class BlogService
{
    private $spot;
    
    public function __construct($spot)
    {
        $this->spot = $spot;
    }
    
    // 投稿作成
    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');
    }
    
    // ユーザーの投稿取得
    public function getUserPosts($userId)
    {
        $postMapper = $this->spot->mapper('App\Entity\Post');
        
        return $postMapper->where(['user_id' => $userId])
                          ->order(['created_at' => 'DESC']);
    }
    
    // 投稿とユーザー情報の結合取得
    public function getPostsWithAuthors()
    {
        $postMapper = $this->spot->mapper('App\Entity\Post');
        
        return $postMapper->all()
                          ->with('user')
                          ->order(['created_at' => 'DESC']);
    }
    
    // 公開済み投稿取得
    public function getPublishedPosts()
    {
        $postMapper = $this->spot->mapper('App\Entity\Post');
        
        return $postMapper->where(['published' => true])
                          ->with('user')
                          ->order(['published_at' => 'DESC']);
    }
    
    // 投稿公開
    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');
    }
    
    // コメント追加
    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');
    }
    
    // 投稿のコメント取得
    public function getPostComments($postId)
    {
        $commentMapper = $this->spot->mapper('App\Entity\Comment');
        
        return $commentMapper->where(['post_id' => $postId])
                             ->with('user')
                             ->order(['created_at' => 'ASC']);
    }
    
    // 複雑なクエリ:人気ユーザー(投稿数順)
    public function getPopularUsers($limit = 10)
    {
        $userMapper = $this->spot->mapper('App\Entity\User');
        
        // SpotでJOINクエリを実行
        $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;
    }
    
    // 統計情報取得
    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;
    }
}

// 使用例
function demonstrateRelationships()
{
    $spot = require __DIR__ . '/config/database.php';
    $userService = new UserService($spot);
    $blogService = new BlogService($spot);
    
    try {
        // コメントテーブル作成
        $commentMapper = $spot->mapper('App\Entity\Comment');
        $commentMapper->migrate();
        
        // ユーザー作成
        $user = $userService->createUser('John Doe', '[email protected]', 30);
        
        // 投稿作成
        $post = $blogService->createPost($user->id, 'My First Post', 'This is the content of my first post.');
        
        // 投稿公開
        $publishedPost = $blogService->publishPost($post->id);
        
        // コメント追加
        $comment = $blogService->addComment($post->id, $user->id, 'Great post!');
        
        // 公開済み投稿とユーザー情報取得
        echo "\n=== Published Posts with Authors ===\n";
        $postsWithAuthors = $blogService->getPublishedPosts();
        foreach ($postsWithAuthors as $post) {
            echo "Post: {$post->title} by {$post->user->name}\n";
        }
        
        // 投稿のコメント取得
        echo "\n=== Post Comments ===\n";
        $comments = $blogService->getPostComments($post->id);
        foreach ($comments as $comment) {
            echo "Comment by {$comment->user->name}: {$comment->content}\n";
        }
        
        // 統計情報
        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";
    }
}

高度な機能とカスタマイゼーション

<?php

// カスタムマッパー
class UserMapper extends \Spot\Mapper
{
    // カスタムスコープ
    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]);
    }
    
    // カスタムクエリメソッド
    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
        ");
    }
    
    // イベントフック
    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)
    {
        // 保存後の処理(ログ出力、キャッシュクリア等)
        echo "User saved: {$entity->name}\n";
        
        return true;
    }
}

// Userエンティティにカスタムマッパーを指定
class User extends Entity
{
    protected static $table = 'users';
    protected static $mapper = 'UserMapper';
    
    // ... その他のメソッドは既存のまま
}

// 高度なサービスクラス
class AdvancedUserService
{
    private $spot;
    
    public function __construct($spot)
    {
        $this->spot = $spot;
    }
    
    // トランザクション処理
    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');
            }
            
            // 投稿を移行
            $posts = $postMapper->where(['user_id' => $fromUserId]);
            foreach ($posts as $post) {
                $postMapper->update($post, ['user_id' => $toUserId]);
            }
            
            // 元のユーザーを削除
            $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());
        }
    }
    
    // バッチ処理
    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)
        ];
    }
    
    // 条件付き更新
    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
        ];
    }
    
    // データエクスポート
    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);
    }
    
    // キャッシュ機能(簡易版)
    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 = [];
        }
    }
}

// 使用例
function demonstrateAdvancedFeatures()
{
    $spot = require __DIR__ . '/config/database.php';
    $advancedService = new AdvancedUserService($spot);
    
    try {
        // バッチユーザー作成
        $usersData = [
            ['name' => 'User 1', 'email' => '[email protected]', 'age' => 25],
            ['name' => 'User 2', 'email' => '[email protected]', 'age' => 30],
            ['name' => '', 'email' => 'invalid', 'age' => -5], // バリデーションエラー
            ['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";
            }
        }
        
        // 条件付き更新
        echo "\n=== Conditional Update ===\n";
        $updateResult = $advancedService->updateUsersByCondition(
            ['age :gte' => 30],
            ['active' => false]
        );
        echo "Updated {$updateResult['updated_count']} users\n";
        
        // データエクスポート
        echo "\n=== Data Export ===\n";
        $jsonData = $advancedService->exportUsersToJson(['active' => true]);
        echo "Export data (first 200 chars): " . substr($jsonData, 0, 200) . "...\n";
        
        // キャッシュテスト
        echo "\n=== Cache Test ===\n";
        $user1 = $advancedService->getUserWithCache(1);
        $user2 = $advancedService->getUserWithCache(1); // キャッシュから取得
        
    } catch (\Exception $e) {
        echo "Error: " . $e->getMessage() . "\n";
    }
}

// メイン実行
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();
}