J4 Issue ?
avatar 810
810
11 May 2019

Steps to reproduce the issue

Driver: Mysqli
3rt party: Kunena 6.0 alpha 3 nightly

Forum Message has 4 attachments.

C:\Users\Jelle\Documents\gitkraken\Kunena-Forum\src\libraries\kunena\attachment\helper.php:237:

SELECT * FROM #__kunena_attachmentsWHEREmesid IN (67)' (length=63)

C:\Users\Jelle\Documents\gitkraken\Kunena-Forum\src\libraries\kunena\attachment\helper.php:261: array (size=1) 245 => object(KunenaAttachment)[1933]

Code:

    $idlist = implode(',', $ids);
		$db     = Factory::getDBO();
		$query  = $db->getQuery(true);
		$query->clear()
			->select('*')
			->from($db->quoteName('#__kunena_attachments'))
			->where($db->quoteName('mesid') . ' IN (' . $idlist . ')');
		$db->setQuery((string) $query);

		try
		{
			$results = (array) $db->loadObjectList('id', 'KunenaAttachment');
		}
		catch (RuntimeException $e)
		{
			KunenaError::displayDatabaseError($e);
		}

Expected result

Loaded 4 Objects

Actual result

Just 1.

With J3.8 i see 4 objects.

System information (as much as possible)

Additional comments

avatar 810 810 - open - 11 May 2019
avatar joomla-cms-bot joomla-cms-bot - change - 11 May 2019
Labels Added: ?
avatar joomla-cms-bot joomla-cms-bot - labeled - 11 May 2019
avatar 810 810 - change - 11 May 2019
The description was changed
avatar 810 810 - edited - 11 May 2019
avatar mbabker
mbabker - comment - 11 May 2019

For starters...

  1. If you're calling $db->getQuery(true);, your first method call doesn't need to be $query->clear() because you're already dealing with a new DatabaseQuery object.

  2. $db->setQuery((string) $query); is just wasting CPU cycles, on both 3.x and 4.0. On 3.x, the PDO drivers are converting string queries into query objects for prepared statement support (not as big of a deal since very few actually use the PDO drivers), the non-PDO drivers will handle converting to string when the time is right; in 4.0 everything is internally standardized into query objects otherwise prepared statements don't work at all. So you're just unwrapping a query object to wrap it back into a new query object.

  3. A test case in https://github.com/joomla-framework/database/tree/2.0-dev for whatever drivers are affected would be beneficial as I highly doubt if there is some kind of bug that it affects 5 different drivers with 3 different code paths (MySQLi, SQL Server, and 3 PDO drivers). A code snippet with a random query from a third party extension without a database schema to work with isn't going to go very far, especially as changes in code should be accompanied by automated tests.

avatar franz-wohlkoenig franz-wohlkoenig - change - 11 May 2019
Labels Added: J4 Issue
avatar franz-wohlkoenig franz-wohlkoenig - labeled - 11 May 2019
avatar franz-wohlkoenig franz-wohlkoenig - change - 11 May 2019
Status New Discussion
avatar alikon
alikon - comment - 11 May 2019

something is wrong on your side i've tested with this silly snippet

$query->clear()
	->select('*')
	->from($db->quoteName('#__extensions'))
	->where($db->quoteName('package_id') . ' IN (802)');
 $db->setQuery($query);			
 $results =  $db->loadObjectList('extension_id');

and results have correctly the 2 instances of English (en-GB) client and admin corresponding to 802 as expected

avatar alikon alikon - change - 11 May 2019
Status Discussion Closed
Closed_Date 0000-00-00 00:00:00 2019-05-11 09:57:14
Closed_By alikon
Labels
avatar joomla-cms-bot joomla-cms-bot - change - 11 May 2019
Closed_By alikon joomla-cms-bot
Labels
avatar joomla-cms-bot joomla-cms-bot - close - 11 May 2019
avatar joomla-cms-bot
joomla-cms-bot - comment - 11 May 2019

Set to "closed" on behalf of @alikon by The JTracker Application at issues.joomla.org/joomla-cms/24857

avatar 810
810 - comment - 11 May 2019

@alikon Could you please test the loadObjectList with key and class, i think there is a issue. Else the code shouldn't be working on J3.8 too.

avatar alikon
alikon - comment - 11 May 2019

stdClass shouldn't be enough ?

avatar 810
810 - comment - 11 May 2019

Not for us, we have our own classes.

avatar xillibit
xillibit - comment - 1 Jun 2019

stdClass shouldn't be enough ?

In this case it should have KunenaAttachment object not stdClass, in previous Joomla! 3.9.x and before the method loadObjectList('id', 'KunenaAttachment') fetch the object KunenaAttachment for each row find with the query. Joomla! 4.0 doesn't behave like that, it fetch the object KunenaAttachment of first row and ignore the others rows.

avatar HLeithner
HLeithner - comment - 2 Jun 2019
avatar xillibit
xillibit - comment - 3 Jun 2019

By calling $db->loadObjectList('id'); it show as result :

