purekid / mongodm

MongoDB ORM that includes support for references,embed and multilevel inheritance.
MIT License
200 stars 47 forks source link

Switch between multiple databases #59

Open mluggy opened 10 years ago

mluggy commented 10 years ago

great package! is there an easy option to switch between different database using the same connection? preferably, have a $database complement $collection in the Model definition.

purekid commented 10 years ago

You can try changing $config in model.

mluggy commented 10 years ago

The only way we we're able to solve this:

1) Extend MongoDB to update DB on each config:

class DatabaseDynamic extends \Purekid\Mongodm\MongoDB
{
    /**
     * @param string $name name
     * @param array $config config
     */
    protected function __construct($name, array $config)
    {
        $this->_name = $name;
        $this->_config = $config;

        /* Store the database instance */
        static::$instances[$name] = $this;
    }

    /**
     * Connect to MongoDB, select database
     *
     * @throws \Exception
     * @return bool
     */
    public function connect()
    {
        if ($this->_connection) {
            return true;
        }

        $config = $this->_config['connection'];
        $result = parent::connect();
        $this->_config['connection'] = $config;

        return $result;
    }

    /**
     * Load instance
     *
     * @param string $name name
     * @param array|null $config config
     *
     * @static
     *
     * @return MongoDB
     */
    public static function instance($name = 'default', array $config = null)
    {
        if (!isset(static::$instances[$name])) {
            if ($config === null) {
                // Load the configuration for this database
                $config = static::config($name);
            }

            static::$instances[$name] = new static($name, $config);
        }

        return static::$instances[$name];
    }

    public function updateConfigDB($db)
    {
        // Extract Db from connection
        if (empty($this->_config['connection'])) {
            throw new \Exception('No Config Specified In MongoDB');
        }

        $this->_config['connection']['database'] = $db;
    }

    /**
     * @param string $db
     *
     * @throws \Exception
     * @return null
     */
    public function selectDB($db)
    {
        $this->updateConfigDB($db);
        if (!$this->_connection) {
            return;
        }

        $this->_db = $this->_connection->selectDB($db);

        if (!$this->_db instanceof \MongoDB) {
            throw new \Exception('Unable to connect to database :: $_db is ' . gettype($this->_db));
        }
    }

    /**
     * @param array $config config
     *
     * @return null
     */
    public static function setConfig($config)
    {
        static::$config = $config;
    }

    /**
     * @param string $block block
     * @param array $config config
     *
     * @return null
     */
    public static function setConfigBlock($block = 'default', $config = array())
    {
        static::$config[$block] = $config;
    }

    /**
     * @param string $config_block config_block
     *
     * @throws \Exception
     * @return array
     */
    public static function config($config_block)
    {
        if (!empty(static::$config)) {
            return static::$config[$config_block];
        }
        else {
            throw new \Exception("database config section '{$config_block}' not exist!");
        }
    }
}

2) Extend Model to use DatabaseDynamic before any action

abstract class ModelDynamic extends \Purekid\Mongodm\Model
{
    protected static $database = null;

    /**
     * Get database name
     *
     * @return string
     */
    public static function databaseName()
    {
        $class = get_called_class();
        $database = $class::$database;

        return $database;
    }

    private function useDatabaseDynamicPrivate()
    {
        $this->_connection->selectDB(static::databaseName());
    }

    public static function useDatabaseDynamic()
    {
        static::connection()->selectDB(static::databaseName());
    }

    /**
     * Model
     *
     * @param array $data data
     * @param bool $mapFields map the field names
     * @param bool $exists record exists in DB
     */
    public function __construct($data = array(), $mapFields = false, $exists = false)
    {
        if ($mapFields === true) {
            $data = static::mapFields($data, true);
        }

        if (is_null($this->_connection)) {
            if (isset($this::$config)) {
                $config = $this::$config;
            } else {
                $config = static::$config;
            }
            $this->_connection = DatabaseDynamic::instance($config);
        }

        $this->update($data, true);

        if ($exists) {
            $this->exist = true;
        } else {
            $this->initAttrs();
        }

        $this->initTypes();
        $this->__init();

    }

    /**
     * Mutate data by direct query
     *
     * @param array $updateQuery update query
     * @param array $options options
     *
     * @throws \Exception
     * @return boolean
     */
    public function mutate($updateQuery, $options = array())
    {
        $this->useDatabaseDynamicPrivate();

        return parent::mutate($updateQuery, $options);
    }

