├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── src
├── Adapters
│ └── D7Adapter.php
├── Debug
│ ├── IlluminateQueryDebugger.php
│ └── debug_info.php
├── Exceptions
│ └── ExceptionFromBitrix.php
├── Helpers.php
├── Models
│ ├── ArrayableModel.php
│ ├── BaseBitrixModel.php
│ ├── BitrixModel.php
│ ├── D7Model.php
│ ├── ElementModel.php
│ ├── EloquentModel.php
│ ├── SectionModel.php
│ ├── Traits
│ │ ├── DeactivationTrait.php
│ │ ├── HidesAttributes.php
│ │ └── ModelEventsTrait.php
│ └── UserModel.php
├── Queries
│ ├── BaseQuery.php
│ ├── BaseRelationQuery.php
│ ├── D7Query.php
│ ├── ElementQuery.php
│ ├── OldCoreQuery.php
│ ├── SectionQuery.php
│ └── UserQuery.php
└── ServiceProvider.php
└── tests
├── D7ModelTest.php
├── D7QueryTest.php
├── ElementModelTest.php
├── ElementQueryTest.php
├── RelationTest.php
├── SectionModelTest.php
├── SectionQueryTest.php
├── Stubs
├── BxUserWithAuth.php
├── BxUserWithoutAuth.php
├── TestD7Element.php
├── TestD7Element2.php
├── TestD7ElementClass.php
├── TestD7ElementClass2.php
├── TestD7ResultObject.php
├── TestElement.php
├── TestElement2.php
├── TestSection.php
└── TestUser.php
├── TestCase.php
├── UserModelTest.php
└── UserQueryTest.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
5 | /.idea
6 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | filter:
2 | paths:
3 | - 'src/*'
4 | excluded_paths:
5 | - 'vendor/*'
6 | - 'tests/*'
7 | tools:
8 | php_cs_fixer:
9 | config: { level: psr2 }
10 | checks:
11 | php:
12 | code_rating: true
13 | duplication: true
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.6
5 | - 7.0
6 | - 7.1
7 | - 7.2
8 | - 7.3
9 | - 7.4
10 | - 8.0
11 |
12 | before_script:
13 | - composer self-update
14 | - composer install --prefer-source --no-interaction
15 |
16 | script:
17 | - vendor/bin/phpunit
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Nekrasov Ilya
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ge1i0n/bitrix-models",
3 | "license": "MIT",
4 | "keywords": [
5 | "bitrix",
6 | "models"
7 | ],
8 | "authors": [
9 | {
10 | "name": "Nekrasov Ilya",
11 | "email": "nekrasov.ilya90@gmail.com"
12 | }
13 | ],
14 | "homepage": "https://github.com/ge1i0n/bitrix-models",
15 | "require": {
16 | "php": "^8.2",
17 | "illuminate/support": "^11.0",
18 | "illuminate/pagination": "^11.0"
19 | },
20 | "suggest": {
21 | "arrilot/bitrix-blade": "To render pagination views using ->links()",
22 | "illuminate/database": "5.*|6.*|7.*|8.*|9.*|10.*|11.* to use Eloquent models",
23 | "illuminate/events": "5.*|6.*|7.*|8.*|9.*|10.*|11.* to use events in Eloquent models"
24 | },
25 | "require-dev": {
26 | "phpunit/phpunit": "^5.0",
27 | "mockery/mockery": "~1"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "Arrilot\\BitrixModels\\": "src/"
32 | }
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "Arrilot\\Tests\\BitrixModels\\": "tests/"
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | tests
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Adapters/D7Adapter.php:
--------------------------------------------------------------------------------
1 | className = $className;
31 | }
32 |
33 | /**
34 | * Getter for class name.
35 | *
36 | * @return string
37 | */
38 | public function getClassName()
39 | {
40 | return $this->className;
41 | }
42 |
43 | /**
44 | * Handle dynamic method calls into a static calls on bitrix entity class.
45 | *
46 | * @param string $method
47 | * @param array $parameters
48 | * @return mixed
49 | */
50 | public function __call($method, $parameters)
51 | {
52 | $className = $this->className;
53 |
54 | return $className::$method(...$parameters);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Debug/IlluminateQueryDebugger.php:
--------------------------------------------------------------------------------
1 | ShowSqlStat && ($USER->CanDoOperation('edit_php') || $_SESSION["SHOW_SQL_STAT"] == "Y"));
16 | if ($bShowStat && class_exists(Manager::class) && Manager::logging()) {
17 | require_once(__DIR__ . '/debug_info.php');
18 | }
19 | }
20 | }
21 |
22 | public static function interpolateQuery($query, $params)
23 | {
24 | $keys = array();
25 |
26 | # build a regular expression for each parameter
27 | foreach ($params as $key => $value) {
28 | $keys[] = is_string($key) ? '/:' . $key . '/' : '/[?]/';
29 | $params[$key] = "'" . $value . "'";
30 | }
31 |
32 | return preg_replace($keys, $params, $query, 1, $count);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Debug/debug_info.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | use Arrilot\BitrixModels\Debug\IlluminateQueryDebugger;
4 | use Illuminate\Database\Capsule\Manager;
5 |
6 | if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();
7 |
8 | if (!$bShowStat) {
9 | return;
10 | }
11 |
12 | $totalQueryCount = 0;
13 | $totalQueryTime = 0.0;
14 |
15 | $queryLog = Manager::getQueryLog();
16 | foreach ($queryLog as $loggedQuery) {
17 | $totalQueryCount++;
18 | $totalQueryTime += $loggedQuery['time'] / 1000;
19 | }
20 |
21 | echo '
';
22 | echo 'Статистика SQL запросов illuminate/database
';
23 | echo '
' . 'Всего SQL запросов: ' . " " . intval($totalQueryCount) . "
";
24 | echo "Время исполнения запросов: " . round($totalQueryTime, 4) . " сек.
";
25 | echo '
';
26 |
27 | //CJSPopup
28 | require_once($_SERVER["DOCUMENT_ROOT"] . BX_ROOT . "/modules/main/interface/admin_lib.php");
29 | ?>
30 |
33 |
34 | $obJSPopup = new CJSPopupOnPage('', array());
35 | $obJSPopup->jsPopup = 'BX_DEBUG_INFO_ILLUMINATE';
36 | $obJSPopup->StartDescription('bx-core-debug-info');
37 | ?>
38 | Всего запросов: = $totalQueryCount ?>, время: = round($totalQueryTime, 4) ?> сек.
39 | Поиск:
40 |
41 | $obJSPopup->StartContent(['buffer' => true]);
42 | if (count($queryLog) > 0) {
43 | ?>
44 | $arQueries = [];
45 | foreach ($queryLog as $j => $arQueryDebug) {
46 | $strSql = $arQueryDebug["query"];
47 | $arQueries[$strSql]["COUNT"]++;
48 | $arQueries[$strSql]["CALLS"][] = [
49 | "QUERY" => $strSql,
50 | "BINDINGS" => $arQueryDebug['bindings'],
51 | "TIME" => $arQueryDebug["time"] / 1000,
52 | ];
53 | }
54 | ?>
55 | $j = 1;
56 | foreach ($arQueries as $strSql => $query) {
57 | ?>
58 | = $j ?> |
59 | = htmlspecialcharsbx(substr($strSql, 0, 100)) . "..." ?> (= $query["COUNT"] ?>) |
60 |
61 | $t = 0.0;
62 | foreach ($query["CALLS"] as $call) {
63 | $t += $call["TIME"];
64 | }
65 | echo number_format($t / $query["COUNT"], 5);
66 | ?> |
67 |
68 | $j++;
69 | }
70 | ?>
71 |
#DIVIDER#
72 |
73 | $j = 1;
74 | foreach ($arQueries as $strSql => $query) {
75 | ?>
76 | Запрос № = $j ?>:
77 |
78 |
79 | $strSql = preg_replace("/[\\n\\r\\t\\s ]+/", " ", $strSql);
80 | $strSql = preg_replace("/^ +/", "", $strSql);
81 | $strSql = preg_replace("/ (INNER JOIN|OUTER JOIN|LEFT JOIN|SET|LIMIT) /i", "\n\\1 ", $strSql);
82 | $strSql = preg_replace("/(INSERT INTO [A-Z_0-1]+?)\\s/i", "\\1\n", $strSql);
83 | $strSql = preg_replace("/(INSERT INTO [A-Z_0-1]+?)([(])/i", "\\1\n\\2", $strSql);
84 | $strSql = preg_replace("/([\\s)])(VALUES)([\\s(])/i", "\\1\n\\2\n\\3", $strSql);
85 | $strSql = preg_replace("/ (FROM|WHERE|ORDER BY|GROUP BY|HAVING) /i", "\n\\1\n", $strSql);
86 | echo str_replace(["\n"], ["
"], htmlspecialcharsbx($strSql));
87 | ?>
88 |
89 |
90 | $k = 1;
91 | foreach ($query["CALLS"] as $call) {
92 | ?>
93 |
94 | Экземпляр № = $k ?>:
95 | = htmlspecialcharsbx(IlluminateQueryDebugger::interpolateQuery($call['QUERY'], $call['BINDINGS'])) ?>
96 |
97 | Время выполнения: = round($call["TIME"], 5) ?> сек.
98 |
99 | $k++;
100 | }
101 | ?>
102 |
103 |
104 | $j++;
105 | };
106 | ?>
107 |
108 |
109 | }
110 | $obJSPopup->StartButtons();
111 | $obJSPopup->ShowStandardButtons(array('close'));
112 |
--------------------------------------------------------------------------------
/src/Exceptions/ExceptionFromBitrix.php:
--------------------------------------------------------------------------------
1 | $primaryModel) {
35 | if ($multiple && is_array($keys = $primaryModel[$primaryKey])) {
36 | $value = [];
37 | foreach ($keys as $key) {
38 | $key = static::normalizeModelKey($key);
39 | if (isset($buckets[$key])) {
40 | $value = array_merge($value, $buckets[$key]);
41 | }
42 | }
43 | } else {
44 | $key = static::normalizeModelKey($primaryModel[$primaryKey]);
45 | $value = isset($buckets[$key]) ? $buckets[$key] : ($multiple ? [] : null);
46 | }
47 |
48 | $primaryModel->populateRelation($relationName, is_array($value) ? (new Collection($value))->keyBy(function ($item) {
49 | return $item->id;
50 | }) : $value);
51 | }
52 | }
53 |
54 | /**
55 | * Сгруппировать найденные модели
56 | * @param array $models
57 | * @param string $linkKey
58 | * @param bool $multiple
59 | * @return array
60 | */
61 | protected static function buildBuckets($models, $linkKey, $multiple)
62 | {
63 | $buckets = [];
64 |
65 | foreach ($models as $model) {
66 | $key = $model[$linkKey];
67 | if (is_scalar($key)) {
68 | $buckets[$key][] = $model;
69 | } elseif (is_array($key)) {
70 | foreach ($key as $k) {
71 | $k = static::normalizeModelKey($k);
72 | $buckets[$k][] = $model;
73 | }
74 | } else {
75 | $key = static::normalizeModelKey($key);
76 | $buckets[$key][] = $model;
77 | }
78 | }
79 |
80 | if (!$multiple) {
81 | foreach ($buckets as $i => $bucket) {
82 | $buckets[$i] = reset($bucket);
83 | }
84 | }
85 |
86 | return $buckets;
87 | }
88 |
89 | /**
90 | * @param mixed $value raw key value.
91 | * @return string normalized key value.
92 | */
93 | protected static function normalizeModelKey($value)
94 | {
95 | if (is_object($value) && method_exists($value, '__toString')) {
96 | // ensure matching to special objects, which are convertable to string, for cross-DBMS relations, for example: `|MongoId`
97 | $value = $value->__toString();
98 | }
99 |
100 | return $value;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Models/ArrayableModel.php:
--------------------------------------------------------------------------------
1 | fields[] = $value;
71 | } else {
72 | $this->fields[$offset] = $value;
73 | }
74 | }
75 |
76 | /**
77 | * Exists method for ArrayIterator.
78 | *
79 | * @param $offset
80 | *
81 | * @return bool
82 | */
83 | public function offsetExists($offset)
84 | {
85 | return $this->getAccessor($offset) || $this->getAccessorForLanguageField($offset)
86 | ? true : isset($this->fields[$offset]);
87 | }
88 |
89 | /**
90 | * Unset method for ArrayIterator.
91 | *
92 | * @param $offset
93 | *
94 | * @return void
95 | */
96 | public function offsetUnset($offset)
97 | {
98 | unset($this->fields[$offset]);
99 | }
100 |
101 | /**
102 | * Get method for ArrayIterator.
103 | *
104 | * @param $offset
105 | *
106 | * @return mixed
107 | */
108 | public function offsetGet($offset)
109 | {
110 | $fieldValue = isset($this->fields[$offset]) ? $this->fields[$offset] : null;
111 | $accessor = $this->getAccessor($offset);
112 | if ($accessor) {
113 | return $this->$accessor($fieldValue);
114 | }
115 |
116 | $accessorForLanguageField = $this->getAccessorForLanguageField($offset);
117 | if ($accessorForLanguageField) {
118 | return $this->$accessorForLanguageField($offset);
119 | }
120 |
121 | return $fieldValue;
122 | }
123 |
124 | /**
125 | * Get an iterator for fields.
126 | *
127 | * @return ArrayIterator
128 | */
129 | public function getIterator()
130 | {
131 | return new ArrayIterator($this->fields);
132 | }
133 |
134 | /**
135 | * Get accessor method name if it exists.
136 | *
137 | * @param string $field
138 | *
139 | * @return string|false
140 | */
141 | private function getAccessor($field)
142 | {
143 | $method = 'get' . Str::camel($field) . 'Attribute';
144 |
145 | return method_exists($this, $method) ? $method : false;
146 | }
147 |
148 | /**
149 | * Get accessor for language field method name if it exists.
150 | *
151 | * @param string $field
152 | *
153 | * @return string|false
154 | */
155 | private function getAccessorForLanguageField($field)
156 | {
157 | $method = 'getValueFromLanguageField';
158 |
159 | return in_array($field, $this->languageAccessors) && method_exists($this, $method) ? $method : false;
160 | }
161 |
162 | /**
163 | * Add value to append.
164 | *
165 | * @param array|string $attributes
166 | * @return $this
167 | */
168 | public function append($attributes)
169 | {
170 | $this->appends = array_unique(
171 | array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes)
172 | );
173 |
174 | return $this;
175 | }
176 |
177 | /**
178 | * Setter for appends.
179 | *
180 | * @param array $appends
181 | * @return $this
182 | */
183 | public function setAppends(array $appends)
184 | {
185 | $this->appends = $appends;
186 |
187 | return $this;
188 | }
189 |
190 | /**
191 | * Cast model to array.
192 | *
193 | * @return array
194 | */
195 | public function toArray()
196 | {
197 | $array = $this->fields;
198 |
199 | foreach ($this->appends as $accessor) {
200 | if (isset($this[$accessor])) {
201 | $array[$accessor] = $this[$accessor];
202 | }
203 | }
204 |
205 | foreach ($this->related as $key => $value) {
206 | if (is_object($value) && method_exists($value, 'toArray')) {
207 | $array[$key] = $value->toArray();
208 | } elseif (is_null($value) || $value === false) {
209 | $array[$key] = $value;
210 | }
211 | }
212 |
213 | if (count($this->getVisible()) > 0) {
214 | $array = array_intersect_key($array, array_flip($this->getVisible()));
215 | }
216 |
217 | if (count($this->getHidden()) > 0) {
218 | $array = array_diff_key($array, array_flip($this->getHidden()));
219 | }
220 |
221 | return $array;
222 | }
223 |
224 | /**
225 | * Convert model to json.
226 | *
227 | * @param int $options
228 | *
229 | * @return string
230 | */
231 | public function toJson($options = 0)
232 | {
233 | return json_encode($this->toArray(), $options);
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/src/Models/BaseBitrixModel.php:
--------------------------------------------------------------------------------
1 | load();
83 |
84 | return $this->fields;
85 | }
86 |
87 | /**
88 | * Load model fields from database if they are not loaded yet.
89 | *
90 | * @return $this
91 | */
92 | public function load()
93 | {
94 | if (!$this->fieldsAreFetched) {
95 | $this->refresh();
96 | }
97 |
98 | return $this;
99 | }
100 |
101 | /**
102 | * Get model fields from cache or database.
103 | *
104 | * @return array
105 | */
106 | public function getFields()
107 | {
108 | if ($this->fieldsAreFetched) {
109 | return $this->fields;
110 | }
111 |
112 | return $this->refreshFields();
113 | }
114 |
115 | /**
116 | * Refresh model from database and place data to $this->fields.
117 | *
118 | * @return array
119 | */
120 | public function refresh()
121 | {
122 | return $this->refreshFields();
123 | }
124 |
125 | /**
126 | * Refresh model fields and save them to a class field.
127 | *
128 | * @return array
129 | */
130 | public function refreshFields()
131 | {
132 | if ($this->id === null) {
133 | $this->original = [];
134 | return $this->fields = [];
135 | }
136 |
137 | $this->fields = static::query()->getById($this->id)->fields;
138 | $this->original = $this->fields;
139 |
140 | $this->fieldsAreFetched = true;
141 |
142 | return $this->fields;
143 | }
144 |
145 | /**
146 | * Fill model fields if they are already known.
147 | * Saves DB queries.
148 | *
149 | * @param array $fields
150 | *
151 | * @return void
152 | */
153 | public function fill($fields)
154 | {
155 | if (!is_array($fields)) {
156 | return;
157 | }
158 |
159 | if (isset($fields['ID'])) {
160 | $this->id = $fields['ID'];
161 | }
162 |
163 | $this->fields = $fields;
164 |
165 | $this->fieldsAreFetched = true;
166 |
167 | if (method_exists($this, 'afterFill')) {
168 | $this->afterFill();
169 | }
170 |
171 | $this->original = $this->fields;
172 | }
173 |
174 | /**
175 | * Set current model id.
176 | *
177 | * @param $id
178 | */
179 | protected function setId($id)
180 | {
181 | $this->id = $id;
182 | $this->fields['ID'] = $id;
183 | }
184 |
185 | /**
186 | * Create new item in database.
187 | *
188 | * @param $fields
189 | *
190 | * @throws LogicException
191 | *
192 | * @return static|bool
193 | */
194 | public static function create($fields)
195 | {
196 | return static::internalCreate($fields);
197 | }
198 |
199 | /**
200 | * Get count of items that match $filter.
201 | *
202 | * @param array $filter
203 | *
204 | * @return int
205 | */
206 | public static function count(array $filter = [])
207 | {
208 | return static::query()->filter($filter)->count();
209 | }
210 |
211 | /**
212 | * Get item by its id.
213 | *
214 | * @param int $id
215 | *
216 | * @return static|bool
217 | */
218 | public static function find($id)
219 | {
220 | return static::query()->getById($id);
221 | }
222 |
223 | /**
224 | * Update model.
225 | *
226 | * @param array $fields
227 | *
228 | * @return bool
229 | */
230 | public function update(array $fields = [])
231 | {
232 | $keys = [];
233 | foreach ($fields as $key => $value) {
234 | Arr::set($this->fields, $key, $value);
235 | $keys[] = $key;
236 | }
237 |
238 | return $this->save($keys);
239 | }
240 |
241 | /**
242 | * Create an array of fields that will be saved to database.
243 | *
244 | * @param $selectedFields
245 | *
246 | * @return array|null
247 | */
248 | protected function normalizeFieldsForSave($selectedFields)
249 | {
250 | $fields = [];
251 | if ($this->fields === null) {
252 | return [];
253 | }
254 |
255 | foreach ($this->fields as $field => $value) {
256 | if (!$this->fieldShouldNotBeSaved($field, $value, $selectedFields)) {
257 | $fields[$field] = $value;
258 | }
259 | }
260 |
261 | return $fields ?: null;
262 | }
263 |
264 | /**
265 | * Instantiate a query object for the model.
266 | *
267 | * @throws LogicException
268 | *
269 | * @return BaseQuery
270 | */
271 | public static function query()
272 | {
273 | throw new LogicException('public static function query() is not implemented');
274 | }
275 |
276 | /**
277 | * Handle dynamic static method calls into a new query.
278 | *
279 | * @param string $method
280 | * @param array $parameters
281 | * @return mixed
282 | */
283 | public static function __callStatic($method, $parameters)
284 | {
285 | return static::query()->$method(...$parameters);
286 | }
287 |
288 | /**
289 | * Returns the value of a model property.
290 | *
291 | * This method will check in the following order and act accordingly:
292 | *
293 | * - a property defined by a getter: return the getter result
294 | *
295 | * Do not call this method directly as it is a PHP magic method that
296 | * will be implicitly called when executing `$value = $component->property;`.
297 | * @param string $name the property name
298 | * @return mixed the property value
299 | * @throws \Exception if the property is not defined
300 | * @see __set()
301 | */
302 | public function __get($name)
303 | {
304 | // Если уже сохранен такой релейшн, то возьмем его
305 | if (isset($this->related[$name]) || array_key_exists($name, $this->related)) {
306 | return $this->related[$name];
307 | }
308 |
309 | // Если нет сохраненных данных, ищем подходящий геттер
310 | $getter = $name;
311 | if (method_exists($this, $getter)) {
312 | // read property, e.g. getName()
313 | $value = $this->$getter();
314 |
315 | // Если геттер вернул запрос, значит $name - релейшен. Нужно выполнить запрос и сохранить во внутренний массив
316 | if ($value instanceof BaseQuery) {
317 | $this->related[$name] = $value->findFor();
318 | return $this->related[$name];
319 | }
320 | }
321 |
322 | throw new \Exception('Getting unknown property: ' . get_class($this) . '::' . $name);
323 | }
324 |
325 | /**
326 | * Получить запрос для релейшена по имени
327 | * @param string $name - название релейшена, например `orders` для релейшена, определенного через метод getOrders()
328 | * @param bool $throwException - кидать ли исключение в случае ошибки
329 | * @return BaseQuery - запрос для подгрузки релейшена
330 | * @throws \InvalidArgumentException
331 | */
332 | public function getRelation($name, $throwException = true)
333 | {
334 | $getter = $name;
335 | try {
336 | $relation = $this->$getter();
337 | } catch (\BadMethodCallException $e) {
338 | if ($throwException) {
339 | throw new \InvalidArgumentException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
340 | }
341 |
342 | return null;
343 | }
344 |
345 | if (!$relation instanceof BaseQuery) {
346 | if ($throwException) {
347 | throw new \InvalidArgumentException(get_class($this) . ' has no relation named "' . $name . '".');
348 | }
349 |
350 | return null;
351 | }
352 |
353 | return $relation;
354 | }
355 |
356 | /**
357 | * Reset event errors back to default.
358 | */
359 | protected function resetEventErrors()
360 | {
361 | $this->eventErrors = [];
362 | }
363 |
364 | /**
365 | * Declares a `has-one` relation.
366 | * The declaration is returned in terms of a relational [[BaseQuery]] instance
367 | * through which the related record can be queried and retrieved back.
368 | *
369 | * A `has-one` relation means that there is at most one related record matching
370 | * the criteria set by this relation, e.g., a customer has one country.
371 | *
372 | * For example, to declare the `country` relation for `Customer` class, we can write
373 | * the following code in the `Customer` class:
374 | *
375 | * ```php
376 | * public function country()
377 | * {
378 | * return $this->hasOne(Country::className(), 'ID', 'PROPERTY_COUNTRY');
379 | * }
380 | * ```
381 | *
382 | * Note that in the above, the 'ID' key in the `$link` parameter refers to an attribute name
383 | * in the related class `Country`, while the 'PROPERTY_COUNTRY' value refers to an attribute name
384 | * in the current BaseBitrixModel class.
385 | *
386 | * Call methods declared in [[BaseQuery]] to further customize the relation.
387 | *
388 | * @param string $class the class name of the related record
389 | * @param string $foreignKey
390 | * @param string $localKey
391 | * @return BaseQuery the relational query object.
392 | */
393 | public function hasOne($class, $foreignKey, $localKey = 'ID')
394 | {
395 | return $this->createRelationQuery($class, $foreignKey, $localKey, false);
396 | }
397 |
398 | /**
399 | * Declares a `has-many` relation.
400 | * The declaration is returned in terms of a relational [[BaseQuery]] instance
401 | * through which the related record can be queried and retrieved back.
402 | *
403 | * A `has-many` relation means that there are multiple related records matching
404 | * the criteria set by this relation, e.g., a customer has many orders.
405 | *
406 | * For example, to declare the `orders` relation for `Customer` class, we can write
407 | * the following code in the `Customer` class:
408 | *
409 | * ```php
410 | * public function orders()
411 | * {
412 | * return $this->hasMany(Order::className(), 'PROPERTY_COUNTRY_VALUE', 'ID');
413 | * }
414 | * ```
415 | *
416 | * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
417 | * an attribute name in the related class `Order`, while the 'id' value refers to
418 | * an attribute name in the current BaseBitrixModel class.
419 | *
420 | * Call methods declared in [[BaseQuery]] to further customize the relation.
421 | *
422 | * @param string $class the class name of the related record
423 | * @param string $foreignKey
424 | * @param string $localKey
425 | * @return BaseQuery the relational query object.
426 | */
427 | public function hasMany($class, $foreignKey, $localKey = 'ID')
428 | {
429 | return $this->createRelationQuery($class, $foreignKey, $localKey, true);
430 | }
431 |
432 | /**
433 | * Creates a query instance for `has-one` or `has-many` relation.
434 | * @param string $class the class name of the related record.
435 | * @param string $foreignKey
436 | * @param string $localKey
437 | * @param bool $multiple whether this query represents a relation to more than one record.
438 | * @return BaseQuery the relational query object.
439 | * @see hasOne()
440 | * @see hasMany()
441 | */
442 | protected function createRelationQuery($class, $foreignKey, $localKey, $multiple)
443 | {
444 | /* @var $class BaseBitrixModel */
445 | /* @var $query BaseQuery */
446 | $query = $class::query();
447 | $query->foreignKey = $localKey;
448 | $query->localKey = $foreignKey;
449 | $query->primaryModel = $this;
450 | $query->multiple = $multiple;
451 | return $query;
452 | }
453 |
454 | /**
455 | * Записать модели как связанные
456 | * @param string $name - название релейшена
457 | * @param Collection|BaseBitrixModel $records - связанные модели
458 | * @see getRelation()
459 | */
460 | public function populateRelation($name, $records)
461 | {
462 | $this->related[$name] = $records;
463 | }
464 |
465 | /**
466 | * Setter for currentLanguage.
467 | *
468 | * @param $language
469 | * @return mixed
470 | */
471 | public static function setCurrentLanguage($language)
472 | {
473 | self::$currentLanguage = $language;
474 | }
475 |
476 | /**
477 | * Getter for currentLanguage.
478 | *
479 | * @return string
480 | */
481 | public static function getCurrentLanguage()
482 | {
483 | return self::$currentLanguage;
484 | }
485 |
486 | /**
487 | * Get value from language field according to current language.
488 | *
489 | * @param $field
490 | * @return mixed
491 | */
492 | protected function getValueFromLanguageField($field)
493 | {
494 | $key = $field . '_' . $this->getCurrentLanguage();
495 |
496 | return isset($this->fields[$key]) ? $this->fields[$key] : null;
497 | }
498 | }
499 |
--------------------------------------------------------------------------------
/src/Models/BitrixModel.php:
--------------------------------------------------------------------------------
1 | 'Fetch',
32 | 'params' => [],
33 | ];
34 |
35 | /**
36 | * Constructor.
37 | *
38 | * @param $id
39 | * @param $fields
40 | */
41 | public function __construct($id = null, $fields = null)
42 | {
43 | static::instantiateObject();
44 |
45 | $this->id = $id;
46 |
47 | $this->fill($fields);
48 | }
49 |
50 | /**
51 | * Activate model.
52 | *
53 | * @return bool
54 | */
55 | public function activate()
56 | {
57 | $this->fields['ACTIVE'] = 'Y';
58 |
59 | return $this->save(['ACTIVE']);
60 | }
61 |
62 | /**
63 | * Deactivate model.
64 | *
65 | * @return bool
66 | */
67 | public function deactivate()
68 | {
69 | $this->fields['ACTIVE'] = 'N';
70 |
71 | return $this->save(['ACTIVE']);
72 | }
73 |
74 | /**
75 | * Internal part of create to avoid problems with static and inheritance
76 | *
77 | * @param $fields
78 | *
79 | * @throws ExceptionFromBitrix
80 | *
81 | * @return static|bool
82 | */
83 | protected static function internalCreate($fields)
84 | {
85 | $model = new static(null, $fields);
86 |
87 | if ($model->onBeforeSave() === false || $model->onBeforeCreate() === false) {
88 | return false;
89 | }
90 |
91 | $bxObject = static::instantiateObject();
92 | $id = static::internalDirectCreate($bxObject, $model->fields);
93 | $model->setId($id);
94 |
95 | $result = $id ? true : false;
96 |
97 | $model->setEventErrorsOnFail($result, $bxObject);
98 | $model->onAfterCreate($result);
99 | $model->onAfterSave($result);
100 | $model->resetEventErrors();
101 | $model->throwExceptionOnFail($result, $bxObject);
102 |
103 | return $model;
104 | }
105 |
106 | public static function internalDirectCreate($bxObject, $fields)
107 | {
108 | return $bxObject->add($fields);
109 | }
110 |
111 | /**
112 | * Delete model.
113 | *
114 | * @return bool
115 | * @throws ExceptionFromBitrix
116 | */
117 | public function delete()
118 | {
119 | if ($this->onBeforeDelete() === false) {
120 | return false;
121 | }
122 |
123 | $result = static::$bxObject->delete($this->id);
124 |
125 | $this->setEventErrorsOnFail($result, static::$bxObject);
126 | $this->onAfterDelete($result);
127 | $this->resetEventErrors();
128 | $this->throwExceptionOnFail($result, static::$bxObject);
129 |
130 | return $result;
131 | }
132 |
133 | /**
134 | * Save model to database.
135 | *
136 | * @param array $selectedFields save only these fields instead of all.
137 | * @return bool
138 | * @throws ExceptionFromBitrix
139 | */
140 | public function save($selectedFields = [])
141 | {
142 | $fieldsSelectedForSave = is_array($selectedFields) ? $selectedFields : func_get_args();
143 | $this->fieldsSelectedForSave = $fieldsSelectedForSave;
144 | if ($this->onBeforeSave() === false || $this->onBeforeUpdate() === false) {
145 | $this->fieldsSelectedForSave = [];
146 | return false;
147 | } else {
148 | $this->fieldsSelectedForSave = [];
149 | }
150 |
151 | $fields = $this->normalizeFieldsForSave($fieldsSelectedForSave);
152 | $result = $fields === null
153 | ? true
154 | : $this->internalUpdate($fields, $fieldsSelectedForSave);
155 |
156 | $this->setEventErrorsOnFail($result, static::$bxObject);
157 | $this->onAfterUpdate($result);
158 | $this->onAfterSave($result);
159 | $this->resetEventErrors();
160 | $this->throwExceptionOnFail($result, static::$bxObject);
161 |
162 | return $result;
163 | }
164 |
165 | /**
166 | * @param $fields
167 | * @param $fieldsSelectedForSave
168 | * @return bool
169 | */
170 | protected function internalUpdate($fields, $fieldsSelectedForSave)
171 | {
172 | return !empty($fields) ? static::$bxObject->update($this->id, $fields) : false;
173 | }
174 |
175 | /**
176 | * Scope to get only active items.
177 | *
178 | * @param BaseQuery $query
179 | *
180 | * @return BaseQuery
181 | */
182 | public function scopeActive($query)
183 | {
184 | $query->filter['ACTIVE'] = 'Y';
185 |
186 | return $query;
187 | }
188 |
189 | /**
190 | * Determine whether the field should be stopped from passing to "update".
191 | *
192 | * @param string $field
193 | * @param mixed $value
194 | * @param array $selectedFields
195 | *
196 | * @return bool
197 | */
198 | protected function fieldShouldNotBeSaved($field, $value, $selectedFields)
199 | {
200 | $blacklistedFields = [
201 | 'ID',
202 | 'IBLOCK_ID',
203 | 'GROUPS',
204 | ];
205 |
206 | return (!empty($selectedFields) && !in_array($field, $selectedFields))
207 | || in_array($field, $blacklistedFields)
208 | || ($field[0] === '~')
209 | || (substr($field, 0, 9) === 'PROPERTY_')
210 | || (is_array($this->original) && array_key_exists($field, $this->original) && $this->original[$field] === $value);
211 | }
212 |
213 | /**
214 | * Instantiate bitrix entity object.
215 | *
216 | * @throws LogicException
217 | *
218 | * @return object
219 | */
220 | public static function instantiateObject()
221 | {
222 | if (static::$bxObject) {
223 | return static::$bxObject;
224 | }
225 |
226 | if (class_exists(static::$objectClass)) {
227 | return static::$bxObject = new static::$objectClass();
228 | }
229 |
230 | throw new LogicException('Object initialization failed');
231 | }
232 |
233 | /**
234 | * Destroy bitrix entity object.
235 | *
236 | * @return void
237 | */
238 | public static function destroyObject()
239 | {
240 | static::$bxObject = null;
241 | }
242 |
243 | /**
244 | * Set eventErrors field on error.
245 | *
246 | * @param bool $result
247 | * @param object $bxObject
248 | */
249 | protected function setEventErrorsOnFail($result, $bxObject)
250 | {
251 | if (!$result) {
252 | $this->eventErrors = (array) $bxObject->LAST_ERROR;
253 | }
254 | }
255 |
256 | /**
257 | * Throw bitrix exception on fail
258 | *
259 | * @param bool $result
260 | * @param object $bxObject
261 | * @throws ExceptionFromBitrix
262 | */
263 | protected function throwExceptionOnFail($result, $bxObject)
264 | {
265 | if (!$result) {
266 | throw new ExceptionFromBitrix($bxObject->LAST_ERROR);
267 | }
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/src/Models/D7Model.php:
--------------------------------------------------------------------------------
1 | id = $id;
68 | $this->fill($fields);
69 | static::instantiateAdapter();
70 | }
71 |
72 | /**
73 | * Setter for adapter (for testing)
74 | * @param $adapter
75 | */
76 | public static function setAdapter($adapter)
77 | {
78 | static::$adapters[get_called_class()] = $adapter;
79 | }
80 |
81 | /**
82 | * Instantiate adapter if it's not instantiated.
83 | *
84 | * @return D7Adapter
85 | */
86 | public static function instantiateAdapter()
87 | {
88 | $class = get_called_class();
89 | if (isset(static::$adapters[$class])) {
90 | return static::$adapters[$class];
91 | }
92 |
93 | return static::$adapters[$class] = new D7Adapter(static::cachedTableClass());
94 | }
95 |
96 | /**
97 | * Instantiate a query object for the model.
98 | *
99 | * @return D7Query
100 | */
101 | public static function query()
102 | {
103 | return new D7Query(static::instantiateAdapter(), get_called_class());
104 | }
105 |
106 | /**
107 | * @return string
108 | * @throws LogicException
109 | */
110 | public static function tableClass()
111 | {
112 | $tableClass = static::TABLE_CLASS;
113 | if (!$tableClass) {
114 | throw new LogicException('You must set TABLE_CLASS constant inside a model or override tableClass() method');
115 | }
116 |
117 | return $tableClass;
118 | }
119 |
120 | /**
121 | * Cached version of table class.
122 | *
123 | * @return string
124 | */
125 | public static function cachedTableClass()
126 | {
127 | $class = get_called_class();
128 | if (!isset(static::$cachedTableClasses[$class])) {
129 | static::$cachedTableClasses[$class] = static::tableClass();
130 | }
131 |
132 | return static::$cachedTableClasses[$class];
133 | }
134 |
135 | /**
136 | * Internal part of create to avoid problems with static and inheritance
137 | *
138 | * @param $fields
139 | *
140 | * @throws ExceptionFromBitrix
141 | *
142 | * @return static|bool
143 | */
144 | protected static function internalCreate($fields)
145 | {
146 | $model = new static(null, $fields);
147 |
148 | if ($model->onBeforeSave() === false || $model->onBeforeCreate() === false) {
149 | return false;
150 | }
151 |
152 | $resultObject = static::instantiateAdapter()->add($model->fields);
153 | $result = $resultObject->isSuccess();
154 | if ($result) {
155 | $model->setId($resultObject->getId());
156 | }
157 |
158 | $model->setEventErrorsOnFail($resultObject);
159 | $model->onAfterCreate($result);
160 | $model->onAfterSave($result);
161 | $model->throwExceptionOnFail($resultObject);
162 |
163 | return $model;
164 | }
165 |
166 | /**
167 | * Delete model
168 | *
169 | * @return bool
170 | * @throws ExceptionFromBitrix
171 | */
172 | public function delete()
173 | {
174 | if ($this->onBeforeDelete() === false) {
175 | return false;
176 | }
177 |
178 | $resultObject = static::instantiateAdapter()->delete($this->id);
179 | $result = $resultObject->isSuccess();
180 |
181 | $this->setEventErrorsOnFail($resultObject);
182 | $this->onAfterDelete($result);
183 | $this->resetEventErrors();
184 | $this->throwExceptionOnFail($resultObject);
185 |
186 | return $result;
187 | }
188 |
189 | /**
190 | * Save model to database.
191 | *
192 | * @param array $selectedFields save only these fields instead of all.
193 | * @return bool
194 | * @throws ExceptionFromBitrix
195 | */
196 | public function save($selectedFields = [])
197 | {
198 | $fieldsSelectedForSave = is_array($selectedFields) ? $selectedFields : func_get_args();
199 | $this->fieldsSelectedForSave = $fieldsSelectedForSave;
200 | if ($this->onBeforeSave() === false || $this->onBeforeUpdate() === false) {
201 | $this->fieldsSelectedForSave = [];
202 | return false;
203 | } else {
204 | $this->fieldsSelectedForSave = [];
205 | }
206 |
207 | $fields = $this->normalizeFieldsForSave($fieldsSelectedForSave);
208 | $resultObject = $fields === null
209 | ? new UpdateResult()
210 | : static::instantiateAdapter()->update($this->id, $fields);
211 | $result = $resultObject->isSuccess();
212 |
213 | $this->setEventErrorsOnFail($resultObject);
214 | $this->onAfterUpdate($result);
215 | $this->onAfterSave($result);
216 | $this->throwExceptionOnFail($resultObject);
217 |
218 | return $result;
219 | }
220 |
221 | /**
222 | * Determine whether the field should be stopped from passing to "update".
223 | *
224 | * @param string $field
225 | * @param mixed $value
226 | * @param array $selectedFields
227 | *
228 | * @return bool
229 | */
230 | protected function fieldShouldNotBeSaved($field, $value, $selectedFields)
231 | {
232 | return (!empty($selectedFields) && !in_array($field, $selectedFields)) || $field === 'ID';
233 | }
234 |
235 | /**
236 | * Throw bitrix exception on fail
237 | *
238 | * @param \Bitrix\Main\Entity\Result $resultObject
239 | * @throws ExceptionFromBitrix
240 | */
241 | protected function throwExceptionOnFail($resultObject)
242 | {
243 | if (!$resultObject->isSuccess()) {
244 | throw new ExceptionFromBitrix(implode('; ', $resultObject->getErrorMessages()));
245 | }
246 | }
247 |
248 | /**
249 | * Set eventErrors field on error.
250 | *
251 | * @param \Bitrix\Main\Entity\Result $resultObject
252 | */
253 | protected function setEventErrorsOnFail($resultObject)
254 | {
255 | if (!$resultObject->isSuccess()) {
256 | $this->eventErrors = (array) $resultObject->getErrorMessages();
257 | }
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/src/Models/ElementModel.php:
--------------------------------------------------------------------------------
1 | add($fields, static::$workFlow, static::$updateSearch, static::$resizePictures);
149 | }
150 |
151 | /**
152 | * Fetches static::$iblockPropertiesData if it's not fetched and returns it.
153 | *
154 | * @return array
155 | */
156 | protected static function getCachedIblockPropertiesData()
157 | {
158 | $iblockId = static::iblockId();
159 | if (!empty(self::$iblockPropertiesData[$iblockId])) {
160 | return self::$iblockPropertiesData[$iblockId];
161 | }
162 |
163 | $props = [];
164 | $dbRes = CIBlock::GetProperties($iblockId, [], []);
165 | while($property = $dbRes->Fetch()) {
166 | $props[$property['CODE']] = $property;
167 | }
168 |
169 | return self::$iblockPropertiesData[$iblockId] = $props;
170 | }
171 |
172 | /**
173 | * Setter for self::$iblockPropertiesData[static::iblockId()] mainly for testing.
174 | *
175 | * @param $data
176 | * @return void
177 | */
178 | public static function setCachedIblockPropertiesData($data)
179 | {
180 | self::$iblockPropertiesData[static::iblockId()] = $data;
181 | }
182 |
183 | /**
184 | * Corresponding section model full qualified class name.
185 | * MUST be overridden if you are going to use section model for this iblock.
186 | *
187 | * @throws LogicException
188 | *
189 | * @return string
190 | */
191 | public static function sectionModel()
192 | {
193 | throw new LogicException('public static function sectionModel() MUST be overridden');
194 | }
195 |
196 | /**
197 | * Instantiate a query object for the model.
198 | *
199 | * @return ElementQuery
200 | */
201 | public static function query()
202 | {
203 | return new ElementQuery(static::instantiateObject(), get_called_class());
204 | }
205 |
206 | /**
207 | * Scope to sort by date.
208 | *
209 | * @param ElementQuery $query
210 | * @param string $sort
211 | *
212 | * @return ElementQuery
213 | */
214 | public function scopeSortByDate($query, $sort = 'DESC')
215 | {
216 | return $query->sort(['ACTIVE_FROM' => $sort]);
217 | }
218 |
219 | /**
220 | * Scope to get only items from a given section.
221 | *
222 | * @param ElementQuery $query
223 | * @param mixed $id
224 | *
225 | * @return ElementQuery
226 | */
227 | public function scopeFromSectionWithId($query, $id)
228 | {
229 | $query->filter['SECTION_ID'] = $id;
230 |
231 | return $query;
232 | }
233 |
234 | /**
235 | * Scope to get only items from a given section.
236 | *
237 | * @param ElementQuery $query
238 | * @param string $code
239 | *
240 | * @return ElementQuery
241 | */
242 | public function scopeFromSectionWithCode($query, $code)
243 | {
244 | $query->filter['SECTION_CODE'] = $code;
245 |
246 | return $query;
247 | }
248 |
249 | /**
250 | * Fill extra fields when $this->field is called.
251 | *
252 | * @return null
253 | */
254 | protected function afterFill()
255 | {
256 | $this->normalizePropertyFormat();
257 | }
258 |
259 | /**
260 | * Load all model attributes from cache or database.
261 | *
262 | * @return $this
263 | */
264 | public function load()
265 | {
266 | $this->getFields();
267 |
268 | return $this;
269 | }
270 |
271 | /**
272 | * Get element's sections from cache or database.
273 | *
274 | * @return array
275 | */
276 | public function getSections()
277 | {
278 | if ($this->sectionsAreFetched) {
279 | return $this->fields['IBLOCK_SECTION'];
280 | }
281 |
282 | return $this->refreshSections();
283 | }
284 |
285 | /**
286 | * Refresh model from database and place data to $this->fields.
287 | *
288 | * @return array
289 | */
290 | public function refresh()
291 | {
292 | return $this->refreshFields();
293 | }
294 |
295 | /**
296 | * Refresh element's fields and save them to a class field.
297 | *
298 | * @return array
299 | */
300 | public function refreshFields()
301 | {
302 | if ($this->id === null) {
303 | $this->original = [];
304 | return $this->fields = [];
305 | }
306 |
307 | $sectionsBackup = isset($this->fields['IBLOCK_SECTION']) ? $this->fields['IBLOCK_SECTION'] : null;
308 |
309 | $this->fields = static::query()->getById($this->id)->fields;
310 |
311 | if (!empty($sectionsBackup)) {
312 | $this->fields['IBLOCK_SECTION'] = $sectionsBackup;
313 | }
314 |
315 | $this->fieldsAreFetched = true;
316 |
317 | $this->original = $this->fields;
318 |
319 | return $this->fields;
320 | }
321 |
322 | /**
323 | * Refresh element's sections and save them to a class field.
324 | *
325 | * @return array
326 | */
327 | public function refreshSections()
328 | {
329 | if ($this->id === null) {
330 | return [];
331 | }
332 |
333 | $this->fields['IBLOCK_SECTION'] = [];
334 | $dbSections = static::$bxObject->getElementGroups($this->id, true);
335 | while ($section = $dbSections->Fetch()) {
336 | $this->fields['IBLOCK_SECTION'][] = $section;
337 | }
338 |
339 | $this->sectionsAreFetched = true;
340 |
341 | return $this->fields['IBLOCK_SECTION'];
342 | }
343 |
344 | /**
345 | * @deprecated in favour of `->section()`
346 | * Get element direct section as ID or array of fields.
347 | *
348 | * @param bool $load
349 | *
350 | * @return false|int|array
351 | */
352 | public function getSection($load = false)
353 | {
354 | $fields = $this->getFields();
355 | if (!$load) {
356 | return $fields['IBLOCK_SECTION_ID'];
357 | }
358 |
359 | /** @var SectionModel $sectionModel */
360 | $sectionModel = static::sectionModel();
361 | if (!$fields['IBLOCK_SECTION_ID']) {
362 | return false;
363 | }
364 |
365 | return $sectionModel::query()->getById($fields['IBLOCK_SECTION_ID'])->toArray();
366 | }
367 |
368 | /**
369 | * Get element direct section as model object.
370 | *
371 | * @param bool $load
372 | *
373 | * @return false|SectionModel
374 | */
375 | public function section($load = false)
376 | {
377 | $fields = $this->getFields();
378 |
379 | /** @var SectionModel $sectionModel */
380 | $sectionModel = static::sectionModel();
381 |
382 | return $load
383 | ? $sectionModel::query()->getById($fields['IBLOCK_SECTION_ID'])
384 | : new $sectionModel($fields['IBLOCK_SECTION_ID']);
385 | }
386 |
387 | /**
388 | * Proxy for GetPanelButtons
389 | *
390 | * @param array $options
391 | * @return array
392 | */
393 | public function getPanelButtons($options = [])
394 | {
395 | return CIBlock::GetPanelButtons(
396 | static::iblockId(),
397 | $this->id,
398 | 0,
399 | $options
400 | );
401 | }
402 |
403 | /**
404 | * Save props to database.
405 | * If selected is not empty then only props from it are saved.
406 | *
407 | * @param array $selected
408 | *
409 | * @return bool
410 | */
411 | public function saveProps($selected = [])
412 | {
413 | $propertyValues = $this->constructPropertyValuesForSave($selected);
414 | if (empty($propertyValues)) {
415 | return false;
416 | }
417 |
418 | $bxMethod = empty($selected) ? 'setPropertyValues' : 'setPropertyValuesEx';
419 | static::$bxObject->$bxMethod(
420 | $this->id,
421 | static::iblockId(),
422 | $propertyValues
423 | );
424 |
425 | return true;
426 | }
427 |
428 | /**
429 | * Normalize properties's format converting it to 'PROPERTY_"CODE"_VALUE'.
430 | *
431 | * @return null
432 | */
433 | protected function normalizePropertyFormat()
434 | {
435 | if (empty($this->fields['PROPERTIES'])) {
436 | return;
437 | }
438 |
439 | foreach ($this->fields['PROPERTIES'] as $code => $prop) {
440 | $this->fields['PROPERTY_'.$code.'_VALUE'] = $prop['VALUE'];
441 | $this->fields['~PROPERTY_'.$code.'_VALUE'] = $prop['~VALUE'];
442 | $this->fields['PROPERTY_'.$code.'_DESCRIPTION'] = $prop['DESCRIPTION'];
443 | $this->fields['~PROPERTY_'.$code.'_DESCRIPTION'] = $prop['~DESCRIPTION'];
444 | $this->fields['PROPERTY_'.$code.'_VALUE_ID'] = $prop['PROPERTY_VALUE_ID'];
445 | }
446 | }
447 |
448 | /**
449 | * Construct 'PROPERTY_VALUES' => [...] from flat fields array.
450 | * This is used in save.
451 | * If $selectedFields are specified only those are saved.
452 | *
453 | * @param $selectedFields
454 | *
455 | * @return array
456 | */
457 | protected function constructPropertyValuesForSave($selectedFields = [])
458 | {
459 | $propertyValues = [];
460 | $saveOnlySelected = !empty($selectedFields);
461 |
462 | $iblockPropertiesData = static::getCachedIblockPropertiesData();
463 |
464 | if ($saveOnlySelected) {
465 | foreach ($selectedFields as $code) {
466 | // if we pass PROPERTY_X_DESCRIPTION as selected field, we need to add PROPERTY_X_VALUE as well.
467 | if (preg_match('/^PROPERTY_(.*)_DESCRIPTION$/', $code, $matches) && !empty($matches[1])) {
468 | $propertyCode = $matches[1];
469 | $propertyValueKey = "PROPERTY_{$propertyCode}_VALUE";
470 | if (!in_array($propertyValueKey, $selectedFields)) {
471 | $selectedFields[] = $propertyValueKey;
472 | }
473 | }
474 |
475 | // if we pass PROPERTY_X_ENUM_ID as selected field, we need to add PROPERTY_X_VALUE as well.
476 | if (preg_match('/^PROPERTY_(.*)_ENUM_ID$/', $code, $matches) && !empty($matches[1])) {
477 | $propertyCode = $matches[1];
478 | $propertyValueKey = "PROPERTY_{$propertyCode}_VALUE";
479 | if (!in_array($propertyValueKey, $selectedFields)) {
480 | $selectedFields[] = $propertyValueKey;
481 | }
482 | }
483 | }
484 | }
485 |
486 | foreach ($this->fields as $code => $value) {
487 | if ($saveOnlySelected && !in_array($code, $selectedFields)) {
488 | continue;
489 | }
490 |
491 | if (preg_match('/^PROPERTY_(.*)_VALUE$/', $code, $matches) && !empty($matches[1])) {
492 | $propertyCode = $matches[1];
493 | $iblockPropertyData = (array) $iblockPropertiesData[$propertyCode];
494 |
495 | // if file was not changed skip it or it will be duplicated
496 | if ($iblockPropertyData && $iblockPropertyData['PROPERTY_TYPE'] === 'F' && !empty($this->original[$code]) && $this->original[$code] === $value) {
497 | continue;
498 | }
499 |
500 | // if property type is a list we need to use enum ID/IDs as value/values
501 | if (array_key_exists("PROPERTY_{$propertyCode}_ENUM_ID", $this->fields)) {
502 | $value = $this->fields["PROPERTY_{$propertyCode}_ENUM_ID"];
503 | } elseif ($iblockPropertyData && $iblockPropertyData['PROPERTY_TYPE'] === 'L' && $iblockPropertyData['MULTIPLE'] === 'Y') {
504 | $value = array_keys($value);
505 | }
506 |
507 | // if property values have descriptions
508 | // we skip file properties here for now because they cause endless problems. Handle them manually.
509 | if (array_key_exists("PROPERTY_{$propertyCode}_DESCRIPTION", $this->fields) && (!$iblockPropertyData || $iblockPropertyData['PROPERTY_TYPE'] !== 'F')) {
510 | $description = $this->fields["PROPERTY_{$propertyCode}_DESCRIPTION"];
511 |
512 | if (is_array($value) && is_array($description)) {
513 | // for multiple property
514 | foreach ($value as $rowIndex => $rowValue) {
515 | $propertyValues[$propertyCode][] = [
516 | 'VALUE' => $rowValue,
517 | 'DESCRIPTION' => $description[$rowIndex]
518 | ];
519 | }
520 | } else {
521 | // for single property
522 | $propertyValues[$propertyCode] = [
523 | 'VALUE' => $value,
524 | 'DESCRIPTION' => $description
525 | ];
526 | }
527 | } else {
528 | $propertyValues[$propertyCode] = $value;
529 | }
530 | }
531 | }
532 |
533 | return $propertyValues;
534 | }
535 |
536 | /**
537 | * Determine whether the field should be stopped from passing to "update".
538 | *
539 | * @param string $field
540 | * @param mixed $value
541 | * @param array $selectedFields
542 | *
543 | * @return bool
544 | */
545 | protected function fieldShouldNotBeSaved($field, $value, $selectedFields)
546 | {
547 | $blacklistedFields = [
548 | 'ID',
549 | 'IBLOCK_ID',
550 | 'PROPERTIES',
551 | 'PROPERTY_VALUES',
552 | ];
553 |
554 | return (!empty($selectedFields) && !in_array($field, $selectedFields))
555 | || in_array($field, $blacklistedFields)
556 | || ($field[0] === '~');
557 | //|| (substr($field, 0, 9) === 'PROPERTY_');
558 | }
559 |
560 | /**
561 | * @param $fields
562 | * @param $fieldsSelectedForSave
563 | * @return bool
564 | */
565 | protected function internalUpdate($fields, $fieldsSelectedForSave)
566 | {
567 | $fields = $fields ?: [];
568 | foreach ($fields as $key => $value) {
569 | if (substr($key, 0, 9) === 'PROPERTY_') {
570 | unset($fields[$key]);
571 | }
572 | }
573 |
574 | $result = !empty($fields) ? static::$bxObject->update($this->id, $fields, static::$workFlow, static::$updateSearch, static::$resizePictures) : false;
575 | $savePropsResult = $this->saveProps($fieldsSelectedForSave);
576 | $result = $result || $savePropsResult;
577 |
578 | return $result;
579 | }
580 |
581 | /**
582 | * Get value from language field according to current language.
583 | *
584 | * @param $field
585 | * @return mixed
586 | */
587 | protected function getValueFromLanguageField($field)
588 | {
589 | $key = $field . '_' . $this->getCurrentLanguage() . '_VALUE';
590 |
591 | return isset($this->fields[$key]) ? $this->fields[$key] : null;
592 | }
593 |
594 | /**
595 | * @param $value
596 | */
597 | public static function setWorkflow($value)
598 | {
599 | static::$workFlow = $value;
600 | }
601 |
602 | /**
603 | * @param $value
604 | */
605 | public static function setUpdateSearch($value)
606 | {
607 | static::$updateSearch = $value;
608 | }
609 |
610 | /**
611 | * @param $value
612 | */
613 | public static function setResizePictures($value)
614 | {
615 | static::$resizePictures = $value;
616 | }
617 | }
618 |
--------------------------------------------------------------------------------
/src/Models/EloquentModel.php:
--------------------------------------------------------------------------------
1 | multipleHighloadBlockFields)) {
44 | return unserialize($this->attributes[$key]);
45 | }
46 |
47 | return parent::getAttribute($key);
48 | }
49 |
50 | /**
51 | * Set a given attribute on the model.
52 | *
53 | * @param string $key
54 | * @param mixed $value
55 | * @return $this
56 | */
57 | public function setAttribute($key, $value)
58 | {
59 | if (in_array($key, $this->multipleHighloadBlockFields)) {
60 | $this->attributes[$key] = serialize($value);
61 |
62 | return $this;
63 | }
64 |
65 | parent::setAttribute($key, $value);
66 |
67 | return $this;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Models/SectionModel.php:
--------------------------------------------------------------------------------
1 | filter($filter)
145 | ->filter(['SECTION_ID' => $this->id])
146 | ->select('ID')
147 | ->getList()
148 | ->transform(function ($section) {
149 | return (int) $section['ID'];
150 | })
151 | ->all();
152 | }
153 |
154 | /**
155 | * Get IDs of all children of the section (direct or not).
156 | * Additional filter can be specified.
157 | *
158 | * @param array $filter
159 | * @param array|string $sort
160 | *
161 | * @return array
162 | */
163 | public function getAllChildren(array $filter = [], $sort = ['LEFT_MARGIN' => 'ASC'])
164 | {
165 | if (!isset($this->fields['LEFT_MARGIN']) || !isset($this->fields['RIGHT_MARGIN'])) {
166 | $this->refresh();
167 | }
168 |
169 | return static::query()
170 | ->sort($sort)
171 | ->filter($filter)
172 | ->filter([
173 | '!ID' => $this->id,
174 | '>LEFT_MARGIN' => $this->fields['LEFT_MARGIN'],
175 | ' $this->fields['RIGHT_MARGIN'],
176 | ])
177 | ->select('ID')
178 | ->getList()
179 | ->transform(function ($section) {
180 | return (int) $section['ID'];
181 | })
182 | ->all();
183 | }
184 |
185 | /**
186 | * Proxy for GetPanelButtons
187 | *
188 | * @param array $options
189 | * @return array
190 | */
191 | public function getPanelButtons($options = [])
192 | {
193 | return CIBlock::GetPanelButtons(
194 | static::iblockId(),
195 | 0,
196 | $this->id,
197 | $options
198 | );
199 | }
200 |
201 | public static function internalDirectCreate($bxObject, $fields)
202 | {
203 | return $bxObject->add($fields, static::$resort, static::$updateSearch, static::$resizePictures);
204 | }
205 |
206 | /**
207 | * @param $fields
208 | * @param $fieldsSelectedForSave
209 | * @return bool
210 | */
211 | protected function internalUpdate($fields, $fieldsSelectedForSave)
212 | {
213 | return !empty($fields) ? static::$bxObject->update($this->id, $fields, static::$resort, static::$updateSearch, static::$resizePictures) : false;
214 | }
215 |
216 | /**
217 | * @param $value
218 | */
219 | public static function setResort($value)
220 | {
221 | static::$resort = $value;
222 | }
223 |
224 | /**
225 | * @param $value
226 | */
227 | public static function setUpdateSearch($value)
228 | {
229 | static::$updateSearch = $value;
230 | }
231 |
232 | /**
233 | * @param $value
234 | */
235 | public static function setResizePictures($value)
236 | {
237 | static::$resizePictures = $value;
238 | }
239 |
240 | /**
241 | * @param $query
242 | * @param SectionModel $section
243 | * @return SectionQuery
244 | */
245 | public function scopeChildrenOf(SectionQuery $query, SectionModel $section)
246 | {
247 | $query->filter['>LEFT_MARGIN'] = $section->fields['LEFT_MARGIN'];
248 | $query->filter['fields['RIGHT_MARGIN'];
249 | $query->filter['>DEPTH_LEVEL'] = $section->fields['DEPTH_LEVEL'];
250 |
251 | return $query;
252 | }
253 |
254 | /**
255 | * @param $query
256 | * @param SectionModel|int $section
257 | * @return SectionQuery
258 | */
259 | public function scopeDirectChildrenOf(SectionQuery $query, $section)
260 | {
261 | $query->filter['SECTION_ID'] = is_int($section) ? $section : $section->id;
262 |
263 | return $query;
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/src/Models/Traits/DeactivationTrait.php:
--------------------------------------------------------------------------------
1 | markForActivation()->save();
16 | }
17 |
18 | /**
19 | * Deactivate element.
20 | */
21 | public function deactivate()
22 | {
23 | $this->markForDeactivation()->save();
24 | }
25 |
26 | /**
27 | * @param $query
28 | * @return mixed
29 | */
30 | public function scopeActive($query)
31 | {
32 | return $this instanceof D7Model
33 | ? $query->filter(['==UF_DEACTIVATED_AT' => null])
34 | : $query->whereNull('UF_DEACTIVATED_AT');
35 | }
36 |
37 | /**
38 | * @param $query
39 | * @return mixed
40 | */
41 | public function scopeDeactivated($query)
42 | {
43 | return $this instanceof D7Model
44 | ? $query->filter(['!==UF_DEACTIVATED_AT' => null])
45 | : $query->whereNotNull('UF_DEACTIVATED_AT');
46 | }
47 |
48 | /**
49 | * @return $this
50 | */
51 | public function markForActivation()
52 | {
53 | $this['UF_DEACTIVATED_AT'] = null;
54 |
55 | return $this;
56 | }
57 |
58 | /**
59 | * @return $this
60 | */
61 | public function markForDeactivation()
62 | {
63 | $this['UF_DEACTIVATED_AT'] = $this instanceof D7Model ? new DateTime() : date('Y-m-d H:i:s');
64 |
65 | return $this;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Models/Traits/HidesAttributes.php:
--------------------------------------------------------------------------------
1 | hidden;
29 | }
30 |
31 | /**
32 | * Set the hidden attributes for the model.
33 | *
34 | * @param array $hidden
35 | * @return $this
36 | */
37 | public function setHidden(array $hidden)
38 | {
39 | $this->hidden = $hidden;
40 |
41 | return $this;
42 | }
43 |
44 | /**
45 | * Add hidden attributes for the model.
46 | *
47 | * @param array|string|null $attributes
48 | * @return void
49 | */
50 | public function addHidden($attributes = null)
51 | {
52 | $this->hidden = array_merge(
53 | $this->hidden,
54 | is_array($attributes) ? $attributes : func_get_args()
55 | );
56 | }
57 |
58 | /**
59 | * Get the visible attributes for the model.
60 | *
61 | * @return array
62 | */
63 | public function getVisible()
64 | {
65 | return $this->visible;
66 | }
67 |
68 | /**
69 | * Set the visible attributes for the model.
70 | *
71 | * @param array $visible
72 | * @return $this
73 | */
74 | public function setVisible(array $visible)
75 | {
76 | $this->visible = $visible;
77 |
78 | return $this;
79 | }
80 |
81 | /**
82 | * Add visible attributes for the model.
83 | *
84 | * @param array|string|null $attributes
85 | * @return void
86 | */
87 | public function addVisible($attributes = null)
88 | {
89 | $this->visible = array_merge(
90 | $this->visible,
91 | is_array($attributes) ? $attributes : func_get_args()
92 | );
93 | }
94 |
95 | /**
96 | * Make the given, typically hidden, attributes visible.
97 | *
98 | * @param array|string $attributes
99 | * @return $this
100 | */
101 | public function makeVisible($attributes)
102 | {
103 | $this->hidden = array_diff($this->hidden, (array) $attributes);
104 |
105 | if (!empty($this->visible)) {
106 | $this->addVisible($attributes);
107 | }
108 |
109 | return $this;
110 | }
111 |
112 | /**
113 | * Make the given, typically visible, attributes hidden.
114 | *
115 | * @param array|string $attributes
116 | * @return $this
117 | */
118 | public function makeHidden($attributes)
119 | {
120 | $attributes = (array) $attributes;
121 |
122 | $this->visible = array_diff($this->visible, $attributes);
123 |
124 | $this->hidden = array_unique(array_merge($this->hidden, $attributes));
125 |
126 | return $this;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Models/Traits/ModelEventsTrait.php:
--------------------------------------------------------------------------------
1 | getId()))->load();
101 | }
102 |
103 | /**
104 | * Fill extra fields when $this->field is called.
105 | *
106 | * @return null
107 | */
108 | protected function afterFill()
109 | {
110 | if (isset($this->fields['GROUP_ID']) && is_array(['GROUP_ID'])) {
111 | $this->groupsAreFetched = true;
112 | }
113 | }
114 |
115 | /**
116 | * Fill model groups if they are already known.
117 | * Saves DB queries.
118 | *
119 | * @param array $groups
120 | *
121 | * @return null
122 | */
123 | public function fillGroups($groups)
124 | {
125 | $this->fields['GROUP_ID'] = $groups;
126 |
127 | $this->groupsAreFetched = true;
128 | }
129 |
130 | /**
131 | * Load model fields from database if they are not loaded yet.
132 | *
133 | * @return $this
134 | */
135 | public function load()
136 | {
137 | $this->getFields();
138 | $this->getGroups();
139 |
140 | return $this;
141 | }
142 |
143 | /**
144 | * Get user groups from cache or database.
145 | *
146 | * @return array
147 | */
148 | public function getGroups()
149 | {
150 | if ($this->groupsAreFetched) {
151 | return $this->fields['GROUP_ID'];
152 | }
153 |
154 | return $this->refreshGroups();
155 | }
156 |
157 | /**
158 | * Refresh model from database and place data to $this->fields.
159 | *
160 | * @return array
161 | */
162 | public function refresh()
163 | {
164 | $this->refreshFields();
165 |
166 | $this->refreshGroups();
167 |
168 | return $this->fields;
169 | }
170 |
171 | /**
172 | * Refresh user fields and save them to a class field.
173 | *
174 | * @return array
175 | */
176 | public function refreshFields()
177 | {
178 | if ($this->id === null) {
179 | $this->original = [];
180 | return $this->fields = [];
181 | }
182 |
183 | $groupBackup = isset($this->fields['GROUP_ID']) ? $this->fields['GROUP_ID'] : null;
184 |
185 | $this->fields = static::query()->getById($this->id)->fields;
186 |
187 | if ($groupBackup) {
188 | $this->fields['GROUP_ID'] = $groupBackup;
189 | }
190 |
191 | $this->fieldsAreFetched = true;
192 |
193 | $this->original = $this->fields;
194 |
195 | return $this->fields;
196 | }
197 |
198 | /**
199 | * Refresh user groups and save them to a class field.
200 | *
201 | * @return array
202 | */
203 | public function refreshGroups()
204 | {
205 | if ($this->id === null) {
206 | return [];
207 | }
208 |
209 | global $USER;
210 |
211 | $this->fields['GROUP_ID'] = $this->isCurrent()
212 | ? $USER->getUserGroupArray()
213 | : static::$bxObject->getUserGroup($this->id);
214 |
215 | $this->groupsAreFetched = true;
216 |
217 | return $this->fields['GROUP_ID'];
218 | }
219 |
220 | /**
221 | * Check if user is an admin.
222 | */
223 | public function isAdmin()
224 | {
225 | return $this->hasGroupWithId(1);
226 | }
227 |
228 | /**
229 | * Check if this user is the operating user.
230 | */
231 | public function isCurrent()
232 | {
233 | global $USER;
234 |
235 | return $USER->getId() && $this->id == $USER->getId();
236 | }
237 |
238 | /**
239 | * Check if user has role with a given ID.
240 | *
241 | * @param $role_id
242 | *
243 | * @return bool
244 | */
245 | public function hasGroupWithId($role_id)
246 | {
247 | return in_array($role_id, $this->getGroups());
248 | }
249 |
250 | /**
251 | * Check if user is authorized.
252 | *
253 | * @return bool
254 | */
255 | public function isAuthorized()
256 | {
257 | global $USER;
258 |
259 | return ($USER->getId() == $this->id) && $USER->isAuthorized();
260 | }
261 |
262 | /**
263 | * Check if user is guest.
264 | *
265 | * @return bool
266 | */
267 | public function isGuest()
268 | {
269 | return !$this->isAuthorized();
270 | }
271 |
272 | /**
273 | * Logout user.
274 | *
275 | * @return void
276 | */
277 | public function logout()
278 | {
279 | global $USER;
280 |
281 | $USER->logout();
282 | }
283 |
284 | /**
285 | * Scope to get only users from a given group / groups.
286 | *
287 | * @param UserQuery $query
288 | * @param int|array $id
289 | *
290 | * @return UserQuery
291 | */
292 | public function scopeFromGroup($query, $id)
293 | {
294 | $query->filter['GROUPS_ID'] = $id;
295 |
296 | return $query;
297 | }
298 |
299 | /**
300 | * Substitute old group with the new one.
301 | *
302 | * @param int $old
303 | * @param int $new
304 | *
305 | * @return void
306 | */
307 | public function substituteGroup($old, $new)
308 | {
309 | $groups = $this->getGroups();
310 |
311 | if (($key = array_search($old, $groups)) !== false) {
312 | unset($groups[$key]);
313 | }
314 |
315 | if (!in_array($new, $groups)) {
316 | $groups[] = $new;
317 | }
318 |
319 | $this->fields['GROUP_ID'] = $groups;
320 | }
321 | }
322 |
--------------------------------------------------------------------------------
/src/Queries/BaseQuery.php:
--------------------------------------------------------------------------------
1 | primaryModel)) {
105 | // Запрос - подгрузка релейшена. Надо добавить filter
106 | $this->filterByModels([$this->primaryModel]);
107 | }
108 |
109 | if ($this->queryShouldBeStopped) {
110 | return new Collection();
111 | }
112 |
113 | $models = $this->loadModels();
114 |
115 | if (!empty($this->with)) {
116 | $this->findWith($this->with, $models);
117 | }
118 |
119 | return $models;
120 | }
121 |
122 | /**
123 | * Get list of items.
124 | *
125 | * @return Collection
126 | */
127 | abstract protected function loadModels();
128 |
129 | /**
130 | * Constructor.
131 | *
132 | * @param object|string $bxObject
133 | * @param string $modelName
134 | */
135 | public function __construct($bxObject, $modelName)
136 | {
137 | $this->bxObject = $bxObject;
138 | $this->modelName = $modelName;
139 | $this->model = new $modelName();
140 | }
141 |
142 | /**
143 | * Get the first item that matches query params.
144 | *
145 | * @return mixed
146 | */
147 | public function first()
148 | {
149 | return $this->limit(1)->getList()->first(null, false);
150 | }
151 |
152 | /**
153 | * Get item by its id.
154 | *
155 | * @param int $id
156 | *
157 | * @return mixed
158 | */
159 | public function getById($id)
160 | {
161 | if (!$id || $this->queryShouldBeStopped) {
162 | return false;
163 | }
164 |
165 | $this->sort = [];
166 | $this->filter['ID'] = $id;
167 |
168 | return $this->getList()->first(null, false);
169 | }
170 |
171 | /**
172 | * Setter for sort.
173 | *
174 | * @param mixed $by
175 | * @param string $order
176 | *
177 | * @return $this
178 | */
179 | public function sort($by, $order = 'ASC')
180 | {
181 | $this->sort = is_array($by) ? $by : [$by => $order];
182 |
183 | return $this;
184 | }
185 |
186 | /**
187 | * Another setter for sort.
188 | *
189 | * @param mixed $by
190 | * @param string $order
191 | *
192 | * @return $this
193 | */
194 | public function order($by, $order = 'ASC')
195 | {
196 | return $this->sort($by, $order);
197 | }
198 |
199 | /**
200 | * Setter for filter.
201 | *
202 | * @param array $filter
203 | *
204 | * @return $this
205 | */
206 | public function filter($filter)
207 | {
208 | $this->filter = array_merge($this->filter, $filter);
209 |
210 | return $this;
211 | }
212 |
213 | /**
214 | * Reset filter.
215 | *
216 | * @return $this
217 | */
218 | public function resetFilter()
219 | {
220 | $this->filter = [];
221 |
222 | return $this;
223 | }
224 |
225 | /**
226 | * Add another filter to filters array.
227 | *
228 | * @param array $filters
229 | *
230 | * @return $this
231 | */
232 | public function addFilter($filters)
233 | {
234 | foreach ($filters as $field => $value) {
235 | $this->filter[$field] = $value;
236 | }
237 |
238 | return $this;
239 | }
240 |
241 | /**
242 | * Setter for navigation.
243 | *
244 | * @param $value
245 | *
246 | * @return $this
247 | */
248 | public function navigation($value)
249 | {
250 | $this->navigation = $value;
251 |
252 | return $this;
253 | }
254 |
255 | /**
256 | * Setter for select.
257 | *
258 | * @param $value
259 | *
260 | * @return $this
261 | */
262 | public function select($value)
263 | {
264 | $this->select = is_array($value) ? $value : func_get_args();
265 |
266 | return $this;
267 | }
268 |
269 | /**
270 | * Setter for cache ttl.
271 | *
272 | * @param float|int $minutes
273 | *
274 | * @return $this
275 | */
276 | public function cache($minutes)
277 | {
278 | $this->cacheTtl = $minutes;
279 |
280 | return $this;
281 | }
282 |
283 | /**
284 | * Setter for keyBy.
285 | *
286 | * @param string $value
287 | *
288 | * @return $this
289 | */
290 | public function keyBy($value)
291 | {
292 | $this->keyBy = $value;
293 |
294 | return $this;
295 | }
296 |
297 | /**
298 | * Set the "limit" value of the query.
299 | *
300 | * @param int $value
301 | *
302 | * @return $this
303 | */
304 | public function limit($value)
305 | {
306 | $this->navigation['nPageSize'] = $value;
307 |
308 | return $this;
309 | }
310 |
311 | /**
312 | * Set the "page number" value of the query.
313 | *
314 | * @param int $num
315 | *
316 | * @return $this
317 | */
318 | public function page($num)
319 | {
320 | $this->navigation['iNumPage'] = $num;
321 |
322 | return $this;
323 | }
324 |
325 | /**
326 | * Alias for "limit".
327 | *
328 | * @param int $value
329 | *
330 | * @return $this
331 | */
332 | public function take($value)
333 | {
334 | return $this->limit($value);
335 | }
336 |
337 | /**
338 | * Set the limit and offset for a given page.
339 | *
340 | * @param int $page
341 | * @param int $perPage
342 | * @return $this
343 | */
344 | public function forPage($page, $perPage = 15)
345 | {
346 | return $this->take($perPage)->page($page);
347 | }
348 |
349 | /**
350 | * Paginate the given query into a paginator.
351 | *
352 | * @param int $perPage
353 | * @param string $pageName
354 | *
355 | * @return \Illuminate\Pagination\LengthAwarePaginator
356 | */
357 | public function paginate($perPage = 15, $pageName = 'page')
358 | {
359 | $page = Paginator::resolveCurrentPage($pageName);
360 | $total = $this->count();
361 | $results = $this->forPage($page, $perPage)->getList();
362 |
363 | return new LengthAwarePaginator($results, $total, $perPage, $page, [
364 | 'path' => Paginator::resolveCurrentPath(),
365 | 'pageName' => $pageName,
366 | ]);
367 | }
368 |
369 | /**
370 | * Get a paginator only supporting simple next and previous links.
371 | *
372 | * This is more efficient on larger data-sets, etc.
373 | *
374 | * @param int $perPage
375 | * @param string $pageName
376 | *
377 | * @return \Illuminate\Pagination\Paginator
378 | */
379 | public function simplePaginate($perPage = 15, $pageName = 'page')
380 | {
381 | $page = Paginator::resolveCurrentPage($pageName);
382 | $results = $this->forPage($page, $perPage + 1)->getList();
383 |
384 | return new Paginator($results, $perPage, $page, [
385 | 'path' => Paginator::resolveCurrentPath(),
386 | 'pageName' => $pageName,
387 | ]);
388 | }
389 |
390 | /**
391 | * Stop the query from touching DB.
392 | *
393 | * @return $this
394 | */
395 | public function stopQuery()
396 | {
397 | $this->queryShouldBeStopped = true;
398 |
399 | return $this;
400 | }
401 |
402 | /**
403 | * Adds $item to $results using keyBy value.
404 | *
405 | * @param $results
406 | * @param BaseBitrixModel $object
407 | *
408 | * @return void
409 | */
410 | protected function addItemToResultsUsingKeyBy(&$results, BaseBitrixModel $object)
411 | {
412 | $item = $object->fields;
413 | if (!array_key_exists($this->keyBy, $item)) {
414 | throw new LogicException("Field {$this->keyBy} is not found in object");
415 | }
416 |
417 | $keyByValue = $item[$this->keyBy];
418 |
419 | if (!isset($results[$keyByValue])) {
420 | $results[$keyByValue] = $object;
421 | } else {
422 | $oldFields = $results[$keyByValue]->fields;
423 | foreach ($oldFields as $field => $oldValue) {
424 | // пропускаем служебные поля.
425 | if (in_array($field, ['_were_multiplied', 'PROPERTIES'])) {
426 | continue;
427 | }
428 |
429 | $alreadyMultiplied = !empty($oldFields['_were_multiplied'][$field]);
430 |
431 | // мультиплицируем только несовпадающие значения полей
432 | $newValue = $item[$field];
433 | if ($oldValue !== $newValue) {
434 | // если еще не мультиплицировали поле, то его надо превратить в массив.
435 | if (!$alreadyMultiplied) {
436 | $oldFields[$field] = [
437 | $oldFields[$field]
438 | ];
439 | $oldFields['_were_multiplied'][$field] = true;
440 | }
441 |
442 | // добавляем новое значению поле если такого еще нет.
443 | if (empty($oldFields[$field]) || (is_array($oldFields[$field]) && !in_array($newValue, $oldFields[$field]))) {
444 | $oldFields[$field][] = $newValue;
445 | }
446 | }
447 | }
448 |
449 | $results[$keyByValue]->fields = $oldFields;
450 | }
451 | }
452 |
453 | /**
454 | * Determine if all fields must be selected.
455 | *
456 | * @return bool
457 | */
458 | protected function fieldsMustBeSelected()
459 | {
460 | return in_array('FIELDS', $this->select);
461 | }
462 |
463 | /**
464 | * Determine if all fields must be selected.
465 | *
466 | * @return bool
467 | */
468 | protected function propsMustBeSelected()
469 | {
470 | return in_array('PROPS', $this->select)
471 | || in_array('PROPERTIES', $this->select)
472 | || in_array('PROPERTY_VALUES', $this->select);
473 | }
474 |
475 | /**
476 | * Set $array[$new] as $array[$old] and delete $array[$old].
477 | *
478 | * @param array $array
479 | * @param $old
480 | * @param $new
481 | *
482 | * return null
483 | */
484 | protected function substituteField(&$array, $old, $new)
485 | {
486 | if (isset($array[$old]) && !isset($array[$new])) {
487 | $array[$new] = $array[$old];
488 | }
489 |
490 | unset($array[$old]);
491 | }
492 |
493 | /**
494 | * Clear select array from duplication and additional fields.
495 | *
496 | * @return array
497 | */
498 | protected function clearSelectArray()
499 | {
500 | $strip = ['FIELDS', 'PROPS', 'PROPERTIES', 'PROPERTY_VALUES', 'GROUPS', 'GROUP_ID', 'GROUPS_ID'];
501 |
502 | return array_values(array_diff(array_unique($this->select), $strip));
503 | }
504 |
505 | /**
506 | * Store closure's result in the cache for a given number of minutes.
507 | *
508 | * @param string $key
509 | * @param double $minutes
510 | * @param Closure $callback
511 | * @return mixed
512 | */
513 | protected function rememberInCache($key, $minutes, Closure $callback)
514 | {
515 | $minutes = (float) $minutes;
516 | if ($minutes <= 0) {
517 | return $callback();
518 | }
519 |
520 | $cache = Cache::createInstance();
521 | if ($cache->initCache($minutes * 60, $key, '/bitrix-models')) {
522 | $vars = $cache->getVars();
523 | return !empty($vars['isCollection']) ? new Collection($vars['cache']) : $vars['cache'];
524 | }
525 |
526 | $cache->startDataCache();
527 | $result = $callback();
528 |
529 | // Bitrix cache is bad for storing collections. Let's convert it to array.
530 | $isCollection = $result instanceof Collection;
531 | if ($isCollection) {
532 | $result = $result->all();
533 | }
534 |
535 | $cache->endDataCache(['cache' => $result, 'isCollection' => $isCollection]);
536 |
537 | return $isCollection ? new Collection($result) : $result;
538 | }
539 |
540 | protected function handleCacheIfNeeded($cacheKeyParams, Closure $callback)
541 | {
542 | return $this->cacheTtl
543 | ? $this->rememberInCache(md5(json_encode($cacheKeyParams)), $this->cacheTtl, $callback)
544 | : $callback();
545 | }
546 |
547 | /**
548 | * Handle dynamic method calls into the method.
549 | *
550 | * @param string $method
551 | * @param array $parameters
552 | *
553 | * @throws BadMethodCallException
554 | *
555 | * @return $this
556 | */
557 | public function __call($method, $parameters)
558 | {
559 | if (method_exists($this->model, 'scope' . $method)) {
560 | array_unshift($parameters, $this);
561 |
562 | $query = call_user_func_array([$this->model, 'scope' . $method], $parameters);
563 |
564 | if ($query === false) {
565 | $this->stopQuery();
566 | }
567 |
568 | return $query instanceof static ? $query : $this;
569 | }
570 |
571 | $className = get_class($this);
572 |
573 | throw new BadMethodCallException("Call to undefined method {$className}::{$method}()");
574 | }
575 |
576 | protected function prepareMultiFilter(&$key, &$value)
577 | {
578 | }
579 | }
580 |
--------------------------------------------------------------------------------
/src/Queries/BaseRelationQuery.php:
--------------------------------------------------------------------------------
1 | primaryModel]]
43 | * Этот метод вызывается когда релейшн вызывается ленивой загрузкой $model->relation
44 | * @return Collection|BaseBitrixModel[]|BaseBitrixModel - связанные модели
45 | * @throws \Exception
46 | */
47 | public function findFor()
48 | {
49 | return $this->multiple ? $this->getList() : $this->first();
50 | }
51 |
52 | /**
53 | * Определяет связи, которые должны быть загружены при выполнении запроса
54 | *
55 | * Передавая массив можно указать ключем - название релейшена, а значением - коллбек для кастомизации запроса
56 | *
57 | * @param array|string $with - связи, которые необходимо жадно подгрузить
58 | * // Загрузить Customer и сразу для каждой модели подгрузить orders и country
59 | * Customer::query()->with(['orders', 'country'])->getList();
60 | *
61 | * // Загрузить Customer и сразу для каждой модели подгрузить orders, а также для orders загрузить address
62 | * Customer::find()->with('orders.address')->getList();
63 | *
64 | * // Загрузить Customer и сразу для каждой модели подгрузить country и orders (только активные)
65 | * Customer::find()->with([
66 | * 'orders' => function (BaseQuery $query) {
67 | * $query->filter(['ACTIVE' => 'Y']);
68 | * },
69 | * 'country',
70 | * ])->all();
71 | *
72 | * @return $this
73 | */
74 | public function with($with)
75 | {
76 | $with = is_string($with) ? func_get_args() : $with;
77 |
78 | if (empty($this->with)) {
79 | $this->with = $with;
80 | } elseif (!empty($with)) {
81 | foreach ($with as $name => $value) {
82 | if (is_int($name)) {
83 | // дубликаты связей будут устранены в normalizeRelations()
84 | $this->with[] = $value;
85 | } else {
86 | $this->with[$name] = $value;
87 | }
88 | }
89 | }
90 |
91 | return $this;
92 | }
93 |
94 | /**
95 | * Добавить фильтр для загрзуки связи относительно моделей
96 | * @param Collection|BaseBitrixModel[] $models
97 | */
98 | protected function filterByModels($models)
99 | {
100 | $values = [];
101 | foreach ($models as $model) {
102 | if (($value = $model[$this->foreignKey]) !== null) {
103 | if (is_array($value)) {
104 | $values = array_merge($values, $value);
105 | } else {
106 | $values[] = $value;
107 | }
108 | }
109 | }
110 |
111 | $values = array_filter($values);
112 | if (empty($values)) {
113 | $this->stopQuery();
114 | }
115 |
116 | $primary = $this->localKey;
117 | if (preg_match('/^PROPERTY_(.*)_VALUE$/', $primary, $matches) && !empty($matches[1])) {
118 | $primary = 'PROPERTY_' . $matches[1];
119 | }
120 | $values = array_unique($values, SORT_REGULAR);
121 | if (count($values) == 1) {
122 | $values = current($values);
123 | } else {
124 | $this->prepareMultiFilter($primary, $values);
125 | }
126 |
127 | $this->filter([$primary => $values]);
128 | $this->select[] = $primary;
129 | }
130 |
131 | /**
132 | * Подгрузить связанные модели для уже загруденных моделей
133 | * @param array $with - массив релейшенов, которые необходимо подгрузить
134 | * @param Collection|BaseBitrixModel[] $models модели, для которых загружать связи
135 | */
136 | public function findWith($with, &$models)
137 | {
138 | // --- получаем модель, на основании которой будем брать запросы релейшенов
139 | $primaryModel = $models->first();
140 | if (!$primaryModel instanceof BaseBitrixModel) {
141 | $primaryModel = $this->model;
142 | }
143 |
144 | $relations = $this->normalizeRelations($primaryModel, $with);
145 | /* @var $relation BaseQuery */
146 | foreach ($relations as $name => $relation) {
147 | $relation->populateRelation($name, $models);
148 | }
149 | }
150 |
151 | /**
152 | * @param BaseBitrixModel $model - модель пустышка, чтобы получить запросы
153 | * @param array $with
154 | * @return BaseQuery[]
155 | */
156 | private function normalizeRelations($model, $with)
157 | {
158 | $relations = [];
159 | foreach ($with as $name => $callback) {
160 | if (is_int($name)) { // Если ключ - число, значит в значении написано название релейшена
161 | $name = $callback;
162 | $callback = null;
163 | }
164 |
165 | if (($pos = strpos($name, '.')) !== false) { // Если есть точка, значит указан вложенный релейшн
166 | $childName = substr($name, $pos + 1); // Название дочернего релейшена
167 | $name = substr($name, 0, $pos); // Название текущего релейшена
168 | } else {
169 | $childName = null;
170 | }
171 |
172 | if (!isset($relations[$name])) { // Указываем новый релейшн
173 | $relation = $model->getRelation($name); // Берем запрос
174 | $relation->primaryModel = null;
175 | $relations[$name] = $relation;
176 | } else {
177 | $relation = $relations[$name];
178 | }
179 |
180 | if (isset($childName)) {
181 | $relation->with[$childName] = $callback;
182 | } elseif ($callback !== null) {
183 | call_user_func($callback, $relation);
184 | }
185 | }
186 |
187 | return $relations;
188 | }
189 | /**
190 | * Находит связанные записи и заполняет их в первичных моделях.
191 | * @param string $name - имя релейшена
192 | * @param array $primaryModels - первичные модели
193 | * @return Collection|BaseBitrixModel[] - найденные модели
194 | */
195 | public function populateRelation($name, &$primaryModels)
196 | {
197 | $this->filterByModels($primaryModels);
198 |
199 | $models = $this->getList();
200 |
201 | Helpers::assocModels($primaryModels, $models, $this->foreignKey, $this->localKey, $name, $this->multiple);
202 |
203 | return $models;
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/src/Queries/D7Query.php:
--------------------------------------------------------------------------------
1 | bxObject->getClassName();
74 | $queryType = 'D7Query::count';
75 | $filter = $this->filter;
76 |
77 | $callback = function () use ($filter) {
78 | return (int) $this->bxObject->getCount($filter);
79 | };
80 |
81 | return $this->handleCacheIfNeeded(compact('className', 'filter', 'queryType'), $callback);
82 | }
83 |
84 | /**
85 | * Get list of items.
86 | *
87 | * @return Collection
88 | */
89 | protected function loadModels()
90 | {
91 | $params = [
92 | 'select' => $this->select,
93 | 'filter' => $this->filter,
94 | 'group' => $this->group,
95 | 'order' => $this->sort,
96 | 'limit' => $this->limit,
97 | 'offset' => $this->offset,
98 | 'runtime' => $this->runtime,
99 | ];
100 |
101 | if ($this->cacheTtl && $this->cacheJoins) {
102 | $params['cache'] = ['ttl' => $this->cacheTtl, 'cache_joins' => true];
103 | }
104 |
105 | $className = $this->bxObject->getClassName();
106 | $queryType = 'D7Query::getList';
107 | $keyBy = $this->keyBy;
108 |
109 | $callback = function () use ($className, $params) {
110 | $rows = [];
111 | $result = $this->bxObject->getList($params);
112 | while ($row = $result->fetch()) {
113 | $this->addItemToResultsUsingKeyBy($rows, new $this->modelName($row['ID'], $row));
114 | }
115 |
116 | return new Collection($rows);
117 | };
118 |
119 | return $this->handleCacheIfNeeded(compact('className', 'params', 'queryType', 'keyBy'), $callback);
120 | }
121 |
122 | /**
123 | * Setter for limit.
124 | *
125 | * @param int|null $value
126 | * @return $this
127 | */
128 | public function limit($value)
129 | {
130 | $this->limit = $value;
131 |
132 | return $this;
133 | }
134 |
135 | /**
136 | * Setter for offset.
137 | *
138 | * @param int|null $value
139 | * @return $this
140 | */
141 | public function offset($value)
142 | {
143 | $this->offset = $value;
144 |
145 | return $this;
146 | }
147 |
148 | /**
149 | * Set the "page number" value of the query.
150 | *
151 | * @param int $num
152 | * @return $this
153 | */
154 | public function page($num)
155 | {
156 | return $this->offset((int) $this->limit * ($num - 1));
157 | }
158 |
159 | /**
160 | * Setter for offset.
161 | *
162 | * @param array|\Bitrix\Main\Entity\ExpressionField $fields
163 | * @return $this
164 | */
165 | public function runtime($fields)
166 | {
167 | $this->runtime = is_array($fields) ? $fields : [$fields];
168 |
169 | return $this;
170 | }
171 |
172 | /**
173 | * Setter for cacheJoins.
174 | *
175 | * @param bool $value
176 | * @return $this
177 | */
178 | public function cacheJoins($value = true)
179 | {
180 | $this->cacheJoins = $value;
181 |
182 | return $this;
183 | }
184 |
185 | public function enableDataDoubling()
186 | {
187 | $this->dataDoubling = true;
188 |
189 | return $this;
190 | }
191 |
192 | public function disableDataDoubling()
193 | {
194 | $this->dataDoubling = false;
195 |
196 | return $this;
197 | }
198 |
199 | /**
200 | * For testing.
201 | *
202 | * @param $bxObject
203 | * @return $this
204 | */
205 | public function setAdapter($bxObject)
206 | {
207 | $this->bxObject = $bxObject;
208 |
209 | return $this;
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/Queries/ElementQuery.php:
--------------------------------------------------------------------------------
1 | 'ASC'];
32 |
33 | /**
34 | * Query group by.
35 | *
36 | * @var array
37 | */
38 | public $groupBy = false;
39 |
40 | /**
41 | * Iblock id.
42 | *
43 | * @var int
44 | */
45 | protected $iblockId;
46 |
47 | /**
48 | * Iblock version.
49 | *
50 | * @var int
51 | */
52 | protected $iblockVersion;
53 |
54 | /**
55 | * List of standard entity fields.
56 | *
57 | * @var array
58 | */
59 | protected $standardFields = [
60 | 'ID',
61 | 'TIMESTAMP_X',
62 | 'TIMESTAMP_X_UNIX',
63 | 'MODIFIED_BY',
64 | 'DATE_CREATE',
65 | 'DATE_CREATE_UNIX',
66 | 'CREATED_BY',
67 | 'IBLOCK_ID',
68 | 'IBLOCK_SECTION_ID',
69 | 'ACTIVE',
70 | 'ACTIVE_FROM',
71 | 'ACTIVE_TO',
72 | 'SORT',
73 | 'NAME',
74 | 'PREVIEW_PICTURE',
75 | 'PREVIEW_TEXT',
76 | 'PREVIEW_TEXT_TYPE',
77 | 'DETAIL_PICTURE',
78 | 'DETAIL_TEXT',
79 | 'DETAIL_TEXT_TYPE',
80 | 'SEARCHABLE_CONTENT',
81 | 'IN_SECTIONS',
82 | 'SHOW_COUNTER',
83 | 'SHOW_COUNTER_START',
84 | 'CODE',
85 | 'TAGS',
86 | 'XML_ID',
87 | 'EXTERNAL_ID',
88 | 'TMP_ID',
89 | 'CREATED_USER_NAME',
90 | 'DETAIL_PAGE_URL',
91 | 'LIST_PAGE_URL',
92 | 'CREATED_DATE',
93 | ];
94 |
95 | /**
96 | * Constructor.
97 | *
98 | * @param object $bxObject
99 | * @param string $modelName
100 | */
101 | public function __construct($bxObject, $modelName)
102 | {
103 | static::instantiateCIblockObject();
104 | parent::__construct($bxObject, $modelName);
105 |
106 | $this->iblockId = $modelName::iblockId();
107 | $this->iblockVersion = $modelName::IBLOCK_VERSION ?: 2;
108 | }
109 |
110 | /**
111 | * Instantiate bitrix entity object.
112 | *
113 | * @throws Exception
114 | *
115 | * @return object
116 | */
117 | public static function instantiateCIblockObject()
118 | {
119 | if (static::$cIblockObject) {
120 | return static::$cIblockObject;
121 | }
122 |
123 | if (class_exists('CIBlock')) {
124 | return static::$cIblockObject = new CIBlock();
125 | }
126 |
127 | throw new Exception('CIblock object initialization failed');
128 | }
129 |
130 | /**
131 | * Setter for groupBy.
132 | *
133 | * @param $value
134 | *
135 | * @return $this
136 | */
137 | public function groupBy($value)
138 | {
139 | $this->groupBy = $value;
140 |
141 | return $this;
142 | }
143 |
144 | /**
145 | * Get list of items.
146 | *
147 | * @return Collection
148 | */
149 | protected function loadModels()
150 | {
151 | $sort = $this->sort;
152 | $filter = $this->normalizeFilter();
153 | $groupBy = $this->groupBy;
154 | $navigation = $this->navigation;
155 | $select = $this->normalizeSelect();
156 | $queryType = 'ElementQuery::getList';
157 | $fetchUsing = $this->fetchUsing;
158 | $keyBy = $this->keyBy;
159 | list($select, $chunkQuery) = $this->multiplySelectForMaxJoinsRestrictionIfNeeded($select);
160 |
161 | $callback = function () use ($sort, $filter, $groupBy, $navigation, $select, $chunkQuery) {
162 | if ($chunkQuery) {
163 | $itemsChunks = [];
164 | foreach ($select as $chunkIndex => $selectForChunk) {
165 | $rsItems = $this->bxObject->GetList($sort, $filter, $groupBy, $navigation, $selectForChunk);
166 | while ($arItem = $this->performFetchUsingSelectedMethod($rsItems)) {
167 | $this->addItemToResultsUsingKeyBy($itemsChunks[$chunkIndex], new $this->modelName($arItem['ID'], $arItem));
168 | }
169 | }
170 |
171 | $items = $this->mergeChunks($itemsChunks);
172 | } else {
173 | $items = [];
174 | $rsItems = $this->bxObject->GetList($sort, $filter, $groupBy, $navigation, $select);
175 | while ($arItem = $this->performFetchUsingSelectedMethod($rsItems)) {
176 | $this->addItemToResultsUsingKeyBy($items, new $this->modelName($arItem['ID'], $arItem));
177 | }
178 | }
179 | return new Collection($items);
180 | };
181 |
182 | $cacheKeyParams = compact('sort', 'filter', 'groupBy', 'navigation', 'select', 'queryType', 'keyBy', 'fetchUsing');
183 |
184 | return $this->handleCacheIfNeeded($cacheKeyParams, $callback);
185 | }
186 |
187 | /**
188 | * Get the first element with a given code.
189 | *
190 | * @param string $code
191 | *
192 | * @return ElementModel
193 | */
194 | public function getByCode($code)
195 | {
196 | $this->filter['=CODE'] = $code;
197 |
198 | return $this->first();
199 | }
200 |
201 | /**
202 | * Get the first element with a given external id.
203 | *
204 | * @param string $id
205 | *
206 | * @return ElementModel
207 | */
208 | public function getByExternalId($id)
209 | {
210 | $this->filter['EXTERNAL_ID'] = $id;
211 |
212 | return $this->first();
213 | }
214 |
215 | /**
216 | * Get count of elements that match $filter.
217 | *
218 | * @return int
219 | */
220 | public function count()
221 | {
222 | if ($this->queryShouldBeStopped) {
223 | return 0;
224 | }
225 |
226 | $filter = $this->normalizeFilter();
227 | $queryType = "ElementQuery::count";
228 |
229 | $callback = function () use ($filter) {
230 | return (int) $this->bxObject->GetList(false, $filter, []);
231 | };
232 |
233 | return $this->handleCacheIfNeeded(compact('filter', 'queryType'), $callback);
234 | }
235 |
236 | // /**
237 | // * Normalize properties's format converting it to 'PROPERTY_"CODE"_VALUE'.
238 | // *
239 | // * @param array $fields
240 | // *
241 | // * @return null
242 | // */
243 | // protected function normalizePropertyResultFormat(&$fields)
244 | // {
245 | // if (empty($fields['PROPERTIES'])) {
246 | // return;
247 | // }
248 | //
249 | // foreach ($fields['PROPERTIES'] as $code => $prop) {
250 | // $fields['PROPERTY_'.$code.'_VALUE'] = $prop['VALUE'];
251 | // $fields['~PROPERTY_'.$code.'_VALUE'] = $prop['~VALUE'];
252 | // $fields['PROPERTY_'.$code.'_DESCRIPTION'] = $prop['DESCRIPTION'];
253 | // $fields['~PROPERTY_'.$code.'_DESCRIPTION'] = $prop['~DESCRIPTION'];
254 | // $fields['PROPERTY_'.$code.'_VALUE_ID'] = $prop['PROPERTY_VALUE_ID'];
255 | // if (isset($prop['VALUE_ENUM_ID'])) {
256 | // $fields['PROPERTY_'.$code.'_ENUM_ID'] = $prop['VALUE_ENUM_ID'];
257 | // }
258 | // }
259 | // }
260 |
261 | /**
262 | * Normalize filter before sending it to getList.
263 | * This prevents some inconsistency.
264 | *
265 | * @return array
266 | */
267 | protected function normalizeFilter()
268 | {
269 | $this->filter['IBLOCK_ID'] = $this->iblockId;
270 |
271 | return $this->filter;
272 | }
273 |
274 | /**
275 | * Normalize select before sending it to getList.
276 | * This prevents some inconsistency.
277 | *
278 | * @return array
279 | */
280 | protected function normalizeSelect()
281 | {
282 | if ($this->fieldsMustBeSelected()) {
283 | $this->select = array_merge($this->standardFields, $this->select);
284 | }
285 |
286 | $this->select[] = 'ID';
287 | $this->select[] = 'IBLOCK_ID';
288 |
289 | return $this->clearSelectArray();
290 | }
291 |
292 | /**
293 | * Fetch all iblock property codes from database
294 | *
295 | * return array
296 | */
297 | protected function fetchAllPropsForSelect()
298 | {
299 | $props = [];
300 | $rsProps = static::$cIblockObject->GetProperties($this->iblockId);
301 | while ($prop = $rsProps->Fetch()) {
302 | $props[] = 'PROPERTY_' . $prop['CODE'];
303 | }
304 |
305 | return $props;
306 | }
307 |
308 | protected function multiplySelectForMaxJoinsRestrictionIfNeeded($select)
309 | {
310 | if (!$this->propsMustBeSelected()) {
311 | return [$select, false];
312 | }
313 |
314 | $chunkSize = 20;
315 | $props = $this->fetchAllPropsForSelect();
316 | if ($this->iblockVersion !== 1 || (count($props) <= $chunkSize)) {
317 | return [array_merge($select, $props), false];
318 | }
319 |
320 | // начинаем формировать селекты из свойств
321 | $multipleSelect = array_chunk($props, $chunkSize);
322 |
323 | // добавляем в каждый селект поля "несвойства"
324 | foreach ($multipleSelect as $i => $partOfProps) {
325 | $multipleSelect[$i] = array_merge($select, $partOfProps);
326 | }
327 |
328 | return [$multipleSelect, true];
329 | }
330 |
331 | protected function mergeChunks($chunks)
332 | {
333 | $items = [];
334 | foreach ($chunks as $chunk) {
335 | foreach ($chunk as $k => $item) {
336 | if (isset($items[$k])) {
337 | $item->fields['_were_multiplied'] = array_merge((array) $items[$k]->fields['_were_multiplied'], (array) $item->fields['_were_multiplied']);
338 | $items[$k]->fields = (array) $item->fields + (array) $items[$k]->fields;
339 | } else {
340 | $items[$k] = $item;
341 | }
342 | }
343 | }
344 |
345 | return $items;
346 | }
347 | }
348 |
--------------------------------------------------------------------------------
/src/Queries/OldCoreQuery.php:
--------------------------------------------------------------------------------
1 | fetchUsing($modelName::$fetchUsing);
32 | }
33 |
34 | /**
35 | * Set fetch using from string or array.
36 | *
37 | * @param string|array $methodAndParams
38 | * @return $this
39 | */
40 | public function fetchUsing($methodAndParams)
41 | {
42 | // simple case
43 | if (is_string($methodAndParams) || empty($methodAndParams['method'])) {
44 | $this->fetchUsing = in_array($methodAndParams, ['GetNext', 'getNext'])
45 | ? ['method' => 'GetNext', 'params' => [true, true]]
46 | : ['method' => 'Fetch'];
47 |
48 | return $this;
49 | }
50 |
51 | // complex case
52 | if (in_array($methodAndParams['method'], ['GetNext', 'getNext'])) {
53 | $bTextHtmlAuto = isset($methodAndParams['params'][0]) ? $methodAndParams['params'][0] : true;
54 | $useTilda = isset($methodAndParams['params'][1]) ? $methodAndParams['params'][1] : true;
55 | $this->fetchUsing = ['method' => 'GetNext', 'params' => [$bTextHtmlAuto, $useTilda]];
56 | } else {
57 | $this->fetchUsing = ['method' => 'Fetch'];
58 | }
59 |
60 | return $this;
61 | }
62 |
63 | /**
64 | * Choose between Fetch() and GetNext($bTextHtmlAuto, $useTilda) and then fetch
65 | *
66 | * @param \CDBResult $rsItems
67 | * @return array|false
68 | */
69 | protected function performFetchUsingSelectedMethod($rsItems)
70 | {
71 | return $this->fetchUsing['method'] === 'GetNext'
72 | ? $rsItems->GetNext($this->fetchUsing['params'][0], $this->fetchUsing['params'][1])
73 | : $rsItems->Fetch();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/Queries/SectionQuery.php:
--------------------------------------------------------------------------------
1 | 'ASC'];
20 |
21 | /**
22 | * Query bIncCnt.
23 | * This is sent to getList directly.
24 | *
25 | * @var array|false
26 | */
27 | public $countElements = false;
28 |
29 | /**
30 | * Iblock id.
31 | *
32 | * @var int
33 | */
34 | protected $iblockId;
35 |
36 | /**
37 | * List of standard entity fields.
38 | *
39 | * @var array
40 | */
41 | protected $standardFields = [
42 | 'ID',
43 | 'CODE',
44 | 'EXTERNAL_ID',
45 | 'IBLOCK_ID',
46 | 'IBLOCK_SECTION_ID',
47 | 'TIMESTAMP_X',
48 | 'SORT',
49 | 'NAME',
50 | 'ACTIVE',
51 | 'GLOBAL_ACTIVE',
52 | 'PICTURE',
53 | 'DESCRIPTION',
54 | 'DESCRIPTION_TYPE',
55 | 'LEFT_MARGIN',
56 | 'RIGHT_MARGIN',
57 | 'DEPTH_LEVEL',
58 | 'SEARCHABLE_CONTENT',
59 | 'SECTION_PAGE_URL',
60 | 'MODIFIED_BY',
61 | 'DATE_CREATE',
62 | 'CREATED_BY',
63 | 'DETAIL_PICTURE',
64 | ];
65 |
66 | /**
67 | * Constructor.
68 | *
69 | * @param object $bxObject
70 | * @param string $modelName
71 | */
72 | public function __construct($bxObject, $modelName)
73 | {
74 | parent::__construct($bxObject, $modelName);
75 |
76 | $this->iblockId = $modelName::iblockId();
77 | }
78 |
79 | /**
80 | * CIBlockSection::getList substitution.
81 | *
82 | * @return Collection
83 | */
84 | protected function loadModels()
85 | {
86 | $queryType = 'SectionQuery::getList';
87 | $sort = $this->sort;
88 | $filter = $this->normalizeFilter();
89 | $countElements = $this->countElements;
90 | $select = $this->normalizeSelect();
91 | $navigation = $this->navigation;
92 | $keyBy = $this->keyBy;
93 |
94 | $callback = function () use ($sort, $filter, $countElements, $select, $navigation) {
95 | $sections = [];
96 | $rsSections = $this->bxObject->getList($sort, $filter, $countElements, $select, $navigation);
97 | while ($arSection = $this->performFetchUsingSelectedMethod($rsSections)) {
98 |
99 | // Если передать nPageSize, то Битрикс почему-то перестает десериализовать множественные свойсвта...
100 | // Проверим это еще раз, и если есть проблемы то пофиксим.
101 | foreach ($arSection as $field => $value) {
102 | if (
103 | is_string($value)
104 | && Helpers::startsWith($value, 'a:')
105 | && (Helpers::startsWith($field, 'UF_') || Helpers::startsWith($field, '~UF_'))
106 | ) {
107 | $unserializedValue = @unserialize($value);
108 | $arSection[$field] = $unserializedValue === false ? $value : $unserializedValue;
109 | }
110 | }
111 |
112 | $this->addItemToResultsUsingKeyBy($sections, new $this->modelName($arSection['ID'], $arSection));
113 | }
114 |
115 | return new Collection($sections);
116 | };
117 |
118 | $cacheParams = compact('queryType', 'sort', 'filter', 'countElements', 'select', 'navigation', 'keyBy');
119 |
120 | return $this->handleCacheIfNeeded($cacheParams, $callback);
121 | }
122 |
123 | /**
124 | * Get the first section with a given code.
125 | *
126 | * @param string $code
127 | *
128 | * @return SectionModel
129 | */
130 | public function getByCode($code)
131 | {
132 | $this->filter['=CODE'] = $code;
133 |
134 | return $this->first();
135 | }
136 |
137 | /**
138 | * Get the first section with a given external id.
139 | *
140 | * @param string $id
141 | *
142 | * @return SectionModel
143 | */
144 | public function getByExternalId($id)
145 | {
146 | $this->filter['EXTERNAL_ID'] = $id;
147 |
148 | return $this->first();
149 | }
150 |
151 | /**
152 | * Get count of sections that match filter.
153 | *
154 | * @return int
155 | */
156 | public function count()
157 | {
158 | if ($this->queryShouldBeStopped) {
159 | return 0;
160 | }
161 |
162 | $queryType = 'SectionQuery::count';
163 | $filter = $this->normalizeFilter();
164 | $callback = function () use ($filter) {
165 | return (int) $this->bxObject->getCount($filter);
166 | };
167 |
168 | return $this->handleCacheIfNeeded(compact('queryType', 'filter'), $callback);
169 | }
170 |
171 | /**
172 | * Setter for countElements.
173 | *
174 | * @param $value
175 | *
176 | * @return $this
177 | */
178 | public function countElements($value)
179 | {
180 | $this->countElements = $value;
181 |
182 | return $this;
183 | }
184 |
185 | /**
186 | * Normalize filter before sending it to getList.
187 | * This prevents some inconsistency.
188 | *
189 | * @return array
190 | */
191 | protected function normalizeFilter()
192 | {
193 | $this->filter['IBLOCK_ID'] = $this->iblockId;
194 |
195 | return $this->filter;
196 | }
197 |
198 | /**
199 | * Normalize select before sending it to getList.
200 | * This prevents some inconsistency.
201 | *
202 | * @return array
203 | */
204 | protected function normalizeSelect()
205 | {
206 | if ($this->fieldsMustBeSelected()) {
207 | $this->select = array_merge($this->standardFields, $this->select);
208 | }
209 |
210 | if ($this->propsMustBeSelected()) {
211 | $this->select[] = 'IBLOCK_ID';
212 | $this->select[] = 'UF_*';
213 | }
214 |
215 | $this->select[] = 'ID';
216 |
217 | return $this->clearSelectArray();
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/src/Queries/UserQuery.php:
--------------------------------------------------------------------------------
1 | 'asc'];
20 |
21 | /**
22 | * List of standard entity fields.
23 | *
24 | * @var array
25 | */
26 | protected $standardFields = [
27 | 'ID',
28 | 'IS_ONLINE',
29 | 'LAST_ACTIVITY_DATE',
30 | 'AUTO_TIME_ZONE',
31 | 'TIME_ZONE',
32 | 'CONFIRM_CODE',
33 | 'STORED_HASH',
34 | 'EXTERNAL_AUTH_ID',
35 | 'LOGIN_ATTEMPTS',
36 | 'CHECKWORD',
37 | 'CHECKWORD_TIME',
38 | 'DATE_REGISTER',
39 | 'TIMESTAMP_X',
40 | 'LAST_LOGIN',
41 | 'ACTIVE',
42 | 'BLOCKED',
43 | 'TITLE',
44 | 'NAME',
45 | 'LAST_NAME',
46 | 'SECOND_NAME',
47 | 'EMAIL',
48 | 'LOGIN',
49 | 'PHONE_NUMBER',
50 | 'PASSWORD',
51 | 'XML_ID',
52 | 'LID',
53 | 'LANGUAGE_ID',
54 | 'PERSONAL_PROFESSION',
55 | 'PERSONAL_WWW',
56 | 'PERSONAL_ICQ',
57 | 'PERSONAL_GENDER',
58 | 'PERSONAL_BIRTHDAY',
59 | 'PERSONAL_PHOTO',
60 | 'PERSONAL_PHONE',
61 | 'PERSONAL_FAX',
62 | 'PERSONAL_MOBILE',
63 | 'PERSONAL_PAGER',
64 | 'PERSONAL_COUNTRY',
65 | 'PERSONAL_STATE',
66 | 'PERSONAL_CITY',
67 | 'PERSONAL_ZIP',
68 | 'PERSONAL_STREET',
69 | 'PERSONAL_MAILBOX',
70 | 'PERSONAL_NOTES',
71 | 'WORK_COMPANY',
72 | 'WORK_WWW',
73 | 'WORK_DEPARTMENT',
74 | 'WORK_POSITION',
75 | 'WORK_PROFILE',
76 | 'WORK_LOGO',
77 | 'WORK_PHONE',
78 | 'WORK_FAX',
79 | 'WORK_PAGER',
80 | 'WORK_COUNTRY',
81 | 'WORK_STATE',
82 | 'WORK_CITY',
83 | 'WORK_ZIP',
84 | 'WORK_STREET',
85 | 'WORK_MAILBOX',
86 | 'WORK_NOTES',
87 | 'ADMIN_NOTES',
88 | ];
89 |
90 | /**
91 | * Get the collection of users according to the current query.
92 | *
93 | * @return Collection
94 | */
95 | protected function loadModels()
96 | {
97 | $queryType = 'UserQuery::getList';
98 | $sort = $this->sort;
99 | $filter = $this->normalizeFilter();
100 | $params = [
101 | 'SELECT' => $this->propsMustBeSelected() ? ['UF_*'] : ($this->normalizeUfSelect() ?: false),
102 | 'NAV_PARAMS' => $this->navigation,
103 | 'FIELDS' => $this->normalizeSelect(),
104 | ];
105 | $selectGroups = $this->groupsMustBeSelected();
106 | $keyBy = $this->keyBy;
107 |
108 | $callback = function () use ($sort, $filter, $params, $selectGroups) {
109 | $users = [];
110 | $rsUsers = $this->bxObject->getList($sort, $sortOrder = false, $filter, $params);
111 | while ($arUser = $this->performFetchUsingSelectedMethod($rsUsers)) {
112 | if ($selectGroups) {
113 | $arUser['GROUP_ID'] = $this->bxObject->getUserGroup($arUser['ID']);
114 | }
115 |
116 | $this->addItemToResultsUsingKeyBy($users, new $this->modelName($arUser['ID'], $arUser));
117 | }
118 |
119 | return new Collection($users);
120 | };
121 |
122 | return $this->handleCacheIfNeeded(compact('queryType', 'sort', 'filter', 'params', 'selectGroups', 'keyBy'), $callback);
123 | }
124 |
125 | /**
126 | * Get the first user with a given login.
127 | *
128 | * @param string $login
129 | *
130 | * @return UserModel
131 | */
132 | public function getByLogin($login)
133 | {
134 | $this->filter['LOGIN_EQUAL_EXACT'] = $login;
135 |
136 | return $this->first();
137 | }
138 |
139 | /**
140 | * Get the first user with a given email.
141 | *
142 | * @param string $email
143 | *
144 | * @return UserModel
145 | */
146 | public function getByEmail($email)
147 | {
148 | $this->filter['EMAIL'] = $email;
149 |
150 | return $this->first();
151 | }
152 |
153 | /**
154 | * Get count of users according the current query.
155 | *
156 | * @return int
157 | */
158 | public function count()
159 | {
160 | if ($this->queryShouldBeStopped) {
161 | return 0;
162 | }
163 |
164 | $queryType = 'UserQuery::count';
165 | $filter = $this->normalizeFilter();
166 | $callback = function () use ($filter) {
167 | return (int) $this->bxObject->getList($order = 'ID', $by = 'ASC', $filter, [
168 | 'NAV_PARAMS' => [
169 | 'nTopCount' => 0,
170 | ],
171 | ])->NavRecordCount;
172 | };
173 |
174 | return $this->handleCacheIfNeeded(compact('queryType', 'filter'), $callback);
175 | }
176 |
177 | /**
178 | * Determine if groups must be selected.
179 | *
180 | * @return bool
181 | */
182 | protected function groupsMustBeSelected()
183 | {
184 | return in_array('GROUPS', $this->select) || in_array('GROUP_ID', $this->select) || in_array('GROUPS_ID', $this->select);
185 | }
186 |
187 | /**
188 | * Normalize filter before sending it to getList.
189 | * This prevents some inconsistency.
190 | *
191 | * @return array
192 | */
193 | protected function normalizeFilter()
194 | {
195 | $this->substituteField($this->filter, 'GROUPS', 'GROUPS_ID');
196 | $this->substituteField($this->filter, 'GROUP_ID', 'GROUPS_ID');
197 |
198 | return $this->filter;
199 | }
200 |
201 | /**
202 | * Normalize select before sending it to getList.
203 | * This prevents some inconsistency.
204 | *
205 | * @return array
206 | */
207 | protected function normalizeSelect()
208 | {
209 | if ($this->fieldsMustBeSelected()) {
210 | $this->select = array_merge($this->standardFields, $this->select);
211 | }
212 |
213 | $this->select[] = 'ID';
214 |
215 | return $this->clearSelectArray();
216 | }
217 |
218 | /**
219 | * Normalize select UF before sending it to getList.
220 | *
221 | * @return array
222 | */
223 | protected function normalizeUfSelect()
224 | {
225 | return preg_grep('/^(UF_+)/', $this->select);
226 | }
227 |
228 | protected function prepareMultiFilter(&$key, &$value)
229 | {
230 | $value = join(' | ', $value);
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | addEventHandler('main', 'OnAfterEpilog', [IlluminateQueryDebugger::class, 'onAfterEpilogHandler']);
46 | }
47 |
48 | static::addEventListenersForHelpersHighloadblockTables($capsule);
49 | }
50 |
51 | /**
52 | * Bootstrap illuminate/pagination
53 | */
54 | protected static function bootstrapIlluminatePagination()
55 | {
56 | if (class_exists(BladeProvider::class)) {
57 | Paginator::viewFactoryResolver(function () {
58 | return BladeProvider::getViewFactory();
59 | });
60 | }
61 |
62 | Paginator::$defaultView = 'pagination.default';
63 | Paginator::$defaultSimpleView = 'pagination.simple-default';
64 |
65 | Paginator::currentPathResolver(function () {
66 | return $GLOBALS['APPLICATION']->getCurPage();
67 | });
68 |
69 | Paginator::currentPageResolver(function ($pageName = 'page') {
70 | $page = $_GET[$pageName];
71 |
72 | if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int)$page >= 1) {
73 | return $page;
74 | }
75 |
76 | return 1;
77 | });
78 | }
79 |
80 | /**
81 | * Bootstrap illuminate/database
82 | * @return Capsule
83 | */
84 | protected static function bootstrapIlluminateDatabase()
85 | {
86 | $capsule = new Capsule(self::instantiateServiceContainer());
87 |
88 | if ($dbConfig = Configuration::getInstance()->get('bitrix-models.illuminate-database')) {
89 | foreach ($dbConfig['connections'] as $name => $connection) {
90 | $capsule->addConnection($connection, $name);
91 | }
92 |
93 | $capsule->getDatabaseManager()->setDefaultConnection((isset($dbConfig['default'])) ? $dbConfig['default'] : 'default');
94 | } else {
95 | $config = self::getBitrixDbConfig();
96 |
97 | $capsule->addConnection([
98 | 'driver' => 'mysql',
99 | 'host' => $config['host'],
100 | 'database' => $config['database'],
101 | 'username' => $config['login'],
102 | 'password' => $config['password'],
103 | 'charset' => 'utf8',
104 | 'collation' => 'utf8_unicode_ci',
105 | 'prefix' => '',
106 | 'strict' => false,
107 | ]);
108 | }
109 |
110 | if (class_exists(Dispatcher::class)) {
111 | $capsule->setEventDispatcher(new Dispatcher());
112 | }
113 |
114 | $capsule->setAsGlobal();
115 | $capsule->bootEloquent();
116 |
117 | static::$illuminateDatabaseIsUsed = true;
118 |
119 | return $capsule;
120 | }
121 |
122 | /**
123 | * Instantiate service container if it's not instantiated yet.
124 | */
125 | protected static function instantiateServiceContainer()
126 | {
127 | $container = Container::getInstance();
128 |
129 | if (!$container) {
130 | $container = new Container();
131 | Container::setInstance($container);
132 | }
133 |
134 | return $container;
135 | }
136 |
137 | /**
138 | * Get bitrix database configuration array.
139 | *
140 | * @return array
141 | */
142 | protected static function getBitrixDbConfig()
143 | {
144 | $config = Configuration::getInstance();
145 | $connections = $config->get('connections');
146 |
147 | return $connections['default'];
148 | }
149 |
150 | /**
151 | * Для множественных полей Highload блоков битрикс использует вспомогательные таблицы.
152 | * Данный метод вешает обработчики на eloquent события добавления и обновления записей которые будут актуализировать и эти таблицы.
153 | *
154 | * @param Capsule $capsule
155 | */
156 | private static function addEventListenersForHelpersHighloadblockTables(Capsule $capsule)
157 | {
158 | $dispatcher = $capsule->getEventDispatcher();
159 | if (!$dispatcher) {
160 | return;
161 | }
162 |
163 | $dispatcher->listen(['eloquent.deleted: *'], function ($event, $payload) {
164 | /** @var EloquentModel $model */
165 | $model = $payload[0];
166 | if (empty($model->multipleHighloadBlockFields)) {
167 | return;
168 | }
169 |
170 | $modelTable = $model->getTable();
171 | foreach ($model->multipleHighloadBlockFields as $multipleHighloadBlockField) {
172 | if (!empty($model['ID'])) {
173 | $tableName = $modelTable . '_' . strtolower($multipleHighloadBlockField);
174 | DB::table($tableName)->where('ID', $model['ID'])->delete();
175 | }
176 | }
177 | });
178 |
179 | $dispatcher->listen(['eloquent.updated: *', 'eloquent.created: *'], function ($event, $payload) {
180 | /** @var EloquentModel $model */
181 | $model = $payload[0];
182 | if (empty($model->multipleHighloadBlockFields)) {
183 | return;
184 | }
185 |
186 | $dirty = $model->getDirty();
187 | $modelTable = $model->getTable();
188 | foreach ($model->multipleHighloadBlockFields as $multipleHighloadBlockField) {
189 | if (isset($dirty[$multipleHighloadBlockField]) && !empty($model['ID'])) {
190 | $tableName = $modelTable . '_' . strtolower($multipleHighloadBlockField);
191 |
192 | if (substr($event, 0, 16) === 'eloquent.updated') {
193 | DB::table($tableName)->where('ID', $model['ID'])->delete();
194 | }
195 |
196 | $unserializedValues = unserialize($dirty[$multipleHighloadBlockField]);
197 | if (!$unserializedValues) {
198 | continue;
199 | }
200 |
201 | $newRows = [];
202 | foreach ($unserializedValues as $unserializedValue) {
203 | $newRows[] = [
204 | 'ID' => $model['ID'],
205 | 'VALUE' => $unserializedValue,
206 | ];
207 | }
208 |
209 | if ($newRows) {
210 | DB::table($tableName)->insert($newRows);
211 | }
212 | }
213 | }
214 | });
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/tests/D7ModelTest.php:
--------------------------------------------------------------------------------
1 | assertSame(1, $element->id);
16 |
17 | $fields = [
18 | 'UF_EMAIL' => 'John',
19 | 'UF_IMAGE_ID' => '1',
20 | ];
21 | $element = new TestD7Element(1, $fields);
22 | $this->assertSame(1, $element->id);
23 | $this->assertSame($fields, $element->fields);
24 | }
25 |
26 | public function testMultipleInitialization()
27 | {
28 | // 1
29 | $element = new TestD7Element(1);
30 | $this->assertSame(1, $element->id);
31 |
32 | $fields = [
33 | 'UF_EMAIL' => 'John',
34 | 'UF_IMAGE_ID' => '1',
35 | ];
36 | $element = new TestD7Element(1, $fields);
37 | $this->assertSame(1, $element->id);
38 | $this->assertSame($fields, $element->fields);
39 |
40 | // 2
41 | $element2 = new TestD7Element2(1);
42 | $this->assertSame(1, $element2->id);
43 |
44 | $fields = [
45 | 'UF_EMAIL' => 'John',
46 | 'UF_IMAGE_ID' => '1',
47 | ];
48 | $element2 = new TestD7Element2(1, $fields);
49 | $this->assertSame(1, $element2->id);
50 | $this->assertSame($fields, $element2->fields);
51 |
52 | // dd([TestD7Element::cachedTableClass(), TestD7Element2::cachedTableClass()]);
53 | $this->assertTrue(TestD7Element::cachedTableClass() !== TestD7Element2::cachedTableClass());
54 | $this->assertTrue(TestD7Element::instantiateAdapter() !== TestD7Element2::instantiateAdapter());
55 | }
56 |
57 | public function testAdd()
58 | {
59 | $resultObject = new TestD7ResultObject();
60 | $adapter = m::mock('adapter');
61 | $adapter->shouldReceive('add')->once()->with(['UF_NAME' => 'Jane', 'UF_AGE' => '18'])->andReturn($resultObject);
62 |
63 | TestD7Element::setAdapter($adapter);
64 | $element = TestD7Element::create(['UF_NAME' => 'Jane', 'UF_AGE' => '18']);
65 | $this->assertEquals($element->id, 1);
66 | $this->assertEquals($element->fields, ['UF_NAME' => 'Jane', 'UF_AGE' => '18', 'ID' => '1']);
67 | }
68 |
69 | public function testUpdate()
70 | {
71 | $resultObject = new TestD7ResultObject();
72 | $adapter = m::mock('adapter');
73 | $adapter->shouldReceive('update')->once()->with(1, ['UF_NAME' => 'Jane'])->andReturn($resultObject);
74 |
75 | $element = new TestD7Element(1);
76 | TestD7Element::setAdapter($adapter);
77 |
78 |
79 | $this->assertTrue($element->update(['UF_NAME' => 'Jane']));
80 | }
81 |
82 | public function testDelete()
83 | {
84 | // normal
85 | $resultObject = new TestD7ResultObject();
86 | $adapter = m::mock('adapter');
87 | $adapter->shouldReceive('delete')->once()->with(1)->andReturn($resultObject);
88 |
89 | $element = m::mock('Arrilot\Tests\BitrixModels\Stubs\TestD7Element[onAfterDelete, onBeforeDelete]', [1])
90 | ->shouldAllowMockingProtectedMethods();
91 | $element::setAdapter($adapter);
92 | $element->shouldReceive('onBeforeDelete')->once()->andReturn(null);
93 | $element->shouldReceive('onAfterDelete')->once()->with(true);
94 |
95 | $this->assertTrue($element->delete());
96 |
97 | // cancelled
98 | $element = m::mock('Arrilot\Tests\BitrixModels\Stubs\TestD7Element[onAfterDelete, onBeforeDelete]', [1])
99 | ->shouldAllowMockingProtectedMethods();
100 | $element->shouldReceive('onBeforeDelete')->once()->andReturn(false);
101 | $element->shouldReceive('onAfterDelete')->never();
102 | $this->assertFalse($element->delete());
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tests/D7QueryTest.php:
--------------------------------------------------------------------------------
1 | setAdapter($adapter);
26 | }
27 |
28 | public function testCount()
29 | {
30 | $adapter = m::mock('D7Adapter');
31 | $adapter->shouldReceive('getClassName')->once()->andReturn('TestD7ClassName');
32 | $adapter->shouldReceive('getCount')->once()->andReturn(6);
33 |
34 | $query = $this->createQuery($adapter);
35 | $count = $query->count();
36 | $this->assertSame(6, $count);
37 |
38 | $adapter = m::mock('D7Adapter');
39 | $adapter->shouldReceive('getClassName')->once()->andReturn('TestD7ClassName');
40 | $adapter->shouldReceive('getCount')->with(['>ID' => 5])->once()->andReturn(3);
41 |
42 | $query = $this->createQuery($adapter);
43 | $count = $query->filter(['>ID' => 5])->count();
44 | $this->assertSame(3, $count);
45 | }
46 |
47 | public function testGetList()
48 | {
49 | $adapter = m::mock('D7Adapter');
50 | $params = [
51 | 'select' => ['ID', 'UF_NAME'],
52 | 'filter' => ['UF_NAME' => 'John'],
53 | 'group' => [],
54 | 'order' => ['ID' => 'ASC'],
55 | 'limit' => null,
56 | 'offset' => null,
57 | 'runtime' => [],
58 | ];
59 | $adapter->shouldReceive('getClassName')->once()->andReturn('TestD7ClassName');
60 | $adapter->shouldReceive('getList')->with($params)->once()->andReturn(m::self());
61 | $adapter->shouldReceive('fetch')->andReturn(['ID' => 1, 'UF_NAME' => 'John Doe'], ['ID' => 2, 'UF_NAME' => 'John Doe 2'], false);
62 |
63 | $query = $this->createQuery($adapter);
64 | $items = $query->sort(['ID' => 'ASC'])->filter(['UF_NAME' => 'John'])->select(['ID', 'UF_NAME'])->getList();
65 |
66 | $expected = [
67 | 1 => ['ID' => 1, 'UF_NAME' => 'John Doe'],
68 | 2 => ['ID' => 2, 'UF_NAME' => 'John Doe 2'],
69 | ];
70 | foreach ($items as $k => $item) {
71 | $this->assertSame($expected[$k], $item->toArray());
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/tests/ElementQueryTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('GetList')->with([], ['IBLOCK_ID' => 1], [])->once()->andReturn(6);
35 |
36 | $query = $this->createQuery($bxObject);
37 | $count = $query->count();
38 |
39 | $this->assertSame(6, $count);
40 |
41 | $bxObject = m::mock('obj');
42 | TestElement::$bxObject = $bxObject;
43 | $bxObject->shouldReceive('GetList')->with([], ['ACTIVE' => 'Y', 'IBLOCK_ID' => 1], [])->once()->andReturn(3);
44 |
45 | $query = $this->createQuery($bxObject);
46 | $count = $query->filter(['ACTIVE' => 'Y'])->count();
47 |
48 | $this->assertSame(3, $count);
49 | }
50 |
51 | public function testGetListWithSelectAndFilter()
52 | {
53 | $bxObject = m::mock('obj');
54 | TestElement::$bxObject = $bxObject;
55 | $bxObject->shouldReceive('GetList')->with(['SORT' => 'ASC'], ['ACTIVE' => 'N', '!CODE' => false, 'IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
56 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
57 |
58 | $query = $this->createQuery($bxObject);
59 | $items = $query->filter(['ACTIVE' => 'N'])->addFilter(['!CODE' => false])->select('ID', 'NAME')->getList();
60 |
61 | $expected = [
62 | 1 => ['ID' => 1, 'NAME' => 'foo'],
63 | 2 => ['ID' => 2, 'NAME' => 'bar'],
64 | ];
65 | foreach ($items as $k => $item) {
66 | $this->assertSame($expected[$k], $item->fields);
67 | }
68 | }
69 |
70 | public function testGetListWithKeyBy()
71 | {
72 | $bxObject = m::mock('obj');
73 | $bxObject->shouldReceive('GetList')->with(['SORT' => 'ASC'], ['ACTIVE' => 'N', 'IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
74 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
75 |
76 | $query = $this->createQuery($bxObject);
77 | $items = $query->filter(['ACTIVE' => 'N'])->select('ID', 'NAME')->getList();
78 |
79 | $expected = [
80 | 1 => ['ID' => 1, 'NAME' => 'foo', 'ACCESSOR_THREE' => [], 'PROPERTY_LANG_ACCESSOR_ONE' => null],
81 | 2 => ['ID' => 2, 'NAME' => 'bar', 'ACCESSOR_THREE' => [], 'PROPERTY_LANG_ACCESSOR_ONE' => null],
82 | ];
83 |
84 | $this->assertSame($expected, $items->toArray());
85 | $this->assertSame(json_encode($expected), $items->toJson());
86 |
87 | $bxObject = m::mock('obj');
88 | $bxObject->shouldReceive('GetList')->with(['SORT' => 'ASC'], ['ACTIVE' => 'N', 'IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
89 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
90 |
91 | $query = $this->createQuery($bxObject);
92 | $items = $query->filter(['ACTIVE' => 'N'])->keyBy('NAME')->select(['ID', 'NAME'])->getList();
93 |
94 | $expected = [
95 | 'foo' => ['ID' => 1, 'NAME' => 'foo'],
96 | 'bar' => ['ID' => 2, 'NAME' => 'bar'],
97 | ];
98 | foreach ($items as $k => $item) {
99 | $this->assertSame($expected[$k], $item->fields);
100 | }
101 | }
102 |
103 | public function testGetListWithKeyByAndMissingKey()
104 | {
105 | $this->setExpectedException('LogicException');
106 |
107 | $bxObject = m::mock('obj');
108 | $bxObject->shouldReceive('GetList')->with(['SORT' => 'ASC'], ['ACTIVE' => 'N', 'IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
109 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
110 |
111 | $query = $this->createQuery($bxObject);
112 | $items = $query->filter(['ACTIVE' => 'N'])->keyBy('GUID')->select(['ID', 'NAME'])->getList();
113 |
114 | $expected = [
115 | 'foo' => ['ID' => 1, 'NAME' => 'foo'],
116 | 'bar' => ['ID' => 2, 'NAME' => 'bar'],
117 | ];
118 | foreach ($items as $k => $item) {
119 | $this->assertSame($expected[$k], $item->fields);
120 | }
121 | }
122 |
123 | public function testGetListGroupsItemsByKeyBy()
124 | {
125 | $bxObject = m::mock('obj');
126 | $bxObject->shouldReceive('GetList')->with(['SORT' => 'ASC'], ['ACTIVE' => 'N', 'IBLOCK_ID' => 1], false, false, ['ID', 'PROPERTY_FOO', 'IBLOCK_ID'])->once()->andReturn(m::self());
127 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'PROPERTY_FOO_VALUE' => 'foo'], ['ID' => 2, 'PROPERTY_FOO_VALUE' => 'bar'], ['ID' => 2, 'PROPERTY_FOO_VALUE' => 'bar2'], ['ID' => 2, 'PROPERTY_FOO_VALUE' => 'bar3'], false);
128 |
129 | $query = $this->createQuery($bxObject);
130 | $items = $query->filter(['ACTIVE' => 'N'])->select(['ID', 'PROPERTY_FOO'])->getList();
131 |
132 | $expected = [
133 | 1 => ['ID' => 1, 'PROPERTY_FOO_VALUE' => 'foo'],
134 | 2 => ['ID' => 2, 'PROPERTY_FOO_VALUE' => ['bar', 'bar2', 'bar3'], '_were_multiplied' => ['PROPERTY_FOO_VALUE' => true]],
135 | ];
136 | foreach ($items as $k => $item) {
137 | $this->assertSame($expected[$k], $item->fields);
138 | }
139 | }
140 |
141 | public function testResetFilter()
142 | {
143 | $bxObject = m::mock('obj');
144 | TestElement::$bxObject = $bxObject;
145 | $bxObject->shouldReceive('GetList')->with(['SORT' => 'ASC'], ['IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
146 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
147 |
148 | $query = $this->createQuery($bxObject);
149 | $items = $query->filter(['NAME' => 'John'])->resetFilter()->select('ID', 'NAME')->getList();
150 |
151 | $expected = [
152 | 1 => ['ID' => 1, 'NAME' => 'foo'],
153 | 2 => ['ID' => 2, 'NAME' => 'bar'],
154 | ];
155 | foreach ($items as $k => $item) {
156 | $this->assertSame($expected[$k], $item->fields);
157 | }
158 | }
159 |
160 | public function testScopeActive()
161 | {
162 | $bxObject = m::mock('obj');
163 | TestElement::$bxObject = $bxObject;
164 | $bxObject->shouldReceive('GetList')->with(['SORT' => 'ASC'], ['NAME' => 'John', 'ACTIVE' => 'Y', 'IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
165 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
166 |
167 | $query = $this->createQuery($bxObject);
168 | $items = $query->active()->filter(['NAME' => 'John'])->select('ID', 'NAME')->getList();
169 |
170 | $expected = [
171 | 1 => ['ID' => 1, 'NAME' => 'foo'],
172 | 2 => ['ID' => 2, 'NAME' => 'bar'],
173 | ];
174 | foreach ($items as $k => $item) {
175 | $this->assertSame($expected[$k], $item->fields);
176 | }
177 | }
178 |
179 | public function testFromSection()
180 | {
181 | $bxObject = m::mock('obj');
182 | TestElement::$bxObject = $bxObject;
183 | $bxObject->shouldReceive('GetList')->with(['SORT' => 'ASC'], ['SECTION_ID' => 15, 'SECTION_CODE' => 'articles', 'IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
184 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
185 |
186 | $query = $this->createQuery($bxObject);
187 | $items = $query
188 | ->fromSectionWithId(15)
189 | ->fromSectionWithCode('articles')
190 | ->select('ID', 'NAME')
191 | ->getList();
192 |
193 | $expected = [
194 | 1 => ['ID' => 1, 'NAME' => 'foo'],
195 | 2 => ['ID' => 2, 'NAME' => 'bar'],
196 | ];
197 | foreach ($items as $k => $item) {
198 | $this->assertSame($expected[$k], $item->fields);
199 | }
200 | }
201 |
202 | public function testGetById()
203 | {
204 | $bxObject = m::mock('obj');
205 | $query = m::mock('Arrilot\BitrixModels\Queries\ElementQuery[getList]', [$bxObject, 'Arrilot\Tests\BitrixModels\Stubs\TestElement', 1]);
206 | $query->shouldReceive('getList')->once()->andReturn(new Collection([
207 | 1 => [
208 | 'ID' => 1,
209 | 'NAME' => 2,
210 | ],
211 | ]));
212 |
213 | $this->assertSame(['ID' => 1, 'NAME' => 2], $query->getById(1));
214 | $this->assertSame(false, $query->getById(0));
215 | }
216 |
217 | // public function testGetListWithFetchUsing()
218 | // {
219 | // $bxObject = m::mock('obj');
220 | // $bxObject->shouldReceive('getList')
221 | // ->with(['SORT' => 'ASC'], ['ACTIVE' => 'N', 'IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'PROPERTY_GUID', 'IBLOCK_ID'])
222 | // ->once()
223 | // ->andReturn(m::self());
224 | // $bxObject->shouldReceive('Fetch')->andReturn(
225 | // ['ID' => 1, 'NAME' => 'foo', 'PROPERTY_GUID_VALUE' => 'foo'],
226 | // ['ID' => 2, 'NAME' => 'bar', 'PROPERTY_GUID_VALUE' => ''],
227 | // false
228 | // );
229 | //
230 | // TestElement::$bxObject = $bxObject;
231 | // $query = $this->createQuery($bxObject);
232 | // $items = $query->filter(['ACTIVE' => 'N'])->select('ID', 'NAME', 'PROPERTY_GUID')->fetchUsing('Fetch')->getList();
233 | //
234 | // $expected = [
235 | // 1 => ['ID' => 1, 'NAME' => 'foo', 'PROPERTY_GUID_VALUE' => 'foo'],
236 | // 2 => ['ID' => 2, 'NAME' => 'bar', 'PROPERTY_GUID_VALUE' => ''],
237 | // ];
238 | // foreach ($items as $k => $item) {
239 | // $this->assertSame($expected[$k], $item->fields);
240 | // }
241 | // }
242 | //
243 | // public function testGetListWithFetchUsingAndNoProps()
244 | // {
245 | // $bxObject = m::mock('obj');
246 | // $bxObject->shouldReceive('getList')->with(['SORT' => 'ASC'], ['ACTIVE' => 'N', 'IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
247 | // $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
248 | //
249 | // TestElement::$bxObject = $bxObject;
250 | // $query = $this->createQuery($bxObject);
251 | // $items = $query->filter(['ACTIVE' => 'N'])->select('ID', 'NAME')->fetchUsing('Fetch')->getList();
252 | //
253 | // $expected = [
254 | // 1 => ['ID' => 1, 'NAME' => 'foo'],
255 | // 2 => ['ID' => 2, 'NAME' => 'bar'],
256 | // ];
257 | // foreach ($items as $k => $item) {
258 | // $this->assertSame($expected[$k], $item->fields);
259 | // }
260 | // }
261 |
262 | public function testLimitAndPage()
263 | {
264 | $bxObject = m::mock('obj');
265 | TestElement::$bxObject = $bxObject;
266 | $bxObject->shouldReceive('GetList')->with(['SORT' => 'ASC'], ['NAME' => 'John', 'IBLOCK_ID' => 1], false, ['iNumPage' => 3, 'nPageSize' => 2], ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
267 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
268 |
269 | $query = $this->createQuery($bxObject);
270 | $items = $query->filter(['NAME' => 'John'])->page(3)->limit(2)->select('ID', 'NAME')->getList();
271 |
272 | $expected = [
273 | 1 => ['ID' => 1, 'NAME' => 'foo'],
274 | 2 => ['ID' => 2, 'NAME' => 'bar'],
275 | ];
276 | foreach ($items as $k => $item) {
277 | $this->assertSame($expected[$k], $item->fields);
278 | }
279 | }
280 |
281 | public function testSort()
282 | {
283 | $bxObject = m::mock('obj');
284 | TestElement::$bxObject = $bxObject;
285 | $bxObject->shouldReceive('GetList')->with(['NAME' => 'DESC'], ['IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
286 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
287 |
288 | $query = $this->createQuery($bxObject);
289 | $query->sort(['NAME' => 'DESC'])
290 | ->select('ID', 'NAME')
291 | ->getList();
292 |
293 | $bxObject = m::mock('obj');
294 | TestElement::$bxObject = $bxObject;
295 | $bxObject->shouldReceive('GetList')->with(['NAME' => 'ASC'], ['IBLOCK_ID' => 1], false, false, ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
296 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
297 |
298 | $query = $this->createQuery($bxObject);
299 | $query->sort('NAME')
300 | ->select('ID', 'NAME')
301 | ->getList();
302 | }
303 |
304 | public function testFirst()
305 | {
306 | $bxObject = m::mock('obj');
307 | TestElement::$bxObject = $bxObject;
308 | $bxObject->shouldReceive('GetList')->with(['SORT' => 'ASC'], ['NAME' => 'John', 'IBLOCK_ID' => 1], false, ['nPageSize' => 1], ['ID', 'NAME', 'IBLOCK_ID'])->once()->andReturn(m::self());
309 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], false);
310 |
311 | $query = $this->createQuery($bxObject);
312 | $item = $query->filter(['NAME' => 'John'])->select('ID', 'NAME')->first();
313 |
314 | $this->assertSame(['ID' => 1, 'NAME' => 'foo'], $item->fields);
315 | }
316 |
317 | public function testStopAction()
318 | {
319 | $bxObject = m::mock('obj');
320 | TestElement::$bxObject = $bxObject;
321 |
322 | $query = $this->createQuery($bxObject);
323 | $items = $query->filter(['NAME' => 'John'])->stopQuery()->getList();
324 | $this->assertSame((new Collection())->all(), $items->all());
325 |
326 | $query = $this->createQuery($bxObject);
327 | $item = $query->filter(['NAME' => 'John'])->stopQuery()->getById(1);
328 | $this->assertSame(false, $item);
329 |
330 | $query = $this->createQuery($bxObject);
331 | $count = $query->filter(['NAME' => 'John'])->stopQuery()->count();
332 | $this->assertSame(0, $count);
333 | }
334 |
335 | public function testStopActionFromScope()
336 | {
337 | $bxObject = m::mock('obj');
338 | TestElement::$bxObject = $bxObject;
339 |
340 | $query = $this->createQuery($bxObject);
341 | $items = $query->filter(['NAME' => 'John'])->stopActionScope()->getList();
342 | $this->assertSame((new Collection())->all(), $items->all());
343 |
344 | $query = $this->createQuery($bxObject);
345 | $item = $query->filter(['NAME' => 'John'])->stopActionScope()->getById(1);
346 | $this->assertSame(false, $item);
347 |
348 | $query = $this->createQuery($bxObject);
349 | $count = $query->filter(['NAME' => 'John'])->stopActionScope()->count();
350 | $this->assertSame(0, $count);
351 | }
352 |
353 | public function testPaginate()
354 | {
355 | if (!class_exists('Illuminate\Pagination\LengthAwarePaginator')) {
356 | $this->markTestSkipped();
357 | }
358 | $query = m::mock('Arrilot\BitrixModels\Queries\ElementQuery[getList, count]', [null, 'Arrilot\Tests\BitrixModels\Stubs\TestElement'])
359 | ->shouldAllowMockingProtectedMethods();
360 |
361 | $query->shouldReceive('count')->once()->andReturn(100);
362 | $query->shouldReceive('getList')->once()->andReturn(collect(range(1, 15)));
363 |
364 | $items = $query->paginate();
365 |
366 | $this->assertInstanceOf('Illuminate\Pagination\LengthAwarePaginator', $items);
367 | }
368 |
369 | public function testSimplePaginate()
370 | {
371 | if (!class_exists('Illuminate\Pagination\Paginator')) {
372 | $this->markTestSkipped();
373 | }
374 |
375 | $query = m::mock('Arrilot\BitrixModels\Queries\ElementQuery[getList, count]', [null, 'Arrilot\Tests\BitrixModels\Stubs\TestElement'])
376 | ->shouldAllowMockingProtectedMethods();
377 |
378 | $query->shouldReceive('count')->never();
379 | $query->shouldReceive('getList')->once()->andReturn(collect(range(1, 15)));
380 |
381 | $items = $query->simplePaginate();
382 |
383 | $this->assertInstanceOf('Illuminate\Pagination\Paginator', $items);
384 | }
385 | }
386 |
--------------------------------------------------------------------------------
/tests/RelationTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('GetProperties')->withAnyArgs()->andReturn(m::self());
17 | $cIblockObject->shouldReceive('Fetch')->times(3)->andReturn(
18 | [
19 | 'ID' => '1',
20 | 'IBLOCK_ID' => TestElement::IBLOCK_ID,
21 | 'CODE' => 'ELEMENT',
22 | ],
23 | false,
24 | false
25 | );
26 | ElementQuery::$cIblockObject = $cIblockObject;
27 |
28 | $bxObject = m::mock('obj');
29 | $bxObject->shouldReceive('GetList')->withAnyArgs()->once()->andReturn(m::self());
30 | $bxObject->shouldReceive('Fetch')->times(2)->andReturn(
31 | [
32 | 'ID' => 1,
33 | 'NAME' => 'Название',
34 | 'PROPERTY_ELEMENT_VALUE' => ['1', '2'],
35 | 'PROPERTY_ELEMENT_DESCRIPTION' => ['', ''],
36 | 'PROPERTY_ELEMENT_VALUE_ID' => ['element_prop_id_1', 'element_prop_id_2'],
37 | ],
38 | false
39 | );
40 | TestElement::$bxObject = $bxObject;
41 |
42 | $product = TestElement::getById(1);
43 |
44 | $bxObject = m::mock('obj');
45 | $bxObject->shouldReceive('GetList')->with(m::any(), ['IBLOCK_ID' => TestElement2::IBLOCK_ID, 'ID' => [1, 2]], m::any(), false, m::any())->once()->andReturn(m::self());
46 | $bxObject->shouldReceive('Fetch')->times(3)->andReturn(
47 | [
48 | 'ID' => 1,
49 | 'NAME' => 'Название',
50 | ],
51 | [
52 | 'ID' => 2,
53 | 'NAME' => 'Название 2',
54 | ],
55 | false
56 | );
57 | TestElement::$bxObject = $bxObject;
58 |
59 |
60 | $this->assertInstanceOf(Collection::class, $product->elements);
61 | $this->assertCount(2, $product->elements);
62 | $this->assertEquals(['ID' => 1, 'NAME' => 'Название'], $product->elements[1]->fields);
63 | $this->assertEquals(['ID' => 2, 'NAME' => 'Название 2'], $product->elements[2]->fields);
64 | }
65 |
66 | public function testWith()
67 | {
68 | $cIblockObject = m::mock('cIblockObject');
69 | $cIblockObject->shouldReceive('GetProperties')->withAnyArgs()->andReturn(m::self());
70 | $cIblockObject->shouldReceive('Fetch')->times(3)->andReturn(
71 | false,
72 | [
73 | 'ID' => '1',
74 | 'IBLOCK_ID' => TestElement::IBLOCK_ID,
75 | 'CODE' => 'ELEMENT',
76 | ],
77 | false
78 | );
79 | ElementQuery::$cIblockObject = $cIblockObject;
80 |
81 | $bxObject = m::mock('obj');
82 | $bxObject->shouldReceive('GetList')->withAnyArgs()->twice()->andReturn(m::self());
83 | $brandField = [
84 | 'ID' => 1,
85 | 'NAME' => 'Название',
86 | 'PROPERTY_ELEMENT_VALUE' => '1',
87 | 'PROPERTY_ELEMENT_DESCRIPTION' => '',
88 | 'PROPERTY_ELEMENT_VALUE_ID' => 'element_prop_id',
89 | ];
90 |
91 | $bxObject->shouldReceive('Fetch')->times(4)->andReturn(
92 | [
93 | 'ID' => 1,
94 | 'NAME' => 'Название',
95 | ],
96 | false,
97 | $brandField,
98 | false
99 | );
100 | TestElement::$bxObject = $bxObject;
101 |
102 | $product = TestElement::query()->with('element')->getById(1);
103 |
104 | // Проверяем что все запросы были выполнены до текущего момента
105 | $cIblockObject->mockery_verify();
106 | $bxObject->mockery_verify();
107 |
108 | $this->assertEquals($brandField, $product->element->fields);
109 | }
110 |
111 | public function testOne()
112 | {
113 | $cIblockObject = m::mock('cIblockObject');
114 | $cIblockObject->shouldReceive('GetProperties')->withAnyArgs()->andReturn(m::self());
115 | $cIblockObject->shouldReceive('Fetch')->times(3)->andReturn(
116 | false,
117 | [
118 | 'ID' => '1',
119 | 'IBLOCK_ID' => TestElement::IBLOCK_ID,
120 | 'CODE' => 'ELEMENT',
121 | ],
122 | false
123 | );
124 | ElementQuery::$cIblockObject = $cIblockObject;
125 |
126 | $bxObject = m::mock('obj');
127 | $bxObject->shouldReceive('GetList')->withAnyArgs()->once()->andReturn(m::self());
128 | $bxObject->shouldReceive('Fetch')->times(2)->andReturn(
129 | [
130 | 'ID' => 1,
131 | 'NAME' => 'Название',
132 | ],
133 | false
134 | );
135 | TestElement::$bxObject = $bxObject;
136 |
137 | $product = TestElement::getById(1);
138 |
139 |
140 | $bxObject = m::mock('obj');
141 | $bxObject->shouldReceive('GetList')->with(m::any(), ['IBLOCK_ID' => TestElement2::IBLOCK_ID, 'PROPERTY_ELEMENT' => 1], m::any(), ['nPageSize' => 1], m::any())->once()->andReturn(m::self());
142 | $brandField = [
143 | 'ID' => 1,
144 | 'NAME' => 'Название',
145 | ];
146 | $bxObject->shouldReceive('Fetch')->times(2)->andReturn(
147 | $brandField,
148 | false
149 | );
150 | TestElement::$bxObject = $bxObject;
151 |
152 | $this->assertEquals($brandField, $product->element->fields);
153 |
154 | // Проверка, что не выполняются дополнительные запросы
155 | $product->element;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/tests/SectionModelTest.php:
--------------------------------------------------------------------------------
1 | 'Section one']);
24 |
25 | $this->assertSame(2, $section->id);
26 | $this->assertSame(['NAME' => 'Section one'], $section->fields);
27 | }
28 |
29 | public function testDelete()
30 | {
31 | $bxObject = m::mock('obj');
32 | $bxObject->shouldReceive('delete')->once()->andReturn(true);
33 |
34 | TestSection::$bxObject = $bxObject;
35 | $section = new TestSection(1);
36 |
37 | $this->assertTrue($section->delete());
38 | }
39 |
40 | public function testActivate()
41 | {
42 | $bxObject = m::mock('obj');
43 | $bxObject->shouldReceive('update')->with(1, ['ACTIVE' => 'Y'], true, true, false)->once()->andReturn(true);
44 |
45 | TestSection::$bxObject = $bxObject;
46 | $section = new TestSection(1);
47 |
48 | $this->assertTrue($section->activate());
49 | }
50 |
51 | public function testDeactivate()
52 | {
53 | $bxObject = m::mock('obj');
54 | $bxObject->shouldReceive('update')->with(1, ['ACTIVE' => 'N'], true, true, false)->once()->andReturn(true);
55 |
56 | TestSection::$bxObject = $bxObject;
57 | $section = new TestSection(1);
58 |
59 | $this->assertTrue($section->deactivate());
60 | }
61 |
62 | public function testCreate()
63 | {
64 | $bxObject = m::mock('obj');
65 | $bxObject->shouldReceive('add')->with(['NAME' => 'Section 1', 'IBLOCK_ID' => TestSection::iblockId()], true, true, false)->once()->andReturn(3);
66 |
67 | TestSection::$bxObject = $bxObject;
68 |
69 | $newTestSection = TestSection::create(['NAME' => 'Section 1']);
70 |
71 | $this->assertSame(3, $newTestSection->id);
72 | $this->assertEquals([
73 | 'NAME' => 'Section 1',
74 | 'ID' => 3,
75 | 'IBLOCK_ID' => TestSection::iblockId(),
76 | ], $newTestSection->fields);
77 | }
78 |
79 | public function testUpdate()
80 | {
81 | $section = m::mock('Arrilot\Tests\BitrixModels\Stubs\TestSection[save]', [1]);
82 | $section->shouldReceive('save')->with(['NAME', 'UF_FOO'])->andReturn(true);
83 |
84 | $this->assertTrue($section->update(['NAME' => 'Section 1', 'UF_FOO' => 'bar']));
85 | $this->assertSame('Section 1', $section->fields['NAME']);
86 | $this->assertSame('bar', $section->fields['UF_FOO']);
87 | }
88 |
89 | public function testFill()
90 | {
91 | TestSection::$bxObject = m::mock('obj');
92 | $section = new TestSection(1);
93 |
94 | $fields = ['ID' => 2, 'NAME' => 'Section 1'];
95 | $section->fill($fields);
96 |
97 | $this->assertSame(2, $section->id);
98 | $this->assertSame($fields, $section->fields);
99 | $this->assertSame($fields, $section->get());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/tests/SectionQueryTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('getList')->with(
35 | ['SORT' => 'ASC'],
36 | ['NAME' => 'John', 'ACTIVE' => 'Y', 'IBLOCK_ID' => 1],
37 | false,
38 | ['ID', 'NAME'],
39 | false
40 | )->once()->andReturn(m::self());
41 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
42 |
43 | $query = $this->createQuery($bxObject);
44 | $items = $query->sort(['SORT' => 'ASC'])->filter(['NAME' => 'John'])->active()->select('ID', 'NAME')->getList();
45 |
46 | $expected = [
47 | 1 => ['ID' => 1, 'NAME' => 'foo'],
48 | 2 => ['ID' => 2, 'NAME' => 'bar'],
49 | ];
50 | foreach ($items as $k => $item) {
51 | $this->assertSame($expected[$k], $item->toArray());
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Stubs/BxUserWithAuth.php:
--------------------------------------------------------------------------------
1 | hasMany(TestElement2::class, 'ID', 'PROPERTY_ELEMENT_VALUE');
46 | }
47 |
48 | public function element()
49 | {
50 | return $this->hasOne(TestElement2::class, 'PROPERTY_ELEMENT_VALUE', 'ID');
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Stubs/TestElement2.php:
--------------------------------------------------------------------------------
1 | 'John Doe']);
27 |
28 | $this->assertSame(2, $user->id);
29 | $this->assertSame(['NAME' => 'John Doe'], $user->fields);
30 |
31 | $user = new TestUser(2, ['NAME' => 'John Doe', 'GROUP_ID' => [1, 2]]);
32 |
33 | $this->assertSame(2, $user->id);
34 | $this->assertSame(['NAME' => 'John Doe', 'GROUP_ID' => [1, 2]], $user->get());
35 | $this->assertSame([1, 2], $user->getGroups());
36 | }
37 |
38 | public function testCurrentWithAuth()
39 | {
40 | $GLOBALS['USER'] = new BxUserWithAuth();
41 | global $USER;
42 |
43 | $this->mockLoadCurrentUserMethods();
44 |
45 | $user = TestUser::freshCurrent();
46 | $this->assertSame($USER->getId(), $user->id);
47 | $this->assertSame(['ID' => 1, 'NAME' => 'John Doe', 'GROUP_ID' => [1, 2, 3]], $user->fields);
48 | $this->assertSame([1, 2, 3], $user->getGroups());
49 | }
50 |
51 | public function testCurrentWithoutAuth()
52 | {
53 | $GLOBALS['USER'] = new BxUserWithoutAuth();
54 |
55 | $user = TestUser::freshCurrent();
56 | $this->assertSame(null, $user->id);
57 | $this->assertSame([], $user->fields);
58 | }
59 |
60 | public function testHasRoleWithId()
61 | {
62 | $GLOBALS['USER'] = new BxUserWithoutAuth();
63 |
64 | $user = TestUser::freshCurrent();
65 | $this->assertFalse($user->hasGroupWithId(1));
66 |
67 | $user = new TestUser(2, ['NAME' => 'John Doe', 'GROUP_ID' => [1, 2]]);
68 |
69 | $this->assertTrue($user->hasGroupWithId(1));
70 | $this->assertTrue($user->hasGroupWithId(2));
71 | $this->assertFalse($user->hasGroupWithId(3));
72 | }
73 |
74 | public function testIsCurrent()
75 | {
76 | $GLOBALS['USER'] = new BxUserWithAuth();
77 |
78 | $this->mockLoadCurrentUserMethods();
79 |
80 | $user = TestUser::freshCurrent();
81 | $this->assertTrue($user->isCurrent());
82 |
83 | $user = new TestUser(1);
84 | $this->assertTrue($user->isCurrent());
85 |
86 | $user = new TestUser(263);
87 | $this->assertFalse($user->isCurrent());
88 | }
89 |
90 | public function testIsAuthorized()
91 | {
92 | $GLOBALS['USER'] = new BxUserWithAuth();
93 | $this->mockLoadCurrentUserMethods();
94 | $user = TestUser::freshCurrent();
95 | $this->assertTrue($user->isAuthorized());
96 |
97 | $GLOBALS['USER'] = new BxUserWithoutAuth();
98 | $user = TestUser::freshCurrent();
99 | $this->assertFalse($user->isAuthorized());
100 | }
101 |
102 | public function testIsGuest()
103 | {
104 | $GLOBALS['USER'] = new BxUserWithAuth();
105 | $this->mockLoadCurrentUserMethods();
106 | $user = TestUser::freshCurrent();
107 | $this->assertFalse($user->isGuest());
108 |
109 | $GLOBALS['USER'] = new BxUserWithoutAuth();
110 | $user = TestUser::freshCurrent();
111 | $this->assertTrue($user->isGuest());
112 | }
113 |
114 | public function testDelete()
115 | {
116 | $bxObject = m::mock('obj');
117 | $bxObject->shouldReceive('delete')->once()->andReturn(true);
118 |
119 | TestUser::$bxObject = $bxObject;
120 | $user = new TestUser(1);
121 |
122 | $this->assertTrue($user->delete());
123 | }
124 |
125 | public function testActivate()
126 | {
127 | $bxObject = m::mock('obj');
128 | $bxObject->shouldReceive('update')->with(1, ['ACTIVE' => 'Y'])->once()->andReturn(true);
129 |
130 | TestUser::$bxObject = $bxObject;
131 | $user = new TestUser(1);
132 |
133 | $this->assertTrue($user->activate());
134 | }
135 |
136 | public function testDeactivate()
137 | {
138 | $bxObject = m::mock('obj');
139 | $bxObject->shouldReceive('update')->with(1, ['ACTIVE' => 'N'])->once()->andReturn(true);
140 |
141 | TestUser::$bxObject = $bxObject;
142 | $user = new TestUser(1);
143 |
144 | $this->assertTrue($user->deactivate());
145 | }
146 |
147 | public function testCreate()
148 | {
149 | $bxObject = m::mock('obj');
150 | $bxObject->shouldReceive('add')->with(['NAME' => 'John Doe'])->once()->andReturn(3);
151 |
152 | TestUser::$bxObject = $bxObject;
153 |
154 | $newTestUser = TestUser::create(['NAME' => 'John Doe']);
155 |
156 | $this->assertSame(3, $newTestUser->id);
157 | $this->assertSame([
158 | 'NAME' => 'John Doe',
159 | 'ID' => 3,
160 | ], $newTestUser->fields);
161 | }
162 |
163 | public function testCount()
164 | {
165 | $bxObject = m::mock('obj');
166 | $bxObject->shouldReceive('getList')->with('ID', 'ASC', ['ACTIVE' => 'Y'], [
167 | 'NAV_PARAMS' => [
168 | 'nTopCount' => 0,
169 | ],
170 | ])->once()->andReturn(m::self());
171 | $bxObject->NavRecordCount = 2;
172 |
173 | TestUser::$bxObject = $bxObject;
174 |
175 | $this->assertSame(2, TestUser::count(['ACTIVE' => 'Y']));
176 |
177 | $bxObject = m::mock('obj');
178 | $bxObject->shouldReceive('getList')->with('ID', 'ASC', [], [
179 | 'NAV_PARAMS' => [
180 | 'nTopCount' => 0,
181 | ],
182 | ])->once()->andReturn(m::self());
183 | $bxObject->NavRecordCount = 3;
184 |
185 | TestUser::$bxObject = $bxObject;
186 |
187 | $this->assertSame(3, TestUser::count());
188 | }
189 |
190 | public function testUpdate()
191 | {
192 | $user = m::mock('Arrilot\Tests\BitrixModels\Stubs\TestUser[save]', [1]);
193 | $user->shouldReceive('save')->with(['NAME', 'UF_FOO'])->andReturn(true);
194 |
195 | $this->assertTrue($user->update(['NAME' => 'John', 'UF_FOO' => 'bar']));
196 | $this->assertSame('John', $user->fields['NAME']);
197 | $this->assertSame('bar', $user->fields['UF_FOO']);
198 | }
199 |
200 | public function testFill()
201 | {
202 | $user = new TestUser(1);
203 |
204 | $fields = ['ID' => 2, 'NAME' => 'John Doe', 'GROUP_ID' => [1, 2]];
205 | $user->fill($fields);
206 |
207 | $this->assertSame(2, $user->id);
208 | $this->assertSame($fields, $user->fields);
209 | $this->assertSame($fields, $user->get());
210 |
211 | $bxObject = m::mock('obj');
212 | $bxObject->shouldReceive('getUserGroup')->once()->andReturn([1]);
213 | TestUser::$bxObject = $bxObject;
214 | $user = new TestUser(1);
215 |
216 | $fields = ['ID' => 2, 'NAME' => 'John Doe'];
217 | $user->fill($fields);
218 |
219 | $this->assertSame(2, $user->id);
220 | $this->assertSame($fields, $user->fields);
221 | $this->assertSame($fields + ['GROUP_ID' => [1]], $user->get());
222 | }
223 |
224 | public function testFillGroups()
225 | {
226 | $user = new TestUser(1);
227 |
228 | $user->fillGroups([1, 2]);
229 |
230 | $this->assertSame(1, $user->id);
231 | $this->assertSame(['GROUP_ID' => [1, 2]], $user->fields);
232 | $this->assertSame([1, 2], $user->getGroups());
233 | }
234 |
235 | protected function mockLoadCurrentUserMethods()
236 | {
237 | $bxObject = m::mock('obj');
238 | $bxObject->shouldReceive('getList')->withAnyArgs()->once()->andReturn(m::self());
239 | $bxObject->shouldReceive('Fetch')->twice()->andReturn(['ID' => 1, 'NAME' => 'John Doe', 'GROUP_ID' => [1, 2, 3]], false);
240 |
241 | TestUser::$bxObject = $bxObject;
242 | }
243 |
244 | public function testItCanWorkAsAStartingPointForAQuery()
245 | {
246 | $this->assertInstanceOf(UserQuery::class, TestUser::query());
247 | }
248 |
249 | public function testItCanWorkAsAStaticProxy()
250 | {
251 | $this->assertInstanceOf(UserQuery::class, TestUser::filter(['ACTIVE' => 'Y']));
252 | $this->assertInstanceOf(UserQuery::class, TestUser::select('ID'));
253 | $this->assertInstanceOf(UserQuery::class, TestUser::take(15));
254 | $this->assertInstanceOf(UserQuery::class, TestUser::forPage(1, 22));
255 | $this->assertInstanceOf(UserQuery::class, TestUser::active());
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/tests/UserQueryTest.php:
--------------------------------------------------------------------------------
1 | shouldReceive('getList')->with('ID', 'ASC', [], [
34 | 'NAV_PARAMS' => [
35 | 'nTopCount' => 0,
36 | ],
37 | ])->once()->andReturn(m::self());
38 | $bxObject->NavRecordCount = 6;
39 |
40 | $query = $this->createQuery($bxObject);
41 | $count = $query->count();
42 |
43 | $this->assertSame(6, $count);
44 |
45 | $bxObject = m::mock('obj');
46 | $bxObject->shouldReceive('getList')->with('ID', 'ASC', ['ACTIVE' => 'Y'], [
47 | 'NAV_PARAMS' => [
48 | 'nTopCount' => 0,
49 | ],
50 | ])->once()->andReturn(m::self());
51 | $bxObject->NavRecordCount = 3;
52 |
53 | $query = $this->createQuery($bxObject);
54 | $count = $query->filter(['ACTIVE' => 'Y'])->count();
55 |
56 | $this->assertSame(3, $count);
57 | }
58 |
59 | public function testGetListWithScopes()
60 | {
61 | $bxObject = m::mock('obj');
62 | TestUser::$bxObject = $bxObject;
63 | $bxObject->shouldReceive('getList')->with(
64 | ['SORT' => 'ASC'],
65 | false,
66 | ['NAME' => 'John', 'ACTIVE' => 'Y'],
67 | [
68 | 'SELECT' => false,
69 | 'NAV_PARAMS' => false,
70 | 'FIELDS' => ['ID', 'NAME'],
71 | ]
72 | )->once()->andReturn(m::self());
73 | $bxObject->shouldReceive('Fetch')->andReturn(['ID' => 1, 'NAME' => 'foo'], ['ID' => 2, 'NAME' => 'bar'], false);
74 |
75 | $query = $this->createQuery($bxObject);
76 | $items = $query->sort(['SORT' => 'ASC'])->filter(['NAME' => 'John'])->active()->select('ID', 'NAME')->getList();
77 |
78 | $expected = [
79 | 1 => ['ID' => 1, 'NAME' => 'foo'],
80 | 2 => ['ID' => 2, 'NAME' => 'bar'],
81 | ];
82 | foreach ($items as $k => $item) {
83 | $this->assertSame($expected[$k], $item->toArray());
84 | }
85 | }
86 |
87 | public function testGetByLogin()
88 | {
89 | $bxObject = m::mock('obj');
90 | TestUser::$bxObject = $bxObject;
91 | $bxObject->shouldReceive('getList')->with(
92 | ['SORT' => 'ASC'],
93 | false,
94 | ['LOGIN_EQUAL_EXACT' => 'JohnDoe'],
95 | [
96 | 'SELECT' => false,
97 | 'NAV_PARAMS' => ['nPageSize' => 1],
98 | 'FIELDS' => ['ID', 'NAME'],
99 | ]
100 | )->once()->andReturn(m::self());
101 | $bxObject->shouldReceive('Fetch')->times(2)->andReturn(['ID' => 1, 'NAME' => 'foo'], false);
102 |
103 | $query = $this->createQuery($bxObject);
104 | $item = $query->sort(['SORT' => 'ASC'])->select('ID', 'NAME')->getByLogin('JohnDoe');
105 |
106 | $this->assertSame(['ID' => 1, 'NAME' => 'foo'], $item->toArray());
107 | }
108 |
109 | public function testGetByEmail()
110 | {
111 | $bxObject = m::mock('obj');
112 | TestUser::$bxObject = $bxObject;
113 | $bxObject->shouldReceive('getList')->with(
114 | ['SORT' => 'ASC'],
115 | false,
116 | ['EMAIL' => 'john@example.com'],
117 | [
118 | 'SELECT' => false,
119 | 'NAV_PARAMS' => ['nPageSize' => 1],
120 | 'FIELDS' => ['ID', 'NAME'],
121 | ]
122 | )->once()->andReturn(m::self());
123 | $bxObject->shouldReceive('Fetch')->times(2)->andReturn(['ID' => 1, 'NAME' => 'foo'], false);
124 |
125 | $query = $this->createQuery($bxObject);
126 | $item = $query->sort(['SORT' => 'ASC'])->select('ID', 'NAME')->getByEmail('john@example.com');
127 |
128 | $this->assertSame(['ID' => 1, 'NAME' => 'foo'], $item->toArray());
129 | }
130 | }
131 |
--------------------------------------------------------------------------------