array (size=2)
  11 => 
    object(stdClass)[924]
      public 'id' => int 11
      public 'mesid' => int 43
      public 'userid' => int 106
      public 'protected' => int 0
      public 'hash' => string '68ed90e0ef18f2c9414f1160d492987f' (length=32)
      public 'size' => int 146235
      public 'folder' => string 'media/kunena/attachments/106' (length=28)
      public 'filetype' => string 'image/jpeg' (length=10)
      public 'filename' => string 'IMG_20190601_115057_2019-06-03.jpg' (length=34)
      public 'filename_real' => string 'IMG_20190601_115057.jpg' (length=23)
      public 'caption' => string '' (length=0)
      public 'inline' => int 0
  12 => 
    object(stdClass)[916]
      public 'id' => int 12
      public 'mesid' => int 43
      public 'userid' => int 106
      public 'protected' => int 0
      public 'hash' => string 'ce2f0eda84fa846a02df5977fca59c81' (length=32)
      public 'size' => int 174101
      public 'folder' => string 'media/kunena/attachments/106' (length=28)
      public 'filetype' => string 'image/jpeg' (length=10)
      public 'filename' => string 'IMG_20190601_115207.jpg' (length=23)
      public 'filename_real' => string 'IMG_20190601_115207.jpg' (length=23)
      public 'caption' => string '' (length=0)
      public 'inline' => int 0

By calling $db->loadObjectList('id', 'KunenaAttachment'); it show as result :

array (size=1)
  11 => 
    object(KunenaAttachment)[924]
      public 'id' => int 11
      public 'disabled' => boolean false
      protected '_table' => string 'KunenaAttachments' (length=17)
      protected 'path' => null
      protected 'width' => null
      protected 'height' => null
      protected 'shortname' => null
      public 'folder' => string 'media/kunena/attachments/106' (length=28)
      public 'userid' => int 106
      public 'mesid' => int 43
      public 'protected' => int 0
      public 'hash' => string '68ed90e0ef18f2c9414f1160d492987f' (length=32)
      public 'size' => int 146235
      public 'filetype' => string 'image/jpeg' (length=10)
      public 'filename' => string 'IMG_20190601_115057_2019-06-03.jpg' (length=34)
      public 'filename_real' => string 'IMG_20190601_115057.jpg' (length=23)
      public 'comment' => null
      public 'inline' => int 0
      public 'typeAlias' => null
      public 'caption' => string '' (length=0)
      protected '_name' => string 'KunenaAttachment' (length=16)
      protected '_exists' => boolean false
      protected '_saving' => boolean false
      protected '_errors' => 
        array (size=0)
          empty
avatar mbabker
mbabker - comment - 3 Jun 2019

I'm going to take a guess you're using MySQLi. And I'm going to take a guess the custom class you're loading has code in its constructor which results in a database query being executed in select circumstances, and as a result the result cursor from your multi-row query is being lost. Not much that can be done here without dropping prepared statement support or dropping MySQLi support.

The long and short of it is the way things are structured right now is the way things have to be in order to support MySQL compiled with libmysql and not mysqlnd. This diff from Doctrine's DBAL covers part of the problem. As a result of this structure, mysqli_fetch_object($cursor, $class); cannot be called as is the case in 3.x; to emulate this behavior, the MysqliStatement class instantiates your custom class then sets all the properties after instantiation. Note that PDO has quirks in its fetch object behavior as well where the data is set to an object before the constructor is actually executed.

So, relying on PDOStatement::fetchObject() or mysqli_result::fetch_object() to correctly instantiate your custom objects is a bit hit or miss, especially when using prepared statements in the case of MySQLi (which 3.x does not support at all) and you're better off using a data mapping layer to convert the query result into your selected PHP class after the results are pulled.

avatar nikosdion
nikosdion - comment - 29 Apr 2021

Sorry for commenting on this old issue but it's the only reference on this weird error you can find online.

Saying that it's the developer's fault for providing a faulty class which requires initialisation is obviously wrong.

The problem really happens because the Joomla\Database\Mysqli\MysqliStatement class doesn't implement FetchMode::CUSTOM_OBJECT at all. The fetch method of that class is what throws the error.

This means that you can't reliably use the Joomla database driver's loadObjectList method with a custom class name because whether it works or not is up to the database driver being used.

This should be fixed in the upstream database package either by implementing this fetch mode in the MySQLi driver's MySQLiStatement class or by removing the inconsistent support for custom class names altogether. Unless, of course, Joomla decides on a hard PDO dependency in which case mapping the MySQLi driver to the PDO driver would work just fine but Joomla wouldn't run on a non-insignificant proportion of live and most prepackaged local hosts due to lack of default PDO support in PHP.

In the meantime, you can replace this one line code which runs great on Joomla 3:

$results = $db->loadObjectList('', $myClassName) ?: [];

with this monstrosity on Joomla 4:

$results = array_map(function (array $arr) use ($myClassName) {
  $o = new $myClassName;
  foreach ($arr as $k => $v)
  {
    $o->{$k} = $v;
  }
  return $o;
}, $db->loadAssocList() ?: []);

Add a Comment

Login with GitHub to post a comment