2

I'm using Yii1 ORM and I struggled for two hours to figure out an issue with findAll. First, let's see a raw query:

select * from lime_group_l10ns where gid in (232, 234);

this yields:

+------+-----+----------------------+--------------+----------+
| id   | gid | group_name           | description  | language |
+------+-----+----------------------+--------------+----------+
| 1340 | 232 | abc Q927 def         | ghi Q926 jkl | en       |
| 1342 | 234 | Question group three |              | en       |
+------+-----+----------------------+--------------+----------+
2 rows in set (0.00 sec)

so far so good. Now, I have written this:

        $groups = QuestionGroupL10n::model()->findAll("gid in (232, 234)");
        foreach ($groups as $group) {
            echo $group->gid . "<br>";
        }

which is the equivalent of the query I have given and I would expect it to output

232
234

but it only displays

234

so it seems to only take into account the very last element and skips the one before it. I have resolved this issue via

            $fields = ['description', 'group_name'];
            foreach ($gids as $gid) {
                if ($gid != 0) {
                    $group = QuestionGroupl10n::model()->find("gid=" . $gid);
                    if ($this->fixText($group, $fields, $names) || $this->fixText($group, $fields, $additionalNames)) {
                        $group->save();
                    }
                }
            }

where I am looping the group ids previously computed and do some changes if needed. This works. But it does an individual request for each group, which does not look good. How can I make Yii1 ORM findAll to take into account all elements involved? Sometimes I crave to write raw queries instead of ORMs and this is exactly such a moment. Can we say that I'm just missing something and there exists a fix maybe related to the usage?

EDIT

Further information has been asked for. Here's the table's create statement:

CREATE TABLE `lime_group_l10ns` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `gid` int(11) NOT NULL,
  `group_name` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `description` mediumtext COLLATE utf8mb4_unicode_ci,
  `language` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `lime_idx1_group_ls` (`gid`,`language`)
) ENGINE=MyISAM AUTO_INCREMENT=1943 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

and this is the model's source-code:

<?php

/*
 * LimeSurvey
 * Copyright (C) 2007-2011 The LimeSurvey Project Team / Carsten Schmitz
 * All rights reserved.
 * License: GNU/GPL License v2 or later, see LICENSE.php
 * LimeSurvey is free software. This version may have been modified pursuant
 * to the GNU General Public License, and as distributed it includes or
 * is derivative of works licensed under the GNU General Public License or
 * other free or open source software licenses.
 * See COPYRIGHT.php for copyright notices and details.
 *
 */

/**
 * Class SurveyLanguageSetting
 *
 * @property string $language QuestionGroup language code. Note: There is a unique key on qid & language columns combined
 * @property string $group_name QuestionGroup dieplay text. The actual question.
 * @property string $description QuestionGroup help-text for display
 */
class QuestionGroupL10n extends LSActiveRecord
{
    /** @inheritdoc */
    public function tableName()
    {
        return '{{group_l10ns}}';
    }

    /** @inheritdoc */
    public function primaryKey()
    {
        return 'id';
    }

    /**
     * @inheritdoc
     * @return self
     */
    public static function model($className = __CLASS__)
    {
        /** @var self $model */
        $model = parent::model($className);
        return $model;
    }

    /** @inheritdoc */
    public function relations()
    {
        $alias = $this->getTableAlias();
        return array(
            'group' => array(self::BELONGS_TO, QuestionGroup::class, '', 'on' => "$alias.gid = group.gid"),
        );
    }

    public function defaultScope()
    {
        return array('index' => 'language');
    }

    /** @inheritdoc */
    public function rules()
    {
        return array(
            array('group_name,description', 'LSYii_Validators'),
            array('language', 'length', 'min' => 2, 'max' => 20), // in array languages ?
            array('gid', 'unique', 'criteria' => array(
                'condition' => 'language=:language',
                'params' => array(':language' => $this->language)
                ),
                'message' => sprintf(gT("Group ID (gid): “%s” already set with language ”%s”."), $this->gid, $this->language),
            ),
        );
    }

    /** @inheritdoc */
    public function attributeLabels()
    {
        return array(
            'language' => gT('Language'),
            'group_name' => gT('Group name'),
        );
    }
}

