diff --git a/administrator/components/com_config/src/Model/ApplicationModel.php b/administrator/components/com_config/src/Model/ApplicationModel.php index c907e1bbb4435..77d70c8669785 100644 --- a/administrator/components/com_config/src/Model/ApplicationModel.php +++ b/administrator/components/com_config/src/Model/ApplicationModel.php @@ -93,7 +93,7 @@ public function getData() $data = ArrayHelper::fromObject($config); // Get the correct driver at runtime - $data['dbtype'] = Factory::getDbo()->getName(); + $data['dbtype'] = $this->getDatabase()->getName(); // Prime the asset_id for the rules. $data['asset_id'] = 1; @@ -502,11 +502,12 @@ public function save($data) // Purge the database session table if we are changing to the database handler. if ($prev['session_handler'] != 'database' && $data['session_handler'] == 'database') { - $query = $this->_db->getQuery(true) - ->delete($this->_db->quoteName('#__session')) - ->where($this->_db->quoteName('time') . ' < ' . (time() - 1)); - $this->_db->setQuery($query); - $this->_db->execute(); + $db = $this->getDatabase(); + $query = $db->getQuery(true) + ->delete($db->quoteName('#__session')) + ->where($db->quoteName('time') . ' < ' . (time() - 1)); + $db->setQuery($query); + $db->execute(); } // Purge the database session table if we are disabling session metadata @@ -1170,16 +1171,19 @@ public function storePermissions($permission = null) try { + // The database instance + $db = $this->getDatabase(); + // Get the asset id by the name of the component. - $query = $this->_db->getQuery(true) - ->select($this->_db->quoteName('id')) - ->from($this->_db->quoteName('#__assets')) - ->where($this->_db->quoteName('name') . ' = :component') + $query = $db->getQuery(true) + ->select($db->quoteName('id')) + ->from($db->quoteName('#__assets')) + ->where($db->quoteName('name') . ' = :component') ->bind(':component', $permission['component']); - $this->_db->setQuery($query); + $db->setQuery($query); - $assetId = (int) $this->_db->loadResult(); + $assetId = (int) $db->loadResult(); // Fetch the parent asset id. $parentAssetId = null; @@ -1196,38 +1200,38 @@ public function storePermissions($permission = null) { // In this case we need to get the component rules too. $query->clear() - ->select($this->_db->quoteName('parent_id')) - ->from($this->_db->quoteName('#__assets')) - ->where($this->_db->quoteName('id') . ' = :assetid') + ->select($db->quoteName('parent_id')) + ->from($db->quoteName('#__assets')) + ->where($db->quoteName('id') . ' = :assetid') ->bind(':assetid', $assetId, ParameterType::INTEGER); - $this->_db->setQuery($query); + $db->setQuery($query); - $parentAssetId = (int) $this->_db->loadResult(); + $parentAssetId = (int) $db->loadResult(); } // Get the group parent id of the current group. $rule = (int) $permission['rule']; $query->clear() - ->select($this->_db->quoteName('parent_id')) - ->from($this->_db->quoteName('#__usergroups')) - ->where($this->_db->quoteName('id') . ' = :rule') + ->select($db->quoteName('parent_id')) + ->from($db->quoteName('#__usergroups')) + ->where($db->quoteName('id') . ' = :rule') ->bind(':rule', $rule, ParameterType::INTEGER); - $this->_db->setQuery($query); + $db->setQuery($query); - $parentGroupId = (int) $this->_db->loadResult(); + $parentGroupId = (int) $db->loadResult(); // Count the number of child groups of the current group. $query->clear() - ->select('COUNT(' . $this->_db->quoteName('id') . ')') - ->from($this->_db->quoteName('#__usergroups')) - ->where($this->_db->quoteName('parent_id') . ' = :rule') + ->select('COUNT(' . $db->quoteName('id') . ')') + ->from($db->quoteName('#__usergroups')) + ->where($db->quoteName('parent_id') . ' = :rule') ->bind(':rule', $rule, ParameterType::INTEGER); - $this->_db->setQuery($query); + $db->setQuery($query); - $totalChildGroups = (int) $this->_db->loadResult(); + $totalChildGroups = (int) $db->loadResult(); } catch (\Exception $e) { diff --git a/composer.lock b/composer.lock index 20fb94a60c83b..4cf76a6597b22 100644 --- a/composer.lock +++ b/composer.lock @@ -1180,16 +1180,16 @@ }, { "name": "joomla/database", - "version": "2.0.2", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/joomla-framework/database.git", - "reference": "142a624df2f48858467de67a4542d77807cc968b" + "reference": "194415339358b3ded43d5f68446b4fa93e18c3d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/joomla-framework/database/zipball/142a624df2f48858467de67a4542d77807cc968b", - "reference": "142a624df2f48858467de67a4542d77807cc968b", + "url": "https://api.github.com/repos/joomla-framework/database/zipball/194415339358b3ded43d5f68446b4fa93e18c3d3", + "reference": "194415339358b3ded43d5f68446b4fa93e18c3d3", "shasum": "" }, "require": { @@ -1244,7 +1244,7 @@ ], "support": { "issues": "https://github.com/joomla-framework/database/issues", - "source": "https://github.com/joomla-framework/database/tree/2.0.2" + "source": "https://github.com/joomla-framework/database/tree/2.1.0" }, "funding": [ { @@ -1256,7 +1256,7 @@ "type": "github" } ], - "time": "2022-01-10T02:28:52+00:00" + "time": "2022-03-02T16:36:31+00:00" }, { "name": "joomla/di", diff --git a/libraries/src/Extension/Service/Provider/MVCFactory.php b/libraries/src/Extension/Service/Provider/MVCFactory.php index 7213a1a87674b..bdc5c02872dfb 100644 --- a/libraries/src/Extension/Service/Provider/MVCFactory.php +++ b/libraries/src/Extension/Service/Provider/MVCFactory.php @@ -14,6 +14,7 @@ use Joomla\CMS\MVC\Factory\ApiMVCFactory; use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\Router\SiteRouter; +use Joomla\Database\DatabaseInterface; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; use Joomla\Event\DispatcherInterface; @@ -72,6 +73,7 @@ function (Container $container) $factory->setFormFactory($container->get(FormFactoryInterface::class)); $factory->setDispatcher($container->get(DispatcherInterface::class)); + $factory->setDatabase($container->get(DatabaseInterface::class)); $factory->setSiteRouter($container->get(SiteRouter::class)); return $factory; diff --git a/libraries/src/MVC/Factory/MVCFactory.php b/libraries/src/MVC/Factory/MVCFactory.php index f437456b8f1ec..3d427184bb6db 100644 --- a/libraries/src/MVC/Factory/MVCFactory.php +++ b/libraries/src/MVC/Factory/MVCFactory.php @@ -17,6 +17,10 @@ use Joomla\CMS\MVC\Model\ModelInterface; use Joomla\CMS\Router\SiteRouterAwareInterface; use Joomla\CMS\Router\SiteRouterAwareTrait; +use Joomla\Database\DatabaseAwareInterface; +use Joomla\Database\DatabaseAwareTrait; +use Joomla\Database\DatabaseInterface; +use Joomla\Database\Exception\DatabaseNotFoundException; use Joomla\Event\DispatcherAwareInterface; use Joomla\Event\DispatcherAwareTrait; use Joomla\Input\Input; @@ -28,7 +32,7 @@ */ class MVCFactory implements MVCFactoryInterface, FormFactoryAwareInterface, SiteRouterAwareInterface { - use FormFactoryAwareTrait, DispatcherAwareTrait, SiteRouterAwareTrait; + use FormFactoryAwareTrait, DispatcherAwareTrait, DatabaseAwareTrait, SiteRouterAwareTrait; /** * The namespace to create the objects from. @@ -129,6 +133,19 @@ public function createModel($name, $prefix = '', array $config = []) $this->setDispatcherOnObject($model); $this->setRouterOnObject($model); + if ($model instanceof DatabaseAwareInterface) + { + try + { + $model->setDatabase($this->getDatabase()); + } + catch (DatabaseNotFoundException $e) + { + @trigger_error(sprintf('Database must be set, this will not be catched anymore in 5.0.'), E_USER_DEPRECATED); + $model->setDatabase(Factory::getContainer()->get(DatabaseInterface::class)); + } + } + return $model; } @@ -219,13 +236,14 @@ public function createTable($name, $prefix = '', array $config = []) return null; } - if (\array_key_exists('dbo', $config)) + try { - $db = $config['dbo']; + $db = \array_key_exists('dbo', $config) ? $config['dbo'] : $this->getDatabase(); } - else + catch (DatabaseNotFoundException $e) { - $db = Factory::getDbo(); + @trigger_error(sprintf('Database must be set, this will not be catched anymore in 5.0.'), E_USER_DEPRECATED); + $db = Factory::getContainer()->get(DatabaseInterface::class); } return new $className($db); diff --git a/libraries/src/MVC/Model/BaseDatabaseModel.php b/libraries/src/MVC/Model/BaseDatabaseModel.php index 71406102d3a1b..47976c6d6c99a 100644 --- a/libraries/src/MVC/Model/BaseDatabaseModel.php +++ b/libraries/src/MVC/Model/BaseDatabaseModel.php @@ -22,7 +22,11 @@ use Joomla\CMS\MVC\Factory\MVCFactoryInterface; use Joomla\CMS\MVC\Factory\MVCFactoryServiceInterface; use Joomla\CMS\Table\Table; +use Joomla\Database\DatabaseAwareInterface; +use Joomla\Database\DatabaseAwareTrait; +use Joomla\Database\DatabaseInterface; use Joomla\Database\DatabaseQuery; +use Joomla\Database\Exception\DatabaseNotFoundException; use Joomla\Event\DispatcherAwareInterface; use Joomla\Event\DispatcherAwareTrait; use Joomla\Event\DispatcherInterface; @@ -36,7 +40,7 @@ * * @since 2.5.5 */ -abstract class BaseDatabaseModel extends BaseModel implements DatabaseModelInterface, DispatcherAwareInterface +abstract class BaseDatabaseModel extends BaseModel implements DatabaseModelInterface, DatabaseAwareInterface, DispatcherAwareInterface { use DatabaseAwareTrait, MVCFactoryAwareTrait, DispatcherAwareTrait; @@ -82,7 +86,17 @@ public function __construct($config = array(), MVCFactoryInterface $factory = nu $this->option = ComponentHelper::getComponentName($this, $r[1]); } - $this->setDbo(\array_key_exists('dbo', $config) ? $config['dbo'] : Factory::getDbo()); + /** + * @deprecated 5.0 Database instance is injected through the setter function, + * subclasses should not use the db instance in constructor anymore + */ + $db = \array_key_exists('dbo', $config) ? $config['dbo'] : Factory::getDbo(); + + if ($db) + { + @trigger_error(sprintf('Database is not available in constructor in 5.0.'), E_USER_DEPRECATED); + $this->setDatabase($db); + } // Set the default view search path if (\array_key_exists('table_path', $config)) @@ -139,13 +153,13 @@ protected function _getList($query, $limitstart = 0, $limit = 0) { if (\is_string($query)) { - $query = $this->getDbo()->getQuery(true)->setQuery($query); + $query = $this->getDatabase()->getQuery(true)->setQuery($query); } $query->setLimit($limit, $limitstart); - $this->getDbo()->setQuery($query); + $this->getDatabase()->setQuery($query); - return $this->getDbo()->loadObjectList(); + return $this->getDatabase()->loadObjectList(); } /** @@ -175,9 +189,9 @@ protected function _getListCount($query) $query = clone $query; $query->clear('select')->clear('order')->clear('limit')->clear('offset')->select('COUNT(*)'); - $this->getDbo()->setQuery($query); + $this->getDatabase()->setQuery($query); - return (int) $this->getDbo()->loadResult(); + return (int) $this->getDatabase()->loadResult(); } // Otherwise fall back to inefficient way of counting all results. @@ -189,10 +203,10 @@ protected function _getListCount($query) $query->clear('limit')->clear('offset')->clear('order'); } - $this->getDbo()->setQuery($query); - $this->getDbo()->execute(); + $this->getDatabase()->setQuery($query); + $this->getDatabase()->execute(); - return (int) $this->getDbo()->getNumRows(); + return (int) $this->getDatabase()->getNumRows(); } /** @@ -212,7 +226,7 @@ protected function _createTable($name, $prefix = 'Table', $config = array()) // Make sure we are returning a DBO object if (!\array_key_exists('dbo', $config)) { - $config['dbo'] = $this->getDbo(); + $config['dbo'] = $this->getDatabase(); } return $this->getMVCFactory()->createTable($name, $prefix, $config); @@ -339,4 +353,68 @@ protected function dispatchEvent(EventInterface $event) Factory::getContainer()->get(DispatcherInterface::class)->dispatch($event->getName(), $event); } } + + /** + * Get the database driver. + * + * @return DatabaseInterface The database driver. + * + * @since __DEPLOY_VERSION__ + * @throws \UnexpectedValueException + * + * @deprecated 5.0 Use getDatabase() instead + */ + public function getDbo(): DatabaseInterface + { + try + { + return $this->getDatabase(); + } + catch (DatabaseNotFoundException $e) + { + throw new \UnexpectedValueException('Database driver not set in ' . __CLASS__); + } + } + + /** + * Set the database driver. + * + * @param DatabaseInterface $db The database driver. + * + * @return void + * + * @since __DEPLOY_VERSION__ + * + * @deprecated 5.0 Use setDatabase() instead + */ + public function setDbo(DatabaseInterface $db = null): void + { + if ($db === null) + { + return; + } + + $this->setDatabase($db); + } + + /** + * Proxy for _db variable. + * + * @param string $name The name of the element + * + * @return mixed The value of the element if set, null otherwise + * + * @since __DEPLOY_VERSION__ + * + * @deprecated 5.0 Use getDatabase() instead of directly accessing _db + */ + public function __get($name) + { + if ($name === '_db') + { + return $this->getDatabase(); + } + + return $this->$name; + } } diff --git a/libraries/src/MVC/Model/DatabaseAwareTrait.php b/libraries/src/MVC/Model/DatabaseAwareTrait.php index 5157b3b844792..c94c453ec7733 100644 --- a/libraries/src/MVC/Model/DatabaseAwareTrait.php +++ b/libraries/src/MVC/Model/DatabaseAwareTrait.php @@ -16,6 +16,8 @@ * Database aware trait. * * @since 4.0.0 + * + * @deprecated 5.0 Use the trait from the database package */ trait DatabaseAwareTrait { @@ -24,6 +26,8 @@ trait DatabaseAwareTrait * * @var DatabaseInterface * @since 4.0.0 + * + * @deprecated 5.0 Use the trait from the database package */ protected $_db; @@ -34,6 +38,8 @@ trait DatabaseAwareTrait * * @since 4.0.0 * @throws \UnexpectedValueException + * + * @deprecated 5.0 Use the trait from the database package */ public function getDbo() { @@ -53,6 +59,8 @@ public function getDbo() * @return void * * @since 4.0.0 + * + * @deprecated 5.0 Use the trait from the database package */ public function setDbo(DatabaseInterface $db = null) { diff --git a/tests/Unit/Libraries/Cms/MVC/Model/DatabaseModelTest.php b/tests/Unit/Libraries/Cms/MVC/Model/DatabaseModelTest.php new file mode 100644 index 0000000000000..68c249cb3996e --- /dev/null +++ b/tests/Unit/Libraries/Cms/MVC/Model/DatabaseModelTest.php @@ -0,0 +1,236 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\MVC\Model; + +use Exception; +use Joomla\CMS\MVC\Factory\MVCFactoryInterface; +use Joomla\CMS\MVC\Model\BaseDatabaseModel; +use Joomla\CMS\Table\Table; +use Joomla\Database\DatabaseInterface; +use Joomla\Database\DatabaseQuery; +use Joomla\Database\QueryInterface; +use Joomla\Tests\Unit\UnitTestCase; + +/** + * Test class for \Joomla\CMS\MVC\Model\BaseDatabaseModel + * + * @package Joomla.UnitTest + * @subpackage MVC + * @since __DEPLOY_VERSION__ + */ +class DatabaseModelTest extends UnitTestCase +{ + /** + * @testdox Test that the BaseDatabaseModel contains the right db and MVC factory + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testInjectedDatabaseAndMVCFactory() + { + $db = $this->createStub(DatabaseInterface::class); + $mvcFactory = $this->createStub(MVCFactoryInterface::class); + + $model = new class(['dbo' => $db], $mvcFactory) extends BaseDatabaseModel + { + public function getDatabase(): DatabaseInterface + { + return parent::getDatabase(); + } + + public function getMVCFactory(): MVCFactoryInterface + { + return parent::getMVCFactory(); + } + }; + + $this->assertEquals($db, $model->getDatabase()); + $this->assertEquals($mvcFactory, $model->getMVCFactory()); + } + + /** + * @testdox Test that the BaseDatabaseModel returns the right table + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testGetTable() + { + $table = $this->createStub(Table::class); + $mvcFactory = $this->createStub(MVCFactoryInterface::class); + $mvcFactory->method('createTable')->willReturn($table); + + $model = new class(['dbo' => $this->createStub(DatabaseInterface::class)], $mvcFactory) extends BaseDatabaseModel + {}; + + $this->assertEquals($table, $model->getTable()); + } + + /** + * @testdox Test that the BaseDatabaseModel throws an exception when no table can be created + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testGetTableWhenNull() + { + $mvcFactory = $this->createStub(MVCFactoryInterface::class); + $mvcFactory->method('createTable')->willReturn(null); + + $model = new class(['dbo' => $this->createStub(DatabaseInterface::class)], $mvcFactory) extends BaseDatabaseModel + {}; + + $this->expectException(Exception::class); + $model->getTable(); + } + + /** + * @testdox Test that the BaseDatabaseModel returns the right list when the query is an object + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testGetListFromObject() + { + $db = $this->createStub(DatabaseInterface::class); + $db->method('loadObjectList')->willReturn([1]); + + $model = new class(['dbo' => $db], $this->createStub(MVCFactoryInterface::class)) extends BaseDatabaseModel + { + public function _getList($query, $limitstart = 0, $limit = 0) + { + return parent::_getList($query, $limitstart, $limit); + } + }; + + $this->assertEquals([1], $model->_getList($this->getQueryStub($db), 0, 1)); + } + + /** + * @testdox Test that the BaseDatabaseModel returns the right list when the query is a string + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testGetListFromString() + { + $db = $this->createStub(DatabaseInterface::class); + $db->method('loadObjectList')->willReturn([1]); + $db->method('getQuery')->willReturn($this->getQueryStub($db)); + + $model = new class(['dbo' => $db], $this->createStub(MVCFactoryInterface::class)) extends BaseDatabaseModel + { + public function _getList($query, $limitstart = 0, $limit = 0) + { + return parent::_getList($query, $limitstart, $limit); + } + }; + + $this->assertEquals([1], $model->_getList('query', 0, 1)); + } + + /** + * @testdox Test that the BaseDatabaseModel returns the right list count from a query object + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testGetListCountFromObject() + { + $db = $this->createStub(DatabaseInterface::class); + $db->method('getNumRows')->willReturn(5); + + $model = new class(['dbo' => $db], $this->createStub(MVCFactoryInterface::class)) extends BaseDatabaseModel + { + public function _getListCount($query) + { + return parent::_getListCount($query); + } + }; + + $this->assertEquals(5, $model->_getListCount($this->getQueryStub($db))); + } + + /** + * @testdox Test that the BaseDatabaseModel returns the right list count from a query object + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testGetListCountFromObjectTypeSelect() + { + $db = $this->createStub(DatabaseInterface::class); + $db->method('loadResult')->willReturn(5); + + $model = new class(['dbo' => $db], $this->createStub(MVCFactoryInterface::class)) extends BaseDatabaseModel + { + public function _getListCount($query) + { + return parent::_getListCount($query); + } + }; + + $query = $this->getQueryStub($db); + $query->select('*'); + + $this->assertEquals(5, $model->_getListCount($query)); + } + + /** + * @testdox Test that the BaseDatabaseModel returns the right list count from a query string + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function testGetListCountFromString() + { + $db = $this->createStub(DatabaseInterface::class); + $db->method('getNumRows')->willReturn(5); + + $model = new class(['dbo' => $db], $this->createStub(MVCFactoryInterface::class)) extends BaseDatabaseModel + { + public function _getListCount($query) + { + return parent::_getListCount($query); + } + }; + + $this->assertEquals(5, $model->_getListCount('query')); + } + + /** + * Returns a database query instance. + * + * @param DatabaseInterface $db The database + * + * @return QueryInterface + * + * @since __DEPLOY_VERSION__ + */ + private function getQueryStub(DatabaseInterface $db): QueryInterface + { + return new class($db) extends DatabaseQuery + { + public function groupConcat($expression, $separator = ',') + {} + + public function processLimit($query, $limit, $offset = 0) + {} + }; + } +}