tom-- / yii2-dynamic-ar

An extension to add NoSQL-like documents to Yii 2 Framework's Active Record ORM.
ISC License
57 stars 15 forks source link

DynamicActiveQuery::prepare() should only select from current table #23

Open enigmatix opened 7 years ago

enigmatix commented 7 years ago

Hi,

I came across this issue when integrating DynamicActiveRecord into my project. I use scopes to restrict data access for the current user, and these scopes LEFT JOIN a secondary table to ensure that access is permitted.

Unfortunately, if this second table shares column names with the first (eg. id, name), it is these values from the second table that get assigned to the returned model.

The native Yii2 ActiveQuery class handles this by using SELECT tableAlias.* instead of SELECT *

To illustrate another way:

Table 1 - User has one record:

id name
1 joel

Table 2 - Account has one record:

id name details
1 mad max null

Example:

$user = User::findOne(1); echo $user->name; // prints 'mad max', should print 'joel'

The fix would ideally be completed at line 110 in DynamicActiveQuery like so:

    $isDefault = false;

    if (empty($this->select)) {
        list(, $alias) = $this->getTableNameAndAlias();
        $this->select = ["$alias.*"];
        $isDefault = true;

    }

    if (is_array($this->select) && $isDefault) {
        $db = $modelClass::getDb();
        $this->select[$this->_dynamicColumn] =
            'COLUMN_JSON(' . $db->quoteColumnName($this->_dynamicColumn) . ')';
    }

And by copying the getTableNameAndAlias from the native implementation in ActiveQuery

private function getTableNameAndAlias()
{
    if (empty($this->from)) {
        /* @var $modelClass ActiveRecord */
        $modelClass = $this->modelClass;
        $tableName = $modelClass::tableName();
    } else {
        $tableName = '';
        foreach ($this->from as $alias => $tableName) {
            if (is_string($alias)) {
                return [$tableName, $alias];
            } else {
                break;
            }
        }
    }

    if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $tableName, $matches)) {
        $alias = $matches[2];
    } else {
        $alias = $tableName;
    }

    return [$tableName, $alias];
}

If you accept the above and it sounds agreeable, I am happy to prepare a pull request for it.

vuongminh-xx commented 7 years ago

And specific case:

join with 2 tables both have dynamic column with the same name

 $this->select[$this->_dynamicColumn] =
            'COLUMN_JSON(' . $db->quoteColumnName($alias . '.' . $this->_dynamicColumn) . ')';