Completed
Push — 2.0 ( 4e4315...421ddc )
by Christopher
03:25
created

WhereScope::_alterExpression()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
rs 9.4285
cc 3
eloc 6
nc 3
nop 3
1
<?php
2
/**
3
 * Licensed under The GPL-3.0 License
4
 * For full copyright and license information, please see the LICENSE.txt
5
 * Redistributions of files must retain the above copyright notice.
6
 *
7
 * @since    2.0.0
8
 * @author   Christopher Castro <[email protected]>
9
 * @link     http://www.quickappscms.org
10
 * @license  http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License
11
 */
12
namespace Eav\Model\Behavior\QueryScope;
13
14
use Cake\Database\Expression\Comparison;
15
use Cake\Database\Expression\UnaryExpression;
16
use Cake\Database\ExpressionInterface;
17
use Cake\ORM\Query;
18
use Cake\ORM\Table;
19
use Cake\ORM\TableRegistry;
20
use Eav\Model\Behavior\EavToolbox;
21
use Eav\Model\Behavior\QueryScope\QueryScopeInterface;
22
23
/**
24
 * Used by EAV Behavior to scope WHERE statements.
25
 */
26
class WhereScope implements QueryScopeInterface
27
{
28
29
    /**
30
     * The table being managed.
31
     *
32
     * @var \Cake\ORM\Table
33
     */
34
    protected $_table = null;
35
36
    /**
37
     * Instance of toolbox.
38
     *
39
     * @var \Eav\Model\Behavior\EavToolbox
40
     */
41
    protected $_toolbox = null;
42
43
    /**
44
     * {@inheritDoc}
45
     */
46
    public function __construct(Table $table)
47
    {
48
        $this->_table = $table;
49
        $this->_toolbox = new EavToolbox($table);
50
    }
51
52
    /**
53
     * {@inheritDoc}
54
     *
55
     * Look for virtual columns in query's WHERE clause.
56
     *
57
     * @param \Cake\ORM\Query $query The query to scope
58
     * @param string|null $bundle Consider attributes only for a specific bundle
59
     * @return \Cake\ORM\Query The modified query object
60
     */
61
    public function scope(Query $query, $bundle = null)
62
    {
63
        $whereClause = $query->clause('where');
64
        if (!$whereClause) {
65
            return $query;
66
        }
67
68
        $whereClause->traverse(function (ExpressionInterface $expression) use ($bundle, $query) {
69
            $expression = $this->_alterExpression($expression, $bundle, $query);
0 ignored issues
show
Unused Code introduced by
$expression is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
70
        });
71
72
        return $query;
73
    }
74
75
    /**
76
     * Analyzes the given WHERE expression, looks for virtual columns and alters
77
     * the expressions according.
78
     *
79
     * @param \Cake\Database\ExpressionInterface $expression Expression to scope
80
     * @param string $bundle Consider attributes only for a specific bundle
81
     * @param \Cake\ORM\Query $query The query instance this expression comes from
82
     * @return \Cake\Database\ExpressionInterface The altered expression (or not)
83
     */
84
    protected function _alterExpression(ExpressionInterface $expression, $bundle, Query $query)
85
    {
86
        if ($expression instanceof Comparison) {
87
            $expression = $this->_alterComparisonExpression($expression, $bundle, $query);
88
        } elseif ($expression instanceof UnaryExpression) {
89
            debug($expression);die;
90
            // TODO: unary expressions scoping
91
        }
92
93
        return $expression;
94
    }
95
96
    /**
97
     * Analyzes the given comparison expression and alters it according.
98
     *
99
     * @param \Cake\Database\Expression\Comparison $expression Comparison expression
100
     * @param string $bundle Consider attributes only for a specific bundle
101
     * @param \Cake\ORM\Query $query The query instance this expression comes from
102
     * @return \Cake\Database\Expression\Comparison Scoped expression (or not)
103
     */
104
    protected function _alterComparisonExpression(Comparison $expression, $bundle, Query $query)
105
    {
106
        $field = $expression->getField();
107
        $column = is_string($field) ? $this->_toolbox->columnName($field) : '';
108
109
        if (empty($column) ||
110
            in_array($column, (array)$this->_table->schema()->columns()) || // ignore real columns
111
            !in_array($column, $this->_toolbox->getAttributeNames()) ||
112
            !$this->_toolbox->isSearchable($column) // ignore no searchable virtual columns
113
        ) {
114
            // nothing to alter
115
            return $expression;
116
        }
117
118
        $attr = $this->_toolbox->attributes($bundle)[$column];
119
        $value = $expression->getValue();
120
        $type = $this->_toolbox->getType($column);
121
        $conjunction = $expression->getOperator();
122
        $conditions = [
123
            'EavValues.eav_attribute_id' => $attr['id'],
124
            "EavValues.value_{$type} {$conjunction}" => $value,
125
        ];
126
127
        // subquery scope
128
        $subQuery = TableRegistry::get('Eav.EavValues')
129
            ->find()
130
            ->select('EavValues.entity_id')
131
            ->where($conditions);
132
133
        // some variables
134
        $conn = $query->connection(null);
135
        list(, $driverClass) = namespaceSplit(strtolower(get_class($conn->driver())));
136
        $alias = $this->_table->alias();
137
        $pk = $this->_table->primaryKey();
138
139
        if (!is_array($pk)) {
140
            $pk = [$pk];
141
        }
142
143
        $pk = array_map(function ($key) use ($alias) {
144
            return "{$alias}.{$key}";
145
        }, $pk);
146
147
        switch ($driverClass) {
148
            case 'sqlite':
149
                $concat = implode(' || ', $pk);
150
                $field = "({$concat} || '')";
151
                break;
152
            case 'mysql':
153
            case 'postgres':
154
            case 'sqlserver':
155
            default:
156
                $concat = implode(', ', $pk);
157
                $field = "CONCAT({$concat}, '')";
158
                break;
159
        }
160
161
        $ids = $subQuery->all()->extract('entity_id')->toArray();
162
        $ids = empty($ids) ? ['-1'] : $ids;
163
        $expression->setField($field);
164
        $expression->setValue($ids);
165
        $expression->setOperator('IN');
166
167
        $class = new \ReflectionClass($expression);
168
        $property = $class->getProperty('_type');
169
        $property->setAccessible(true);
170
        $property->setValue($expression, 'string[]');
171
172
        return $expression;
173
    }
174
}
175