Skip to content

Migration Guide ​

This guide helps you migrate to Bob Query Builder from other database libraries and query builders.

Migrating from Raw PDO ​

If you're currently using raw PDO, Bob provides a much cleaner API while maintaining the same performance.

Before (PDO) ​

php
// Connection
$pdo = new PDO('mysql:host=localhost;dbname=myapp', 'root', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// Select query
$stmt = $pdo->prepare('SELECT * FROM users WHERE status = ? AND age > ?');
$stmt->execute(['active', 25]);
$users = $stmt->fetchAll(PDO::FETCH_OBJ);

// Insert
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->execute(['John Doe', 'john@example.com']);
$lastId = $pdo->lastInsertId();

// Update
$stmt = $pdo->prepare('UPDATE users SET status = ? WHERE id = ?');
$stmt->execute(['inactive', $lastId]);

After (Bob) ​

php
use Bob\Database\Connection;

// Connection
$connection = new Connection([
    'driver' => 'mysql',
    'host' => 'localhost',
    'database' => 'myapp',
    'username' => 'root',
    'password' => 'password',
]);

// Select query
$users = $connection->table('users')
    ->where('status', 'active')
    ->where('age', '>', 25)
    ->get();

// Insert
$lastId = $connection->table('users')->insertGetId([
    'name' => 'John Doe',
    'email' => 'john@example.com'
]);

// Update
$connection->table('users')
    ->where('id', $lastId)
    ->update(['status' => 'inactive']);

Migrating from WordPress $wpdb ​

Bob is designed to work seamlessly alongside WordPress's $wpdb while providing a modern API.

Before ($wpdb) ​

php
global $wpdb;

// Select
$results = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}users WHERE status = %s AND age > %d",
        'active',
        25
    )
);

// Insert
$wpdb->insert(
    $wpdb->prefix . 'users',
    ['name' => 'John Doe', 'email' => 'john@example.com'],
    ['%s', '%s']
);
$lastId = $wpdb->insert_id;

// Update
$wpdb->update(
    $wpdb->prefix . 'users',
    ['status' => 'inactive'],
    ['id' => $lastId],
    ['%s'],
    ['%d']
);

// Delete
$wpdb->delete(
    $wpdb->prefix . 'users',
    ['id' => $lastId],
    ['%d']
);

After (Bob) ​

php
use Bob\Database\Connection;

global $wpdb;

$connection = new Connection([
    'driver' => 'mysql',
    'host' => DB_HOST,
    'database' => DB_NAME,
    'username' => DB_USER,
    'password' => DB_PASSWORD,
    'charset' => DB_CHARSET,
    'prefix' => $wpdb->prefix,
]);

// Select
$results = $connection->table('users')
    ->where('status', 'active')
    ->where('age', '>', 25)
    ->get();

// Insert
$lastId = $connection->table('users')->insertGetId([
    'name' => 'John Doe',
    'email' => 'john@example.com'
]);

// Update
$connection->table('users')
    ->where('id', $lastId)
    ->update(['status' => 'inactive']);

// Delete
$connection->table('users')->delete($lastId);

WordPress Integration Tips ​

  1. Use WordPress Constants:
php
$connection = new Connection([
    'driver' => 'mysql',
    'host' => DB_HOST,
    'database' => DB_NAME,
    'username' => DB_USER,
    'password' => DB_PASSWORD,
    'charset' => DB_CHARSET,
    'collation' => DB_COLLATE ?: 'utf8mb4_unicode_ci',
    'prefix' => $wpdb->prefix,
]);
  1. Create a Helper Function:
php
function bob_db(): Connection {
    static $connection = null;

    if ($connection === null) {
        global $wpdb;

        $connection = new Connection([
            'driver' => 'mysql',
            'host' => DB_HOST,
            'database' => DB_NAME,
            'username' => DB_USER,
            'password' => DB_PASSWORD,
            'charset' => DB_CHARSET,
            'prefix' => $wpdb->prefix,
        ]);
    }

    return $connection;
}

// Usage
$users = bob_db()->table('users')->get();
  1. Use in Plugins:
php
class MyPlugin {
    private Connection $db;

    public function __construct() {
        global $wpdb;

        $this->db = new Connection([
            'driver' => 'mysql',
            'host' => DB_HOST,
            'database' => DB_NAME,
            'username' => DB_USER,
            'password' => DB_PASSWORD,
            'prefix' => $wpdb->prefix,
        ]);
    }

    public function getActiveUsers() {
        return $this->db->table('users')
            ->where('status', 'active')
            ->get();
    }
}

Migrating from Laravel Query Builder ​

Bob's API is heavily inspired by Laravel's query builder, making migration straightforward.

Key Differences ​

  1. Connection Setup:
php
// Laravel (configured in config/database.php)
DB::table('users')->get();

// Bob
$connection = new Connection($config);
$connection->table('users')->get();
  1. Pagination:
php
// Laravel
$users = DB::table('users')->paginate(15);