    /**
     * Delete this record
     *
     * @param array $options options
     *
     * @return boolean
     */
    public function delete($options = array())
    {
        $this->useDatabaseDynamicPrivate();

        return parent::delete($options);
    }

    /**
     * Save to database
     *
     * @param array $options options
     *
     * @return array
     */
    public function save($options = array())
    {
        $this->useDatabaseDynamicPrivate();

        return parent::save($options);
    }

    /**
     * Retrieve a record
     *
     * @param array $criteria criteria
     * @param array $fields fields
     *
     * @return Model
     */
    public static function one($criteria = array(), $fields = array())
    {
        static::useDatabaseDynamic();

        return parent::one($criteria, $fields);
    }

    /**
     * Retrieve records
     *
     * @param array $criteria criteria
     * @param array $sort sort
     * @param array $fields fields
     * @param int $limit limit
     * @param int $skip skip
     *
     * @return Collection
     */
    public static function find($criteria = array(), $sort = array(), $fields = array(), $limit = null, $skip = null)
    {
        static::useDatabaseDynamic();

        return parent::find($criteria, $sort, $fields, $limit, $skip);
    }

    /**
     * group
     *
     * @param array $keys keys
     * @param array $query query
     * @param mixed $initial initial
     * @param mixed $reduce reduce
     *
     * @return mixed
     */
    public static function group(array $keys, array $query, $initial = null, $reduce = null)
    {
        static::useDatabaseDynamic();

        return parent::group($keys, $query, $initial, $reduce);
    }

    /**
     * aggreate
     *
     * @param array $query query
     *
     * @return array
     */
    public static function aggregate($query)
    {
        static::useDatabaseDynamic();

        return parent::aggregate($query);
    }

    /**
     * Distinct records
     *
     * @param string $key key distinct key
     * @param array $criteria criteria
     *
     * @return string Records
     */
    public static function distinct($key, $criteria = array())
    {
        static::useDatabaseDynamic();
        return parent::distinct($key, $criteria);
    }

    /**
     * Has record
     *
     * A optimized way to see if a record exists in the database. Helps
     * the developer to avoid the extra latency of FindOne by using Find
     * and a limit of 1.
     *
     * @link https://blog.serverdensity.com/checking-if-a-document-exists-mongodb-slow-findone-vs-find/
     *
     * @param array $criteria criteria
     *
     * @return boolean
     */
    public static function has($criteria = array())
    {
        static::useDatabaseDynamic();
        return parent::has($criteria);
    }

    /**
     * Count of records
     *
     * @param array $criteria
     *
     * @return integer
     */
    public static function count($criteria = array())
    {
        static::useDatabaseDynamic();
        return parent::count($criteria);
    }

    /**
     * Drop the collection
     *
     * @return boolean
     */
    public static function drop()
    {
        static::useDatabaseDynamic();

        return parent::drop();
    }

    /**
     * Ensure index
     *
     * @param mixed $keys keys
     * @param array $options options
     *
     * @return boolean
     */
    public static function ensure_index($keys, $options = array())
    {
        static::useDatabaseDynamic();
        return parent::ensure_index($keys, $options);
    }

    /**
     * Return the connection
     *
     * @return MongoDB|null
     */
    public function _getConnection()
    {
        $this->useDatabaseDynamicPrivate();
        return $this->_connection;
    }

    /**
     * Get Mongodb connection instance
     *
     * @return MongoDB
     */
    protected static function connection()
    {
        $class = get_called_class();
        $config = $class::$config;

        return DatabaseDynamic::instance($config);
    }

    /**
     * Get current database name
     *
     * @return string
     */
    protected function dbName()
    {
        $dbName = "default";
        $config = $this::$config;
        $configs = DatabaseDynamic::config($config);
        if ($configs) {
            $dbName = $configs['connection']['database'];
        }

        return $dbName;
    }
}

3) Accompany each Model with a new $database param

class Model extends ModelDynamic
{
    /**
     * @var string
     */
    static $connection = 'default';

    /**
     * @var string
     */
    static $database = 'core';

    /**
     * @var string
     */
    static $collection = 'language';

    /**
     * @var array
     */
    static $attrs = [
        'name' => ['type' => 'string'],
        'isActive' => ['type' => 'boolean', 'default' => false],
    ];
}

4) Connect through the new DatabaseDynamic block:

_\Model\DatabaseDynamic::setConfigBlock('default', [
    'connection' => [
        'hostnames' => '10.10.2.111:27017',
        'database' => 'core'
    ]
]);

Changing all self:: to static or passing an optional $database name on every __call would have made this much simpler...