Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applying doctrine sql filter in many to many relationships

I have a many to many relationships in my project (user_role, grades, user_role_grades). But I also have a requirement not to delete any data from my db. So, I have add a status column to the table, that connecting 2 tables to create many to many relationship. Now I want on

 $userRole->getGrades() 

get only those records, that in unite table (user_role_grades) has no status "0". For those, I`m trying to use doctrine sql filter.

namespace Bis\MpBundle\Filter;
use \Doctrine\ORM\Mapping\ClassMetaData;

class UserRoleGradeFilter extends \Doctrine\ORM\Query\Filter\SQLFilter
{
    public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
    {
        if("Bis\DefaultBundle\Entity\UserRoleGrade" == $targetEntity->name){

            return $targetTableAlias . '.status != 0';
        }

        return '';
    }
}

So, it is called, for Bis\DefaultBundle\Entity\UserRole, but not for Bis\DefaultBundle\Entity\UserRoleGrade entity. Have anyone any ideas?

Or may be you have some other ideas, how I can do that?

like image 639
Artsiom Tymchanka Avatar asked Sep 04 '12 11:09

Artsiom Tymchanka


2 Answers

I don't think this is possible, because it appends directly SQL. Even if you'd try sth like SQL injection:

return $targetTableAlias . '.status != 0)) LEFT join the_other_table ON  '
. $targetTableAlias . '.grades HAVING the_other_table.status = 0 ((';

It would probably collapse on statement like (())

like image 181
Tomas Votruba Avatar answered Oct 20 '22 02:10

Tomas Votruba


You can call $targetEntity->getAssociationMappings()['yourFieldName'] and if joinTable key exist that mean your have manyToMany relation. Where yourFieldName your field with ManyToMany relation.

Correct table alias you can get from Doctrine\ORM\Query\SqlWalker::getSQLTableAlias(). I get SqlWalker with debug_backtrace, not an elegant solution but better I've not found.

/**
 * Get SqlWalker with debug_backtrace
 *
 * @return null|SqlWalker
 */
protected function getSqlWalker()
{
    $caller = debug_backtrace();
    $caller = $caller[2];

    if (isset($caller['object'])) {
        return $caller['object'];
    }

    return null;
}

Full implementation of my addFilterConstraint method

    public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
    if (empty($this->reader)) {
        return '';
    }

    // The Doctrine filter is called for any query on any entity
    // Check if the current entity is "pool aware" (marked with an annotation)
    $poolAware = $this->reader->getClassAnnotation(
        $targetEntity->getReflectionClass(),
        PoolAware::class
    );
    if (!$poolAware) {
        return '';
    }

    if (!$poolId = $this->getParameter('poolId')) {
        return '';
    }

    $fieldName = $poolAware->getFieldName();
    if (empty($fieldName)) {
        return '';
    }

    if (!$sqlWalker = $this->getSqlWalker()) {
        return '';
    }

    if (!isset($targetEntity->getAssociationMappings()[$fieldName])) {
        return '';
    }

    $mapping = $targetEntity->getAssociationMappings()[$fieldName];

    if (isset($mapping['joinColumns'])) {
        // oneToMany relation detected
        $table = $targetEntity->getTableName();
        $columnName = $mapping['joinColumns'][0]['name'];
        $dqlAlias = constant($targetEntity->getName() . '::MNEMO');
    } elseif (isset($mapping['joinTable'])) {
        // manyToMany relation detected
        $dqlAlias = constant($mapping['targetEntity'] . '::MNEMO');
        $component = $sqlWalker->getQueryComponent($dqlAlias);

        // Only main entity in query is interesting for us,
        // otherwise do not apply any filter
        if ($component['parent']) {
            return '';
        }
        $table = $mapping['joinTable']['name'];
        $columnName = $mapping['joinTable']['inverseJoinColumns'][0]['name'];
    } else {
        return '';
    }

    $tableAlias = ($sqlWalker instanceof BasicEntityPersister)
        ? $targetTableAlias // $repository->findBy() has been called
        : $sqlWalker->getSQLTableAlias($table, $dqlAlias);

    $query = sprintf('%s.%s = %s', $tableAlias, $columnName, $this->getConnection()->quote(poolId));

    return $query;
}

All our Doctrine models have MNEMO constant which is simple name of a model. Abc\Module\Model\Product has MNEMO product. This MNEMO is equivalent of _alias in Repository class. That's why we apply this value to $dqlAlias

Full code explanation you can read here

like image 26
Serhii Popov Avatar answered Oct 20 '22 01:10

Serhii Popov