// Bob (manual pagination)
$page = $_GET['page'] ?? 1;
$perPage = 15;
$users = $connection->table('users')
    ->page($page, $perPage)
    ->get();
  1. Model Usage:
php
// Laravel Eloquent
User::where('active', true)->get();

// Bob Model
User::where('active', true)->get(); // Same API!

Compatibility Table ​

Laravel MethodBob EquivalentNotes
DB::table()$connection->table()Same API
->select()->select()✅ Identical
->where()->where()✅ Identical
->orWhere()->orWhere()✅ Identical
->whereIn()->whereIn()✅ Identical
->whereNull()->whereNull()✅ Identical
->join()->join()✅ Identical
->leftJoin()->leftJoin()✅ Identical
->orderBy()->orderBy()✅ Identical
->groupBy()->groupBy()✅ Identical
->having()->having()✅ Identical
->limit()->limit()✅ Identical
->offset()->offset()✅ Identical
->get()->get()✅ Identical
->first()->first()✅ Identical
->find()->find()✅ Identical
->count()->count()✅ Identical
->insert()->insert()✅ Identical
->update()->update()✅ Identical
->delete()->delete()✅ Identical
->paginate()->page()Different API
->chunk()->chunk()✅ Identical
->cursor()->cursor()✅ Identical

Migrating from Doctrine DBAL ​

If you're using Doctrine DBAL, Bob provides a simpler alternative for query building.

Before (Doctrine) ​

php
use Doctrine\DBAL\DriverManager;

$conn = DriverManager::getConnection([
    'dbname' => 'myapp',
    'user' => 'root',
    'password' => 'password',
    'host' => 'localhost',
    'driver' => 'pdo_mysql',
]);

// Query Builder
$queryBuilder = $conn->createQueryBuilder();
$queryBuilder
    ->select('u.*')
    ->from('users', 'u')
    ->where('u.status = :status')
    ->andWhere('u.age > :age')
    ->setParameter('status', 'active')
    ->setParameter('age', 25);

$result = $queryBuilder->execute()->fetchAll();

After (Bob) ​

php
use Bob\Database\Connection;

$connection = new Connection([
    'driver' => 'mysql',
    'host' => 'localhost',
    'database' => 'myapp',
    'username' => 'root',
    'password' => 'password',
]);

$result = $connection->table('users')
    ->where('status', 'active')
    ->where('age', '>', 25)
    ->get();

Migrating from Medoo ​

Bob offers similar simplicity to Medoo with a more Laravel-like API.

Before (Medoo) ​

php
use Medoo\Medoo;

$database = new Medoo([
    'type' => 'mysql',
    'host' => 'localhost',
    'database' => 'myapp',
    'username' => 'root',
    'password' => 'password'
]);

// Select
$users = $database->select('users', '*', [
    'status' => 'active',
    'age[>]' => 25
]);

// Insert
$database->insert('users', [
    'name' => 'John Doe',
    'email' => 'john@example.com'
]);

// Update
$database->update('users', [
    'status' => 'inactive'
], [
    'id' => 1
]);

After (Bob) ​

php
use Bob\Database\Connection;

$connection = new Connection([
    'driver' => 'mysql',
    'host' => 'localhost',
    'database' => 'myapp',
    'username' => 'root',
    'password' => 'password',
]);

// Select
$users = $connection->table('users')
    ->where('status', 'active')
    ->where('age', '>', 25)
    ->get();

// Insert
$connection->table('users')->insert([
    'name' => 'John Doe',
    'email' => 'john@example.com'
]);

// Update
$connection->table('users')
    ->where('id', 1)
    ->update(['status' => 'inactive']);

Common Migration Patterns ​

1. Database Connection Factory ​

Create a factory for managing connections:

php
class DatabaseFactory {
    private static array $connections = [];

    public static function create(string $name = 'default'): Connection {
        if (!isset(self::$connections[$name])) {
            $config = self::getConfig($name);
            self::$connections[$name] = new Connection($config);
        }

        return self::$connections[$name];
    }

    private static function getConfig(string $name): array {
        $configs = [
            'default' => [
                'driver' => $_ENV['DB_DRIVER'],
                'host' => $_ENV['DB_HOST'],
                'database' => $_ENV['DB_DATABASE'],
                'username' => $_ENV['DB_USERNAME'],
                'password' => $_ENV['DB_PASSWORD'],
            ],
            'analytics' => [
                'driver' => $_ENV['ANALYTICS_DB_DRIVER'],
                // ...
            ],
        ];

        return $configs[$name] ?? $configs['default'];
    }
}

// Usage
$db = DatabaseFactory::create();
$users = $db->table('users')->get();

2. Repository Pattern ​

Wrap Bob in repositories for better organization:

php
class UserRepository {
    private Connection $db;

    public function __construct(Connection $db) {
        $this->db = $db;
    }