EDIT2

A further request was related to the raw query. In order to easily figure it out, I induced temporarily an error into the code:

        $groups = QuestionGroupL10n::model()->findAll("gid in (232, 234)foobar");
        foreach ($groups as $group) {
            echo $group->gid . "<br>";
        }
        exit;

and this is the error message:

CDbCommand failed to execute the SQL statement: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'foobar' at line 1. The SQL statement executed was: SELECT * FROM `lime_group_l10ns` `t` WHERE gid in (232, 234)foobar

So the query is

SELECT * FROM `lime_group_l10ns` `t` WHERE gid in (232, 234)

which is, as you can see the raw query I have given at the start of the question. And, as you can see at the start of the question, when executing this raw query, we end up having two records. If I execute the code in the ORM (without the error of course), then I get a single result.

EDIT3

Plurality:

Code:

        $groups = QuestionGroupL10n::model()->findAll();
        foreach ($groups as $group) {
            echo $group->gid . "<br>";
        }
        exit;

Output:

260
494
260
260
260
260
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259
259

Therefore, as tested multiple times, SomeModel::model()->findAll("<yourquery>") is able to find and loop over multiple results. In this particular case it is extremely strange that 232 and 234 is not among the results, which is further sign of inconsistency of findAll or something that I missed.

14
  • Does your model have a primary key defined, and is it correct? Sounds like it's overwriting the results as it loops through them Commented Aug 6 at 16:30
  • Do you have access to the generated SQL query? Commented Aug 6 at 20:19
  • Maybe you have not indexed array and always assign to same key? Write it as foreach ($groups as $key => $group) { and print out $key, check if it's 0. Or turn on step debugging with XDebug Commented Aug 7 at 6:30
  • @rjdown both the table and the model have primary key defined. It is unclear to me whether everything is correct, because I did not spot an issue, yet, it works in a really awkward manner. "Sounds like it's overwriting the results as it loops through them" It cannot be the case in my example. I have a single match in the result set before the loop starts. It should not be the case, but it is. Commented Aug 7 at 8:42
  • @rjdown I have added the details you asked into the question. Commented Aug 7 at 8:49

1 Answer 1

1

TLDR:

$model = new QuestionGroupL10n();
$model->resetScope();
... $model->findAll() ...

This can be hair pulling, but at the end of the day it's something everyone writing PHP actually has already heard about or knows thanks to the array, the swiss-army-knife of data-structures.

The YII Active Record (AR) model's findAll() method returns an array with zero or more entries (one for each model).

By default the array is integer indexed (0, 1, 2, ...), the array is a list (compare array_is_list().)

However, the specific model in the question is with the following default scope:

<?php

/*
 * LimeSurvey
 * Copyright (C) 2007-2011 The LimeSurvey Project Team / Carsten Schmitz
 * All rights reserved.
 * License: GNU/GPL License v2 or later, see LICENSE.php
 * LimeSurvey is free software. This version may have been modified pursuant
 * to the GNU General Public License, and as distributed it includes or
 * is derivative of works licensed under the GNU General Public License or
 * other free or open source software licenses.
 * See COPYRIGHT.php for copyright notices and details.
 *
 */

/**
 * Class SurveyLanguageSetting
 *
 * @property string $language QuestionGroup language code. Note: There is a unique key on qid & language columns combined
 * @property string $group_name QuestionGroup dieplay text. The actual question.
 * @property string $description QuestionGroup help-text for display
 */
class QuestionGroupL10n extends LSActiveRecord
{
    /*** ... ********************************/

    public function defaultScope()
    {
        return array('index' => 'language');
    }

    /*** ... ********************************/
}

The returned array is a parametrization for the "[...] query criteria. This will be used as the parameter to the constructor of CDbCriteria.". (ref. defaultScope())

The reserved-name 'index' summoned the unwanted behaviour, the array as a container didn't had the capacity to return more than one model per language any longer, as the string value of the language column was used as the array key.

This also explains, why the SQL query looked correct, but the result of the AR query not.

The resetScope() method on the model restores the integer indexing of the array.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.