Bitrix-D7  20.0.0
expressionfield.php
См. документацию.
1 <?php
2 /**
3  * Bitrix Framework
4  * @package bitrix
5  * @subpackage main
6  * @copyright 2001-2012 Bitrix
7  */
8 
9 namespace Bitrix\Main\ORM\Fields;
16 
17 /**
18  * Expr field is to describe dynamic fields by expression, e.g. we have PRICE_USD field and need to count price in EUR
19  * then we define expression field PRICE_EUR with expression = array('%s * 1.25', 'PRICE_USD')
20  * @package bitrix
21  * @subpackage main
22  */
23 class ExpressionField extends Field implements IReadable
24 {
25  /**
26  * @var string
27  */
28  protected $expression;
29 
30  /**
31  * Full expression, recursively includes expressions from buildFrom fields
32  * @var string
33  */
34  protected $fullExpression;
35 
36  /** @var string Scalar field class name */
37  protected $valueType;
38 
39  /**
40  * @var ScalarField
41  */
42  protected $valueField;
43 
44  /**
45  * @var array
46  */
47  protected $buildFrom;
48 
49  /** @var \Bitrix\Main\ORM\Query\Chain[] */
50  protected $buildFromChains;
51 
52  protected $isAggregated;
53 
54  protected $hasSubquery;
55 
56  protected static
57  $aggrFunctionsMYSQL = array('AVG', 'BIT_AND', 'BIT_OR', 'BIT_XOR', 'COUNT',
58  'GROUP_CONCAT', 'MAX', 'MIN', 'STD', 'STDDEV_POP', 'STDDEV_SAMP',
59  'STDDEV', 'SUM', 'VAR_POP', 'VAR_SAMP', 'VARIANCE'
60  ),
61  $aggrFunctionsMSSQL = array('AVG', 'MIN', 'CHECKSUM_AGG', 'OVER', 'COUNT',
62  'ROWCOUNT_BIG', 'COUNT_BIG', 'STDEV', 'GROUPING', 'STDEVP',
63  'GROUPING_ID', 'SUM', 'MAX', 'VAR', 'VARP'
64  ),
65  $aggrFunctionsORACLE = array('AVG', 'COLLECT', 'CORR', 'CORR_S', 'CORR_K',
66  'COUNT', 'COVAR_POP', 'COVAR_SAMP', 'CUME_DIST', 'DENSE_RANK', 'FIRST',
67  'GROUP_ID', 'GROUPING', 'GROUPING_ID', 'LAST', 'MAX', 'MEDIAN', 'MIN',
68  'PERCENTILE_CONT', 'PERCENTILE_DISC', 'PERCENT_RANK', 'RANK',
69  'REGR_SLOPE', 'REGR_INTERCEPT', 'REGR_COUNT', 'REGR_R2', 'REGR_AVGX',
70  'REGR_AVGY', 'REGR_SXX', 'REGR_SYY', 'REGR_SXY', 'STATS_BINOMIAL_TEST',
71  'STATS_CROSSTAB', 'STATS_F_TEST', 'STATS_KS_TEST', 'STATS_MODE',
72  'STATS_MW_TEST', 'STATS_ONE_WAY_ANOVA', 'STATS_T_TEST_ONE',
73  'STATS_T_TEST_PAIRED', 'STATS_T_TEST_INDEP', 'STATS_T_TEST_INDEPU',
74  'STATS_WSR_TEST', 'STDDEV', 'STDDEV_POP', 'STDDEV_SAMP', 'SUM',
75  'VAR_POP', 'VAR_SAMP', 'VARIANCE'
76  ),
78 
79  /**
80  * All fields in exression should be placed as %s (or as another placeholder for sprintf),
81  * and the real field names being carrying in $buildFrom array (= args for sprintf)
82  *
83  * @param string $name
84  * @param string $expression
85  * @param array|string|null $buildFrom
86  * @param array $parameters deprecated, use configure* and add* instead
87  *
88  * @throws SystemException
89  */
90  public function __construct($name, $expression, $buildFrom = null, $parameters = array())
91  {
92  if (!isset($parameters['data_type']))
93  {
94  $parameters['data_type'] = 'string'; // deprecated
95 
96  $this->valueType = StringField::class;
97  }
98 
99  parent::__construct($name, $parameters);
100 
101  $this->expression = $expression;
102 
103  if (!is_array($buildFrom) && $buildFrom !== null)
104  {
105  $buildFrom = array($buildFrom);
106  }
107  elseif ($buildFrom === null)
108  {
109  $buildFrom = array();
110  }
111 
112  $this->buildFrom = $buildFrom;
113  }
114 
115  public function __call($name, $arguments)
116  {
117  return call_user_func_array(array($this->valueField, $name), $arguments);
118  }
119 
120  /**
121  * @return mixed
122  */
123  public function getTypeMask()
124  {
126  }
127 
128  /**
129  * @param $class
130  *
131  * @return $this
132  */
133  public function configureValueType($class)
134  {
135  $this->valueType = $class;
136  return $this;
137  }
138 
139  /**
140  * @param Entity $entity
141  *
142  * @throws SystemException
143  * @throws \Bitrix\Main\ArgumentException
144  */
145  public function setEntity(Entity $entity)
146  {
147  parent::setEntity($entity);
148 
149  if ($this->valueType !== null)
150  {
151  /** @var ScalarField $valueField */
152  $valueField = new $this->valueType($this->name);
153  $this->valueField = $this->entity->initializeField($this->name, $valueField);
154  }
155  else
156  {
157  // deprecated - old format with parameters and data_type
158  $parameters = $this->initialParameters;
159 
160  unset($parameters['expression']);
161  $this->valueField = $this->entity->initializeField($this->name, $parameters);
162  $this->valueType = get_class($this->valueField);
163  }
164 
165  if (!($this->valueField instanceof ScalarField))
166  {
167  throw new SystemException('expression field can only be a scalar type.');
168  }
169  }
170 
171  public function getExpression()
172  {
173  return $this->expression;
174  }
175 
176  /**
177  * @return mixed|string
178  * @throws SystemException
179  */
180  public function getFullExpression()
181  {
182  if (!isset($this->fullExpression))
183  {
184  $SQLBuildFrom = array();
185 
186  foreach ($this->getBuildFromChains() as $chain)
187  {
188  if ($chain->getLastElement()->getValue() instanceof ExpressionField)
189  {
190  $SQLBuildFrom[] = $chain->getLastElement()->getValue()->getFullExpression();
191  }
192  else
193  {
194  $SQLBuildFrom[] = '%s';
195  }
196  }
197 
198  $this->fullExpression = call_user_func_array('sprintf', array_merge(array($this->expression), $SQLBuildFrom));
199  }
200 
201  return $this->fullExpression;
202  }
203 
204  /**
205  * @return bool
206  * @throws SystemException
207  */
208  public function isAggregated()
209  {
210  if (!isset($this->isAggregated))
211  {
212  $this->isAggregated = (bool) self::checkAggregation($this->getFullExpression());
213  }
214 
215  return $this->isAggregated;
216  }
217 
218  /**
219  * @return bool
220  * @throws SystemException
221  */
222  public function hasSubquery()
223  {
224  if (!isset($this->hasSubquery))
225  {
226  $this->hasSubquery = (bool) self::checkSubquery($this->getFullExpression());
227  }
228 
229  return $this->hasSubquery;
230  }
231 
232  public function isConstant()
233  {
234  return empty($this->buildFrom);
235  }
236 
237  /**
238  * @return array|\Bitrix\Main\ORM\Query\Chain[]
239  * @throws SystemException
240  * @throws \Bitrix\Main\ArgumentException
241  */
242  public function getBuildFromChains()
243  {
244  if (is_null($this->buildFromChains))
245  {
246  $this->buildFromChains = array();
247 
248  foreach ($this->buildFrom as $elem)
249  {
250  // validate if build from scalar or expression
251  $chain = Chain::getChainByDefinition($this->entity, $elem);
252  $field = $chain->getLastElement()->getValue();
253 
254  if ($field instanceof ScalarField || $field instanceof ExpressionField)
255  {
256  $this->buildFromChains[] = $chain;
257  }
258  else
259  {
260  throw new SystemException(sprintf(
261  'Expected ScalarField or ExpressionField in `%s` build_from, but `%s` was given.',
262  $this->name, is_object($field) ? get_class($field).':'.$field->getName() : gettype($field)
263  ));
264  }
265  }
266  }
267 
268  return $this->buildFromChains;
269  }
270 
271  public static function checkAggregation($expression)
272  {
273  if (empty(self::$aggrFunctions))
274  {
275  self::$aggrFunctions = array_unique(array_merge(
276  self::$aggrFunctionsMYSQL, self::$aggrFunctionsMSSQL, self::$aggrFunctionsORACLE
277  ));
278  }
279 
280  // should remove subqueries from expression here: EXISTS(..(..)..), (SELECT ..(..)..)
281  $expression = static::removeSubqueries($expression);
282 
283  // then check for aggr functions
284  preg_match_all('/(?:^|[^a-z0-9_])('.join('|', self::$aggrFunctions).')[\s\(]+/i', $expression, $matches);
285 
286  return isset($matches[1]) ? $matches[1] : null;
287  }
288 
289  public static function checkSubquery($expression)
290  {
291  return (preg_match('/(?:^|[^a-zA-Z0-9_])EXISTS\s*\(/i', $expression) || preg_match('/(?:^|[^a-zA_Z0-9_])\(\s*SELECT/i', $expression));
292  }
293 
294  public static function removeSubqueries($expression)
295  {
296  // remove double slashes
297  $expression = str_replace('\\\\\\\\', '', $expression);
298 
299  // remove strings
300  $expression = static::removeStrings('"', $expression);
301  $expression = static::removeStrings("'", $expression);
302 
303  // remove subqueries' bodies
304  $clear = static::removeSubqueryBody($expression);
305 
306  while ($clear !== $expression)
307  {
308  $expression = $clear;
309  $clear = static::removeSubqueryBody($expression);
310  }
311 
312  return $clear;
313  }
314 
315  protected static function removeStrings($quote, $expression)
316  {
317  // remove escaped quotes
318  $expression = str_replace('\\' . $quote, '', $expression);
319 
320  // remove quoted strings
321  $expression = preg_replace('/' . $quote . '.*?' . $quote . '/', '', $expression);
322 
323  return $expression;
324  }
325 
326  protected static function removeSubqueryBody($query)
327  {
328  $subqPattern = '\(\s*SELECT\s+';
329 
330  $matches = null;
331  preg_match('/' . $subqPattern . '/i', $query, $matches);
332 
333  if (!empty($matches))
334  {
335  $substring = $matches[0];
336 
337  $subqPosition = strpos($query, $substring);
338  $subqStartPosition = $subqPosition + strlen($substring);
339 
340  $bracketsCount = 1;
341  $currentPosition = $subqStartPosition;
342 
343  // until initial bracket is closed
344  while ($bracketsCount > 0)
345  {
346  $symbol = substr($query, $currentPosition, 1);
347 
348  if ($symbol == '')
349  {
350  // end of string
351  break;
352  }
353 
354  if ($symbol == '(')
355  {
356  $bracketsCount++;
357  }
358  elseif ($symbol == ')')
359  {
360  $bracketsCount--;
361  }
362 
363  $currentPosition++;
364  }
365 
366  $query = substr($query, 0, $subqPosition) . substr($query, $currentPosition);
367  }
368 
369  return $query;
370  }
371 
372  /**
373  * @deprecated
374  * @return null|string
375  */
376  public function getDataType()
377  {
378  return $this->valueField->getDataType();
379  }
380 
381  /**
382  * @return string
383  */
384  public function getValueType()
385  {
386  return $this->valueType;
387  }
388 
389  /**
390  * @return ScalarField
391  */
392  public function getValueField()
393  {
394  return $this->valueField;
395  }
396 
397  public function __clone()
398  {
399  $this->buildFromChains = null;
400  $this->fullExpression = null;
401  }
402 
403  /**
404  * @param mixed $value
405  *
406  * @return mixed
407  */
408  public function cast($value)
409  {
411  return $valueField->cast($value);
412  }
413 
414  /**
415  * @param mixed $value
416  *
417  * @return mixed
418  */
419  public function convertValueFromDb($value)
420  {
422  return $valueField->convertValueFromDb($value);
423  }
424 
425  /**
426  * @param mixed $value
427  *
428  * @return string
429  */
430  public function convertValueToDb($value)
431  {
432  /** @var IStorable $valueField */
434  return $valueField->convertValueToDb($value);
435  }
436 
437  public function validateValue($value, $primary, $row, Result $result)
438  {
439  throw new NotImplementedException;
440  }
441 }
442 
443 
Bitrix\Main\ORM\Fields\ExpressionField\checkAggregation
static checkAggregation($expression)
Definition: expressionfield.php:271
Bitrix\Main\ORM\Fields\Field\$name
$name
Definition: main/lib/orm/fields/field.php:27
Bitrix\Main\ORM\Fields\ExpressionField\getValueField
getValueField()
Definition: expressionfield.php:392
Bitrix\Main\ORM\Fields\ExpressionField\convertValueToDb
convertValueToDb($value)
Definition: expressionfield.php:430
Bitrix\Main\ORM\Fields\ExpressionField\getExpression
getExpression()
Definition: expressionfield.php:171
Bitrix\Main\ORM\Fields
Definition: arrayfield.php:9
Bitrix\Main\ORM\Query\Chain\getChainByDefinition
static getChainByDefinition(Entity $init_entity, $definition)
Definition: main/lib/orm/query/chain.php:208
Bitrix\Main\ORM\Fields\ExpressionField\cast
cast($value)
Definition: expressionfield.php:408
Bitrix\Main\ORM\Fields\ExpressionField\__call
__call($name, $arguments)
Definition: expressionfield.php:115
Bitrix\Main\ORM\Fields\IReadable
Definition: ireadable.php:15
Bitrix\Main\ORM\Fields\ExpressionField\$aggrFunctions
static $aggrFunctions
Definition: expressionfield.php:77
Bitrix\Main\ORM\Fields\ExpressionField\removeSubqueries
static removeSubqueries($expression)
Definition: expressionfield.php:294
Bitrix\Main\ORM\Fields\ExpressionField\getFullExpression
getFullExpression()
Definition: expressionfield.php:180
Bitrix\Main\ORM\Fields\ExpressionField\checkSubquery
static checkSubquery($expression)
Definition: expressionfield.php:289
Bitrix\Main\ORM\Fields\ExpressionField\getValueType
getValueType()
Definition: expressionfield.php:384
Bitrix\Main\ORM\Fields\ExpressionField\__construct
__construct($name, $expression, $buildFrom=null, $parameters=array())
All fields in exression should be placed as s (or as another placeholder for sprintf),...
Definition: expressionfield.php:90
Bitrix\Main\ORM\Fields\ExpressionField\validateValue
validateValue($value, $primary, $row, Result $result)
Definition: expressionfield.php:437
Bitrix\Main\ORM\Fields\ExpressionField\setEntity
setEntity(Entity $entity)
Definition: expressionfield.php:145
Bitrix\Main\ORM\Fields\ScalarField
Definition: scalarfield.php:18
Bitrix\Main\ORM\Fields\ExpressionField
Definition: expressionfield.php:23
Bitrix\Main\ORM\Fields\ExpressionField\isAggregated
isAggregated()
Definition: expressionfield.php:208
Bitrix\Main\ORM\Fields\ExpressionField\__clone
__clone()
Definition: expressionfield.php:397
Bitrix\Main\ORM\Entity
Base entity.
Definition: main/lib/orm/entity.php:25
Bitrix\Main\ORM\Fields\ExpressionField\$valueType
$valueType
Definition: expressionfield.php:37
Bitrix\Main\ORM\Fields\ExpressionField\hasSubquery
hasSubquery()
Definition: expressionfield.php:222
Bitrix\Main\ORM\Fields\ExpressionField\getTypeMask
getTypeMask()
Definition: expressionfield.php:123
Bitrix\Main\ORM\Fields\ExpressionField\$buildFrom
$buildFrom
Definition: expressionfield.php:47
Bitrix\Main\ORM\Fields\FieldTypeMask\EXPRESSION
const EXPRESSION
Definition: fieldtypemask.php:18
Bitrix\Main\ORM\Fields\ExpressionField\$buildFromChains
$buildFromChains
Definition: expressionfield.php:50
Bitrix\Main\ORM\Fields\ExpressionField\getBuildFromChains
getBuildFromChains()
Definition: expressionfield.php:242
Bitrix\Main\ORM\Fields\ExpressionField\$expression
$expression
Definition: expressionfield.php:28
Bitrix\Main\ORM\Fields\ExpressionField\removeSubqueryBody
static removeSubqueryBody($query)
Definition: expressionfield.php:326
Bitrix\Main\SystemException
Base class for fatal exceptions.
Definition: main/lib/exception.php:7
Bitrix\Main\ORM\Query\Chain
Definition: main/lib/orm/query/chain.php:13
Bitrix\Main\ORM\Fields\ExpressionField\$fullExpression
$fullExpression
Definition: expressionfield.php:34
Bitrix\Main\ORM\Fields\ExpressionField\$aggrFunctionsMSSQL
static $aggrFunctionsMSSQL
Definition: expressionfield.php:61
Bitrix\Sender\Connector\__construct
__construct(Base $connector)
Constructor.
Definition: resultview.php:40
Bitrix\Main\ORM\Fields\Field
Definition: main/lib/orm/fields/field.php:24
Bitrix\Main\ORM\Fields\ExpressionField\$isAggregated
$isAggregated
Definition: expressionfield.php:52
Bitrix\Main\ORM\Fields\ExpressionField\isConstant
isConstant()
Definition: expressionfield.php:232
Bitrix\Main\ORM\Fields\FieldTypeMask
Definition: fieldtypemask.php:15
Bitrix\Main\ORM\Fields\ExpressionField\getDataType
getDataType()
Definition: expressionfield.php:376
Bitrix\Main\ORM\Fields\ExpressionField\configureValueType
configureValueType($class)
Definition: expressionfield.php:133
Bitrix\Main\ORM\Fields\ExpressionField\$aggrFunctionsMYSQL
static $aggrFunctionsMYSQL
Definition: expressionfield.php:57
Bitrix\Main\ORM\Fields\ExpressionField\$valueField
$valueField
Definition: expressionfield.php:42
Bitrix\Main\ORM\Fields\Field\$entity
$entity
Definition: main/lib/orm/fields/field.php:76
Bitrix\Main\ORM\Fields\ExpressionField\$aggrFunctionsORACLE
static $aggrFunctionsORACLE
Definition: expressionfield.php:65
Bitrix\Main\ORM\Fields\ExpressionField\$hasSubquery
$hasSubquery
Definition: expressionfield.php:54
Bitrix\Main\ORM\Data\Result
Definition: main/lib/orm/data/result.php:15
Bitrix\Main\ORM\Fields\ExpressionField\convertValueFromDb
convertValueFromDb($value)
Definition: expressionfield.php:419
Bitrix\Main\ORM\Fields\Field\$initialParameters
$initialParameters
Definition: main/lib/orm/fields/field.php:33
Bitrix\Main\NotImplementedException
Exception is thrown when operation is not implemented but should be.
Definition: main/lib/exception.php:146
Bitrix\Main\ORM\Fields\ExpressionField\removeStrings
static removeStrings($quote, $expression)
Definition: expressionfield.php:315