    public function findActive(): array {
        return $this->db->table('users')
            ->where('status', 'active')
            ->orderBy('created_at', 'desc')
            ->get();
    }

    public function findByEmail(string $email): ?object {
        return $this->db->table('users')
            ->where('email', $email)
            ->first();
    }

    public function create(array $data): int {
        return $this->db->table('users')->insertGetId($data);
    }
}

3. Migration from Raw SQL Queries ​

Convert complex raw SQL to Bob's fluent interface:

php
// Before - Raw SQL
$sql = "
    SELECT u.*, COUNT(p.id) as post_count
    FROM users u
    LEFT JOIN posts p ON u.id = p.user_id
    WHERE u.status = 'active'
        AND u.created_at > '2024-01-01'
    GROUP BY u.id
    HAVING post_count > 5
    ORDER BY post_count DESC
    LIMIT 10
";
$stmt = $pdo->query($sql);
$topUsers = $stmt->fetchAll();

// After - Bob Query Builder
$topUsers = $connection->table('users as u')
    ->leftJoin('posts as p', 'u.id', '=', 'p.user_id')
    ->select('u.*', $connection->raw('COUNT(p.id) as post_count'))
    ->where('u.status', 'active')
    ->where('u.created_at', '>', '2024-01-01')
    ->groupBy('u.id')
    ->having('post_count', '>', 5)
    ->orderBy('post_count', 'desc')
    ->limit(10)
    ->get();

Testing Your Migration ​

1. Create Integration Tests ​

php
class MigrationTest extends PHPUnit\Framework\TestCase {
    private Connection $bob;
    private PDO $pdo;

    protected function setUp(): void {
        $config = [
            'driver' => 'sqlite',
            'database' => ':memory:',
        ];

        $this->bob = new Connection($config);
        $this->pdo = new PDO('sqlite::memory:');

        // Create test table
        $this->bob->statement('
            CREATE TABLE users (
                id INTEGER PRIMARY KEY,
                name TEXT,
                email TEXT,
                status TEXT
            )
        ');
    }

    public function testSelectMigration() {
        // Insert test data
        $this->bob->table('users')->insert([
            ['name' => 'John', 'email' => 'john@example.com', 'status' => 'active'],
            ['name' => 'Jane', 'email' => 'jane@example.com', 'status' => 'inactive'],
        ]);

        // Bob query
        $bobResults = $this->bob->table('users')
            ->where('status', 'active')
            ->get();

        // Verify results
        $this->assertCount(1, $bobResults);
        $this->assertEquals('John', $bobResults[0]->name);
    }
}

2. Performance Comparison ​

php
$start = microtime(true);

// Old method (PDO)
for ($i = 0; $i < 1000; $i++) {
    $stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
    $stmt->execute([$i]);
    $user = $stmt->fetch();
}

$pdoTime = microtime(true) - $start;

$start = microtime(true);

// New method (Bob)
for ($i = 0; $i < 1000; $i++) {
    $user = $connection->table('users')->find($i);
}

$bobTime = microtime(true) - $start;

echo "PDO Time: {$pdoTime}s\n";
echo "Bob Time: {$bobTime}s\n";

Gradual Migration Strategy ​

Phase 1: Setup ​

  1. Install Bob alongside existing code
  2. Create connection wrapper
  3. Set up logging for debugging

Phase 2: New Features ​

  1. Use Bob for all new features
  2. Keep existing code unchanged
  3. Monitor performance

Phase 3: Gradual Refactoring ​

  1. Identify most-used queries
  2. Convert one module at a time
  3. Test thoroughly after each conversion

Phase 4: Complete Migration ​

  1. Remove old database code
  2. Optimize Bob configuration
  3. Update documentation

Troubleshooting Migration Issues ​

Connection Issues ​

php
// Debug connection
try {
    $connection = new Connection($config);
    $connection->getPdo(); // Force connection
    echo "Connected successfully";
} catch (\Exception $e) {
    echo "Connection failed: " . $e->getMessage();
}

Query Differences ​

php
// Log queries to compare
$connection->enableQueryLog();

// Run your query
$results = $connection->table('users')->get();

// Check generated SQL
$log = $connection->getQueryLog();
echo $log[0]['query']; // Compare with old SQL

Performance Issues ​

php
// Enable profiling
use Bob\Database\QueryProfiler;

$profiler = new QueryProfiler();
$connection->setProfiler($profiler);
$profiler->enable();

// Run queries
// ...

// Check for slow queries
$slowQueries = $profiler->getSlowQueries();
foreach ($slowQueries as $query) {
    echo "Slow: " . $query['sql'] . " ({$query['time']}ms)\n";
}

Next Steps ​

After migration:

  1. Optimize Queries: Use Bob's profiling to identify slow queries
  2. Implement Caching: Use Bob's query caching features
  3. Add Models: Create model classes for common entities
  4. Set Up Logging: Configure logging for production monitoring
  5. Review Security: Ensure all user input is properly bound

For more details, see:

Released under the MIT License.