├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── migrator
├── phpunit.xml
├── src
├── Autocreate
│ ├── Handlers
│ │ ├── BaseHandler.php
│ │ ├── HandlerInterface.php
│ │ ├── OnBeforeGroupAdd.php
│ │ ├── OnBeforeGroupDelete.php
│ │ ├── OnBeforeGroupUpdate.php
│ │ ├── OnBeforeHLBlockAdd.php
│ │ ├── OnBeforeHLBlockDelete.php
│ │ ├── OnBeforeHLBlockUpdate.php
│ │ ├── OnBeforeIBlockAdd.php
│ │ ├── OnBeforeIBlockDelete.php
│ │ ├── OnBeforeIBlockPropertyAdd.php
│ │ ├── OnBeforeIBlockPropertyDelete.php
│ │ ├── OnBeforeIBlockPropertyUpdate.php
│ │ ├── OnBeforeIBlockUpdate.php
│ │ ├── OnBeforeUserTypeAdd.php
│ │ └── OnBeforeUserTypeDelete.php
│ ├── Manager.php
│ └── Notifier.php
├── BaseMigrations
│ └── BitrixMigration.php
├── Commands
│ ├── AbstractCommand.php
│ ├── ArchiveCommand.php
│ ├── InstallCommand.php
│ ├── MakeCommand.php
│ ├── MigrateCommand.php
│ ├── RollbackCommand.php
│ ├── StatusCommand.php
│ └── TemplatesCommand.php
├── Constructors
│ ├── Constructor.php
│ ├── FieldConstructor.php
│ ├── HighloadBlock.php
│ ├── IBlock.php
│ ├── IBlockProperty.php
│ ├── IBlockPropertyEnum.php
│ ├── IBlockType.php
│ └── UserField.php
├── Exceptions
│ ├── MigrationException.php
│ ├── SkipHandlerException.php
│ └── StopHandlerException.php
├── Helpers.php
├── Interfaces
│ ├── DatabaseStorageInterface.php
│ ├── FileStorageInterface.php
│ └── MigrationInterface.php
├── Logger.php
├── Migrator.php
├── Storages
│ ├── BitrixDatabaseStorage.php
│ └── FileStorage.php
└── TemplatesCollection.php
├── templates
├── add_iblock.template
├── add_iblock_element_property.template
├── add_iblock_type.template
├── add_table.template
├── add_uf.template
├── auto
│ ├── add_group.template
│ ├── add_hlblock.template
│ ├── add_iblock.template
│ ├── add_iblock_element_property.template
│ ├── add_uf.template
│ ├── delete_group.template
│ ├── delete_hlblock.template
│ ├── delete_iblock.template
│ ├── delete_iblock_element_property.template
│ ├── delete_uf.template
│ ├── update_group.template
│ ├── update_hlblock.template
│ ├── update_iblock.template
│ ├── update_iblock_element_property.template
│ └── update_uf.template
├── default.template
├── delete_table.template
└── query.template
└── tests
├── CommandTestCase.php
├── InstallCommandTest.php
├── MakeCommandTest.php
├── MigrateCommandTest.php
├── MigratorTest.php
└── RollbackCommandTest.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 |
10 | before_script:
11 | - composer self-update
12 | - composer install --prefer-source --no-interaction
13 |
14 | script:
15 | - vendor/bin/phpunit
16 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://packagist.org/packages/arrilot/bitrix-migrations/)
2 | [](https://packagist.org/packages/Arrilot/bitrix-migrations)
3 | [](https://travis-ci.org/arrilot/bitrix-migrations)
4 | [](https://scrutinizer-ci.com/g/arrilot/bitrix-migrations/)
5 |
6 | # Данный пакет больше активно не поддерживается
7 |
8 | Причина - мы больше не используем Битрикс в своих проектах.
9 | Если вам интересен этот проект и вы хотите заняться его поддержкой - форкните его и создайте Issue в данном репозитории чтобы мы поместили здесь ссылку на форк.
10 |
11 | Форки:
12 | - https://github.com/informunity/bitrix-migrations
13 |
14 | # Bitrix-migrations
15 |
16 | *Миграции БД для Битрикса и не только*
17 |
18 | ## Установка
19 |
20 | 1) `composer require arrilot/bitrix-migrations`
21 |
22 | 2) `cp vendor/arrilot/bitrix-migrations/migrator migrator` - копируем исполняемый файл в удобное место.
23 |
24 | 3) заходим внутрь и удостоверяемся что задается правильный $_SERVER['DOCUMENT_ROOT']. Меняем настройки если нужно
25 |
26 | 4) `php migrator install`
27 |
28 | Данная команда создаст в БД таблицу для хранения названий выполненных миграций.
29 |
30 | По умолчанию:
31 |
32 | 1) Таблица называется migrations.
33 |
34 | 2) `composer.json` и `migrator` лежат в корне сайта.
35 |
36 | 3) Файлы миграций будут создаваться в директории `./migrations` относительно скопированного на этапе 2 файла.
37 |
38 | При необходимости всё это можно изменить в скопированном файле `migrator`.
39 |
40 | * Крайне рекомендуется сделать `migrator` и `./migrations` недоступными по http через веб-сервер. *
41 |
42 | ## Использование
43 |
44 | ### Рабочий процесс
45 |
46 | Рабочий процесс происходит через консоль и кратко описывается примерно так:
47 |
48 | 1) Создаем файл (или файлы) миграции при помощи `php migrator make название_миграции`
49 |
50 | Файл миграции представляет из себя класс с двумя методами `up()` и `down()`
51 |
52 | 2) Реализуем в методе `up()`необходимые изменения в БД. При желании в методе `down()` реализуем откат этих измнений
53 |
54 | 3) Применяем имеющиеся миграции - `php migrator migrate`
55 |
56 | 4) Вносим файлы миграций в систему контроля версий, чтобы их можно было запустить и на других машинах
57 |
58 |
59 | ### Доступные команды
60 |
61 | Список доступных команд можно получить в консоли - `php migrator list`
62 |
63 |
64 | | Название | Описание |
65 |
66 | | `php migrator install` |
67 | Создает таблицу для хранения миграций. Запускается один раз. |
68 |
69 |
70 | | `php migrator make название_миграции` |
71 |
72 | Создает файл миграции
73 | Опции:
74 | `-d foo/bar` - указать поддиректорию, в которой будет создана миграция
75 | |
76 |
77 |
78 | | `php migrator migrate` |
79 | Применяет все доступные для применения миграции. Миграции примененные ранее не применяются. |
80 |
81 |
82 | | `php migrator rollback` |
83 |
84 | Откатывает последнюю миграцию (метод `down()`). После этого её можно применить повторно.
85 | Опции:
86 | `--hard` - выполнить жесткий откат без вызова метода `down()`
87 | `--delete` - удалить файл с миграцией после отката.
88 | |
89 |
90 |
91 | | `php migrator templates` |
92 | Показывает подробную таблицу со всем существующими шаблонами миграций |
93 |
94 |
95 | | `php migrator status` |
96 | Показывает доступные для выполнения миграции, а также последние выполненные. |
97 |
98 |
99 | | `php migrator archive` |
100 |
101 | Переносит все миграции в архив. По умолчанию это директория archive, но можно переопределить в конфиге, указав "dir_archive"
102 | Опции:
103 | `-w 10` - не переносить в архив последние N миграций
104 | |
105 |
106 |
107 |
108 | ### Шаблоны миграций
109 |
110 | Так как изменение структуры БД битрикса через его АПИ - занятие крайне малоприятное, то для облегчения этого процесса есть механизм шаблонов миграций, работающий следущим образом:
111 | При генерации файла миграции можно указать его шаблон: `php migrator make название_миграции -t add_iblock` где `add_block` - название шаблона.
112 | При этом сгенерируется класс с бойлерплейтом из шаблона и остается лишь указать детали (например название и код инфоблока)
113 | Свои шаблоны миграций можно добавить напрямую в файле `migrator` при помощи `TemplateCollection::registerTemplate()`
114 |
115 | Имеющиеся шаблоны:
116 |
117 |
118 | | Название | Описание | Алиасы |
119 |
120 | | `default` |
121 | Чистый шаблон по умолчанию |
122 | |
123 |
124 |
125 | | `add_iblock_type` |
126 | Добавление типа инфоблока |
127 | |
128 |
129 |
130 | | `add_iblock` |
131 | Добавление инфоблока |
132 | |
133 |
134 |
135 | | `add_iblock_element_property` |
136 | Добавление свойства в инфоблок |
137 | `add_iblock_prop`, `add_iblock_element_prop`, `add_element_prop`, `add_element_property` |
138 |
139 |
140 | | `add_uf` |
141 | Добавление UF свойства |
142 | |
143 |
144 |
145 | | `query` |
146 | Произвольный запрос в БД через АПИ d7 |
147 | |
148 |
149 |
150 | | `add_table` |
151 | Создание таблицы через АПИ d7 |
152 | `create_table` |
153 |
154 |
155 | | `delete_table` |
156 | Удаление таблицы через АПИ d7 |
157 | `drop_table` |
158 |
159 |
160 |
161 | 6) `php migrator status` - показывает доступные для выполнения миграции, а также последние выполненные.
162 |
163 |
164 | ### Автоматическое создание миграций
165 |
166 | Еще одна киллер-фича - режим автоматического создания миграций.
167 | Для его включения необходимо добавить примерно следующее в `init.php`
168 |
169 | ```php
170 | Arrilot\BitrixMigrations\Autocreate\Manager::init($_SERVER["DOCUMENT_ROOT"].'/migrations');
171 | ```
172 |
173 | В метод `Manager::init()` передается путь до директории аналогичной конфигу в файле `migrator`.
174 |
175 | После этого при выполнении ряда действий в админке будет происходить следующее
176 |
177 | 1) Срабатывает битриксовый обработчик события
178 |
179 | 2) Создается файл миграции как при `php migrator make`
180 |
181 | 3) Миграция помечается примененной
182 |
183 | 4) Показывается нотификация о предыдущих пунктах
184 |
185 | Включение данного режима позволяет избавиться от ручного написания миграций для многих случаев.
186 | Ничего в создаваемых автоматически миграциях править не требуется.
187 |
188 | Список обрабатываемых событий:
189 |
190 |
191 | | Событие | Комментарии |
192 |
193 | | Добавление инфоблока |
194 | |
195 |
196 |
197 | | Обновление базовых полей инфоблока |
198 | Из-за специфики работы админки битрикса эта миграция зачастую создается когда не нужно, допустим при добавлении кастомного свойства в инфоблок. Ничего смертельного, но надо смириться. |
199 |
200 |
201 | | Удаление инфоблока |
202 | |
203 |
204 |
205 | | Добавление кастомного свойства в инфоблок |
206 | |
207 |
208 |
209 | | Обновление кастомного свойства инфоблока |
210 | Миграция создается только если какой-либо из атрибутов свойства был изменён |
211 |
212 |
213 | | Удаление кастомного свойства инфоблока |
214 | |
215 |
216 |
217 | | Добавление UF свойства куда-либо (раздел ИБ, пользователь, хайлоадблок) |
218 | К сожалению Битрикс не даёт возможности отслеживать изменение такого свойства - только добавление и удаление |
219 |
220 |
221 | | Удаление UF свойства |
222 | |
223 |
224 |
225 | | Добавление хайлоадблока |
226 | |
227 |
228 |
229 | | Изменение хайлоадблока |
230 | Миграция создается только если какой-либо из атрибутов хайлоадблока был изменён |
231 |
232 |
233 | | Удаление хайлоадблока |
234 | |
235 |
236 |
237 | | Добавление группы пользователей |
238 | |
239 |
240 |
241 | | Изменение группы пользователей |
242 | |
243 |
244 |
245 | | Удаление группы пользователей |
246 | |
247 |
248 |
249 |
250 | * Миграции используют события `OnBefore...`. Если при вашем изменении произошла ошибка (допустим не указана привязка к сайту при добавлении инфоблока)
251 | и было показано уведомление о том что миграция создана, необходимо вручную откатить такую миграцию при помощи `php migrator rollback --hard --delete` *
252 |
253 | ### Обработка ошибок миграций
254 |
255 | Для отмены миграции в момент её выполнения достаточно выкинуть исключение - ```php throw new MigrationException('Тут текст ошибки');```
256 | Ни сама миграция, ни последующие при этом применены не будут.
257 |
258 | ## Использование вне Битрикс
259 |
260 | Пакет создан для использования совместно с Битриксом, однако его довольно просто можно использовать и в других системах.
261 | Для этого нужно в файле `migrator`:
262 |
263 | 1) Заменить подключение ядра Битрикса на ядро другой системы.
264 |
265 | 2) Реализовать свой аналог ` Arrilot\BitrixMigrations\Repositories\BitrixDatabaseRepository;` и использовать его.
266 |
267 | 3) По желанию отключить существующие шаблоны миграций, сделав свои.
268 |
269 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "arrilot/bitrix-migrations",
3 | "license": "MIT",
4 | "description": "Database migrations for Bitrix CMS",
5 | "keywords": [
6 | "bitrix",
7 | "migrations"
8 | ],
9 | "authors": [
10 | {
11 | "name": "Nekrasov Ilya",
12 | "email": "nekrasov.ilya90@gmail.com"
13 | }
14 | ],
15 | "homepage": "https://github.com/Arrilot/bitrix-migrations",
16 | "require": {
17 | "php": ">=5.5.0",
18 | "symfony/console": "~2|~3|~4",
19 | "tightenco/collect": "5.*"
20 | },
21 | "require-dev": {
22 | "phpunit/phpunit": "~4.0",
23 | "mockery/mockery": "~0.9"
24 | },
25 | "autoload": {
26 | "psr-4": {
27 | "Arrilot\\BitrixMigrations\\": "src/"
28 | }
29 | },
30 | "autoload-dev": {
31 | "psr-4": {
32 | "Arrilot\\Tests\\BitrixMigrations\\": "tests/"
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/migrator:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | 'migrations',
26 | 'dir' => './migrations',
27 | // 'dir_archive' => 'archive', // not required. default = "archive"
28 | 'use_transaction' => true, // not required. default = false
29 | 'default_fields' => [
30 | IBlock::class => [
31 | 'INDEX_ELEMENT' => 'N',
32 | 'INDEX_SECTION' => 'N',
33 | 'VERSION' => 2,
34 | 'SITE_ID' => 's1',
35 | ]
36 | ]
37 | ];
38 |
39 | $database = new BitrixDatabaseStorage($config['table']);
40 | $templates = new TemplatesCollection();
41 | $templates->registerBasicTemplates();
42 |
43 | $migrator = new Migrator($config, $templates, $database);
44 |
45 | $app = new Application('Migrator');
46 | $app->add(new MakeCommand($migrator));
47 | $app->add(new InstallCommand($config['table'], $database));
48 | $app->add(new MigrateCommand($migrator));
49 | $app->add(new RollbackCommand($migrator));
50 | $app->add(new TemplatesCommand($templates));
51 | $app->add(new StatusCommand($migrator));
52 | $app->add(new ArchiveCommand($migrator));
53 | $app->run();
54 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | tests
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/BaseHandler.php:
--------------------------------------------------------------------------------
1 | fields = $params[0];
19 |
20 | if (!$this->fields['STRING_ID']) {
21 | throw new StopHandlerException('Code is required to create a migration');
22 | }
23 | }
24 |
25 | /**
26 | * Get migration name.
27 | *
28 | * @return string
29 | */
30 | public function getName()
31 | {
32 | return "auto_add_group_{$this->fields['STRING_ID']}";
33 | }
34 |
35 | /**
36 | * Get template name.
37 | *
38 | * @return string
39 | */
40 | public function getTemplate()
41 | {
42 | return 'auto_add_group';
43 | }
44 |
45 | /**
46 | * Get array of placeholders to replace.
47 | *
48 | * @return array
49 | */
50 | public function getReplace()
51 | {
52 | return [
53 | 'fields' => var_export($this->fields, true),
54 | ];
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeGroupDelete.php:
--------------------------------------------------------------------------------
1 | id = $params[0];
27 |
28 | $this->fields = CGroup::GetByID($this->id)->fetch();
29 | }
30 |
31 | /**
32 | * Get migration name.
33 | *
34 | * @return string
35 | */
36 | public function getName()
37 | {
38 | return "auto_delete_group_{$this->fields['STRING_ID']}";
39 | }
40 |
41 | /**
42 | * Get template name.
43 | *
44 | * @return string
45 | */
46 | public function getTemplate()
47 | {
48 | return 'auto_delete_group';
49 | }
50 |
51 | /**
52 | * Get array of placeholders to replace.
53 | *
54 | * @return array
55 | */
56 | public function getReplace()
57 | {
58 | return [
59 | 'id' => $this->id,
60 | ];
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeGroupUpdate.php:
--------------------------------------------------------------------------------
1 | id = $params[0];
26 | $this->fields = $params[1];
27 |
28 | if (!$this->fields['STRING_ID']) {
29 | throw new StopHandlerException('Code is required to create a migration');
30 | }
31 | }
32 |
33 | /**
34 | * Get migration name.
35 | *
36 | * @return string
37 | */
38 | public function getName()
39 | {
40 | return "auto_update_group_{$this->fields['STRING_ID']}";
41 | }
42 |
43 | /**
44 | * Get template name.
45 | *
46 | * @return string
47 | */
48 | public function getTemplate()
49 | {
50 | return 'auto_update_group';
51 | }
52 |
53 | /**
54 | * Get array of placeholders to replace.
55 | *
56 | * @return array
57 | */
58 | public function getReplace()
59 | {
60 | return [
61 | 'fields' => var_export($this->fields, true),
62 | 'id' => $this->id,
63 | ];
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeHLBlockAdd.php:
--------------------------------------------------------------------------------
1 | event = $params[0];
25 |
26 | $this->fields = $this->event->getParameter('fields');
27 | }
28 |
29 | /**
30 | * Get migration name.
31 | *
32 | * @return string
33 | */
34 | public function getName()
35 | {
36 | return 'auto_add_hlblock_'.$this->fields['TABLE_NAME'];
37 | }
38 |
39 | /**
40 | * Get template name.
41 | *
42 | * @return string
43 | */
44 | public function getTemplate()
45 | {
46 | return 'auto_add_hlblock';
47 | }
48 |
49 | /**
50 | * Get array of placeholders to replace.
51 | *
52 | * @return array
53 | */
54 | public function getReplace()
55 | {
56 | return [
57 | 'fields' => var_export($this->fields, true),
58 | ];
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeHLBlockDelete.php:
--------------------------------------------------------------------------------
1 | event = $params[0];
33 |
34 | $eventParams = $this->event->getParameters();
35 |
36 | $this->id = $eventParams['id']['ID'];
37 | $this->fields = HighloadBlockTable::getById($this->id)->fetch();
38 | }
39 |
40 | /**
41 | * Get migration name.
42 | *
43 | * @return string
44 | */
45 | public function getName()
46 | {
47 | return 'auto_delete_hlblock_'.$this->fields['TABLE_NAME'];
48 | }
49 |
50 | /**
51 | * Get template name.
52 | *
53 | * @return string
54 | */
55 | public function getTemplate()
56 | {
57 | return 'auto_delete_hlblock';
58 | }
59 |
60 | /**
61 | * Get array of placeholders to replace.
62 | *
63 | * @return array
64 | */
65 | public function getReplace()
66 | {
67 | return [
68 | 'fields' => var_export($this->fields, true),
69 | 'id' => $this->id,
70 | ];
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeHLBlockUpdate.php:
--------------------------------------------------------------------------------
1 | event = $params[0];
33 |
34 | $eventParams = $this->event->getParameters();
35 |
36 | $this->fields = $eventParams['fields'];
37 | $this->id = $eventParams['id']['ID'];
38 |
39 | if (!$this->fieldsHaveBeenChanged()) {
40 | throw new SkipHandlerException();
41 | }
42 | }
43 |
44 | /**
45 | * Get migration name.
46 | *
47 | * @return string
48 | */
49 | public function getName()
50 | {
51 | return 'auto_update_hlblock_'.$this->fields['TABLE_NAME'];
52 | }
53 |
54 | /**
55 | * Get template name.
56 | *
57 | * @return string
58 | */
59 | public function getTemplate()
60 | {
61 | return 'auto_update_hlblock';
62 | }
63 |
64 | /**
65 | * Get array of placeholders to replace.
66 | *
67 | * @return array
68 | */
69 | public function getReplace()
70 | {
71 | return [
72 | 'id' => $this->id,
73 | 'fields' => var_export($this->fields, true),
74 | ];
75 | }
76 |
77 | /**
78 | * Determine if fields have been changed.
79 | *
80 | * @return bool
81 | */
82 | protected function fieldsHaveBeenChanged()
83 | {
84 | $old = HighloadBlockTable::getById($this->id)->fetch();
85 | $new = $this->fields + ['ID' => (string) $this->id];
86 |
87 | return $new != $old;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeIBlockAdd.php:
--------------------------------------------------------------------------------
1 | fields = $params[0];
15 | }
16 |
17 | /**
18 | * Get migration name.
19 | *
20 | * @return string
21 | */
22 | public function getName()
23 | {
24 | return "auto_add_iblock_{$this->fields['CODE']}";
25 | }
26 |
27 | /**
28 | * Get template name.
29 | *
30 | * @return string
31 | */
32 | public function getTemplate()
33 | {
34 | return 'auto_add_iblock';
35 | }
36 |
37 | /**
38 | * Get array of placeholders to replace.
39 | *
40 | * @return array
41 | */
42 | public function getReplace()
43 | {
44 | return [
45 | 'fields' => var_export($this->fields, true),
46 | 'code' => "'".$this->fields['CODE']."'",
47 | ];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeIBlockDelete.php:
--------------------------------------------------------------------------------
1 | fields = $this->getIBlockById($params[0]);
17 | }
18 |
19 | /**
20 | * Get migration name.
21 | *
22 | * @return string
23 | */
24 | public function getName()
25 | {
26 | return "auto_delete_iblock_{$this->fields['CODE']}";
27 | }
28 |
29 | /**
30 | * Get template name.
31 | *
32 | * @return string
33 | */
34 | public function getTemplate()
35 | {
36 | return 'auto_delete_iblock';
37 | }
38 |
39 | /**
40 | * Get array of placeholders to replace.
41 | *
42 | * @return array
43 | */
44 | public function getReplace()
45 | {
46 | return [
47 | 'code' => "'".$this->fields['CODE']."'",
48 | ];
49 | }
50 |
51 | /**
52 | * Get iblock by id without checking permissions.
53 | *
54 | * @param $id
55 | *
56 | * @return array
57 | */
58 | protected function getIBlockById($id)
59 | {
60 | $filter = [
61 | 'ID' => $id,
62 | 'CHECK_PERMISSIONS' => 'N',
63 | ];
64 |
65 | return (new CIBlock())->getList([], $filter)->fetch();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeIBlockPropertyAdd.php:
--------------------------------------------------------------------------------
1 | fields = $params[0];
18 |
19 | if (!$this->fields['IBLOCK_ID']) {
20 | throw new SkipHandlerException();
21 | }
22 | }
23 |
24 | /**
25 | * Get migration name.
26 | *
27 | * @return string
28 | */
29 | public function getName()
30 | {
31 | return "auto_add_iblock_element_property_{$this->fields['CODE']}_to_ib_{$this->fields['IBLOCK_ID']}";
32 | }
33 |
34 | /**
35 | * Get template name.
36 | *
37 | * @return string
38 | */
39 | public function getTemplate()
40 | {
41 | return 'auto_add_iblock_element_property';
42 | }
43 |
44 | /**
45 | * Get array of placeholders to replace.
46 | *
47 | * @return array
48 | */
49 | public function getReplace()
50 | {
51 | return [
52 | 'fields' => var_export($this->fields, true),
53 | 'iblockId' => $this->fields['IBLOCK_ID'],
54 | 'code' => "'".$this->fields['CODE']."'",
55 | ];
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeIBlockPropertyDelete.php:
--------------------------------------------------------------------------------
1 | fields = CIBlockProperty::getByID($params[0])->fetch();
17 | }
18 |
19 | /**
20 | * Get migration name.
21 | *
22 | * @return string
23 | */
24 | public function getName()
25 | {
26 | return "auto_delete_iblock_element_property_{$this->fields['CODE']}_in_ib_{$this->fields['IBLOCK_ID']}";
27 | }
28 |
29 | /**
30 | * Get template name.
31 | *
32 | * @return string
33 | */
34 | public function getTemplate()
35 | {
36 | return 'auto_delete_iblock_element_property';
37 | }
38 |
39 | /**
40 | * Get array of placeholders to replace.
41 | *
42 | * @return array
43 | */
44 | public function getReplace()
45 | {
46 | return [
47 | 'iblockId' => $this->fields['IBLOCK_ID'],
48 | 'code' => "'".$this->fields['CODE']."'",
49 | ];
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeIBlockPropertyUpdate.php:
--------------------------------------------------------------------------------
1 | fields = $params[0];
28 |
29 | $this->dbFields = $this->collectPropertyFieldsFromDB();
30 |
31 | if (!$this->propertyHasChanged() || !$this->fields['IBLOCK_ID']) {
32 | throw new SkipHandlerException();
33 | }
34 | }
35 |
36 | /**
37 | * Get migration name.
38 | *
39 | * @return string
40 | */
41 | public function getName()
42 | {
43 | return "auto_update_iblock_element_property_{$this->fields['CODE']}_in_ib_{$this->fields['IBLOCK_ID']}";
44 | }
45 |
46 | /**
47 | * Get template name.
48 | *
49 | * @return string
50 | */
51 | public function getTemplate()
52 | {
53 | return 'auto_update_iblock_element_property';
54 | }
55 |
56 | /**
57 | * Get array of placeholders to replace.
58 | *
59 | * @return array
60 | */
61 | public function getReplace()
62 | {
63 | return [
64 | 'fields' => var_export($this->fields, true),
65 | 'iblockId' => $this->fields['IBLOCK_ID'],
66 | 'code' => "'".$this->fields['CODE']."'",
67 | ];
68 | }
69 |
70 | /**
71 | * Collect property fields from DB and convert them to format that can be compared from user input.
72 | *
73 | * @return array
74 | */
75 | protected function collectPropertyFieldsFromDB()
76 | {
77 | $fields = CIBlockProperty::getByID($this->fields['ID'])->fetch();
78 | $fields['VALUES'] = [];
79 |
80 | $filter = [
81 | 'IBLOCK_ID' => $this->fields['IBLOCK_ID'],
82 | 'PROPERTY_ID' => $this->fields['ID'],
83 | ];
84 | $sort = [
85 | 'SORT' => 'ASC',
86 | 'VALUE' => 'ASC',
87 | ];
88 |
89 | $propertyEnums = CIBlockPropertyEnum::GetList($sort, $filter);
90 | while ($v = $propertyEnums->GetNext()) {
91 | $fields['VALUES'][$v['ID']] = [
92 | 'ID' => $v['ID'],
93 | 'VALUE' => $v['VALUE'],
94 | 'SORT' => $v['SORT'],
95 | 'XML_ID' => $v['XML_ID'],
96 | 'DEF' => $v['DEF'],
97 | ];
98 | }
99 |
100 | return $fields;
101 | }
102 |
103 | /**
104 | * Determine if property has been changed.
105 | *
106 | * @return bool
107 | */
108 | protected function propertyHasChanged()
109 | {
110 | foreach ($this->dbFields as $field => $value) {
111 | if (isset($this->fields[$field]) && ($this->fields[$field] != $value)) {
112 | return true;
113 | }
114 | }
115 |
116 | return false;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeIBlockUpdate.php:
--------------------------------------------------------------------------------
1 | fields = $params[0];
18 |
19 | // Если кода нет то миграция создастся битая.
20 | // Еще это позволяет решить проблему с тем что создается лишняя миграция для торгового каталога
21 | // когда обновляют связанный с ним инфоблок.
22 | if (!$this->fields['CODE']) {
23 | throw new SkipHandlerException();
24 | }
25 | }
26 |
27 | /**
28 | * Get migration name.
29 | *
30 | * @return string
31 | */
32 | public function getName()
33 | {
34 | return "auto_update_iblock_{$this->fields['CODE']}";
35 | }
36 |
37 | /**
38 | * Get template name.
39 | *
40 | * @return string
41 | */
42 | public function getTemplate()
43 | {
44 | return 'auto_update_iblock';
45 | }
46 |
47 | /**
48 | * Get array of placeholders to replace.
49 | *
50 | * @return array
51 | */
52 | public function getReplace()
53 | {
54 | return [
55 | 'fields' => var_export($this->fields, true),
56 | 'code' => "'".$this->fields['CODE']."'",
57 | ];
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeUserTypeAdd.php:
--------------------------------------------------------------------------------
1 | fields = $params[0];
15 | }
16 |
17 | /**
18 | * Get migration name.
19 | *
20 | * @return string
21 | */
22 | public function getName()
23 | {
24 | return "auto_add_uf_{$this->fields['FIELD_NAME']}_to_entity_{$this->fields['ENTITY_ID']}";
25 | }
26 |
27 | /**
28 | * Get template name.
29 | *
30 | * @return string
31 | */
32 | public function getTemplate()
33 | {
34 | return 'auto_add_uf';
35 | }
36 |
37 | /**
38 | * Get array of placeholders to replace.
39 | *
40 | * @return array
41 | */
42 | public function getReplace()
43 | {
44 | return [
45 | 'fields' => var_export($this->fields, true),
46 | 'code' => "'".$this->fields['FIELD_NAME']."'",
47 | 'entity' => "'".$this->fields['ENTITY_ID']."'",
48 | ];
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Autocreate/Handlers/OnBeforeUserTypeDelete.php:
--------------------------------------------------------------------------------
1 | fields = is_array($params[0]) ? $params[0] : CUserTypeEntity::getByID($params[0]);
17 | }
18 |
19 | /**
20 | * Get migration name.
21 | *
22 | * @return string
23 | */
24 | public function getName()
25 | {
26 | return "auto_delete_uf_{$this->fields['FIELD_NAME']}_from_entity_{$this->fields['ENTITY_ID']}";
27 | }
28 |
29 | /**
30 | * Get template name.
31 | *
32 | * @return string
33 | */
34 | public function getTemplate()
35 | {
36 | return 'auto_delete_uf';
37 | }
38 |
39 | /**
40 | * Get array of placeholders to replace.
41 | *
42 | * @return array
43 | */
44 | public function getReplace()
45 | {
46 | return [
47 | 'iblockId' => $this->fields['IBLOCK_ID'],
48 | 'code' => "'".$this->fields['FIELD_NAME']."'",
49 | 'entity' => "'".$this->fields['ENTITY_ID']."'",
50 | 'fields' => var_export($this->fields, true),
51 | ];
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Autocreate/Manager.php:
--------------------------------------------------------------------------------
1 | [
34 | 'OnBeforeIBlockAdd' => 'OnBeforeIBlockAdd',
35 | 'OnBeforeIBlockUpdate' => 'OnBeforeIBlockUpdate',
36 | 'OnBeforeIBlockDelete' => 'OnBeforeIBlockDelete',
37 | 'OnBeforeIBlockPropertyAdd' => 'OnBeforeIBlockPropertyAdd',
38 | 'OnBeforeIBlockPropertyUpdate' => 'OnBeforeIBlockPropertyUpdate',
39 | 'OnBeforeIBlockPropertyDelete' => 'OnBeforeIBlockPropertyDelete',
40 | ],
41 | 'main' => [
42 | 'OnBeforeUserTypeAdd' => 'OnBeforeUserTypeAdd',
43 | 'OnBeforeUserTypeDelete' => 'OnBeforeUserTypeDelete',
44 | 'OnBeforeGroupAdd' => 'OnBeforeGroupAdd',
45 | 'OnBeforeGroupUpdate' => 'OnBeforeGroupUpdate',
46 | 'OnBeforeGroupDelete' => 'OnBeforeGroupDelete',
47 | ],
48 | 'highloadblock' => [
49 | '\\Bitrix\\Highloadblock\\Highloadblock::OnBeforeAdd' => 'OnBeforeHLBlockAdd',
50 | '\\Bitrix\\Highloadblock\\Highloadblock::OnBeforeUpdate' => 'OnBeforeHLBlockUpdate',
51 | '\\Bitrix\\Highloadblock\\Highloadblock::OnBeforeDelete' => 'OnBeforeHLBlockDelete',
52 | ],
53 | ];
54 |
55 | /**
56 | * Initialize autocreation.
57 | *
58 | * @param string $dir
59 | * @param string|null $table
60 | */
61 | public static function init($dir, $table = null)
62 | {
63 | $templates = new TemplatesCollection();
64 | $templates->registerAutoTemplates();
65 |
66 | $config = [
67 | 'dir' => $dir,
68 | 'table' => is_null($table) ? 'migrations' : $table,
69 | ];
70 |
71 | static::$migrator = new Migrator($config, $templates);
72 |
73 | static::addEventHandlers();
74 |
75 | static::turnOn();
76 | }
77 |
78 | /**
79 | * Determine if autocreation is turned on.
80 | *
81 | * @return bool
82 | */
83 | public static function isTurnedOn()
84 | {
85 | return static::$isTurnedOn && defined('ADMIN_SECTION');
86 | }
87 |
88 | /**
89 | * Turn on autocreation.
90 | *
91 | * @return void
92 | */
93 | public static function turnOn()
94 | {
95 | static::$isTurnedOn = true;
96 | }
97 |
98 | /**
99 | * Turn off autocreation.
100 | *
101 | * @return void
102 | */
103 | public static function turnOff()
104 | {
105 | static::$isTurnedOn = false;
106 | }
107 |
108 | /**
109 | * Instantiate handler.
110 | *
111 | * @param string $handler
112 | * @param array $parameters
113 | *
114 | * @return mixed
115 | */
116 | protected static function instantiateHandler($handler, $parameters)
117 | {
118 | $class = __NAMESPACE__.'\\Handlers\\'.$handler;
119 |
120 | return new $class($parameters);
121 | }
122 |
123 | /**
124 | * Create migration and apply it.
125 | *
126 | * @param HandlerInterface $handler
127 | */
128 | protected static function createMigration(HandlerInterface $handler)
129 | {
130 | $migrator = static::$migrator;
131 | $notifier = new Notifier();
132 |
133 | $migration = $migrator->createMigration(
134 | strtolower($handler->getName()),
135 | $handler->getTemplate(),
136 | $handler->getReplace()
137 | );
138 |
139 | $migrator->logSuccessfulMigration($migration);
140 | $notifier->newMigration($migration);
141 | }
142 |
143 | /**
144 | * Add event handlers.
145 | */
146 | protected static function addEventHandlers()
147 | {
148 | $eventManager = EventManager::getInstance();
149 |
150 | foreach (static::$handlers as $module => $handlers) {
151 | foreach ($handlers as $event => $handler) {
152 | $eventManager->addEventHandler($module, $event, [__CLASS__, $handler], false, 5000);
153 | }
154 | }
155 |
156 | $eventManager->addEventHandler('main', 'OnAfterEpilog', function () {
157 | $notifier = new Notifier();
158 | $notifier->deleteNotificationFromPreviousMigration();
159 |
160 | return new EventResult();
161 | });
162 | }
163 |
164 | /**
165 | * Magic static call to a handler.
166 | *
167 | * @param string $method
168 | * @param array $parameters
169 | *
170 | * @return mixed
171 | */
172 | public static function __callStatic($method, $parameters)
173 | {
174 | $eventResult = new EventResult();
175 |
176 | if (!static::isTurnedOn()) {
177 | return $eventResult;
178 | }
179 |
180 | try {
181 | $handler = static::instantiateHandler($method, $parameters);
182 | } catch (SkipHandlerException $e) {
183 | return $eventResult;
184 | } catch (StopHandlerException $e) {
185 | global $APPLICATION;
186 | $APPLICATION->throwException($e->getMessage());
187 |
188 | return false;
189 | }
190 |
191 | static::createMigration($handler);
192 |
193 | return $eventResult;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/Autocreate/Notifier.php:
--------------------------------------------------------------------------------
1 | 'Migration '.$migration.' has been created and applied.',
25 | 'TAG' => $this->tag,
26 | 'MODULE_ID' => 'main',
27 | 'ENABLE_CLOSE' => 'Y',
28 | ];
29 |
30 | CAdminNotify::add($notification);
31 | }
32 |
33 | /**
34 | * Delete notification from the previous migration.
35 | */
36 | public function deleteNotificationFromPreviousMigration()
37 | {
38 | if (defined('ADMIN_SECTION')) {
39 | CAdminNotify::deleteByTag($this->tag);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/BaseMigrations/BitrixMigration.php:
--------------------------------------------------------------------------------
1 | db = Application::getConnection();
33 | }
34 |
35 | /**
36 | * Run the migration.
37 | *
38 | * @return mixed
39 | */
40 | public function up()
41 | {
42 | //
43 | }
44 |
45 | /**
46 | * Reverse the migration.
47 | *
48 | * @return mixed
49 | */
50 | public function down()
51 | {
52 | //
53 | }
54 |
55 | /**
56 | * Does migration use transaction
57 | * @param bool $default
58 | * @return bool
59 | */
60 | public function useTransaction($default = false)
61 | {
62 | if (!is_null($this->use_transaction)) {
63 | return $this->use_transaction;
64 | }
65 |
66 | return $default;
67 | }
68 |
69 | /**
70 | * Find iblock id by its code.
71 | *
72 | * @param string $code
73 | * @param null|string $iBlockType
74 | *
75 | * @throws MigrationException
76 | *
77 | * @return int
78 | */
79 | protected function getIblockIdByCode($code, $iBlockType = null)
80 | {
81 | if (!$code) {
82 | throw new MigrationException('Не задан код инфоблока');
83 | }
84 |
85 | $filter = [
86 | 'CODE' => $code,
87 | 'CHECK_PERMISSIONS' => 'N',
88 | ];
89 |
90 | if ($iBlockType !== null) {
91 | $filter['TYPE'] = $iBlockType;
92 | }
93 |
94 | $iblock = (new CIBlock())->GetList([], $filter)->fetch();
95 |
96 | if (!$iblock['ID']) {
97 | throw new MigrationException("Не удалось найти инфоблок с кодом '{$code}'");
98 | }
99 |
100 | return $iblock['ID'];
101 | }
102 |
103 | /**
104 | * Delete iblock by its code.
105 | *
106 | * @param string $code
107 | *
108 | * @throws MigrationException
109 | *
110 | * @return void
111 | */
112 | protected function deleteIblockByCode($code)
113 | {
114 | $id = $this->getIblockIdByCode($code);
115 |
116 | $this->db->startTransaction();
117 | if (!CIBlock::Delete($id)) {
118 | $this->db->rollbackTransaction();
119 | throw new MigrationException('Ошибка при удалении инфоблока');
120 | }
121 |
122 | $this->db->commitTransaction();
123 | }
124 |
125 | /**
126 | * Add iblock element property.
127 | *
128 | * @param array $fields
129 | *
130 | * @throws MigrationException
131 | *
132 | * @return int
133 | */
134 | public function addIblockElementProperty($fields)
135 | {
136 | $ibp = new CIBlockProperty();
137 | $propId = $ibp->add($fields);
138 |
139 | if (!$propId) {
140 | throw new MigrationException('Ошибка при добавлении свойства инфоблока '.$ibp->LAST_ERROR);
141 | }
142 |
143 | return $propId;
144 | }
145 |
146 | /**
147 | * Delete iblock element property.
148 | *
149 | * @param string $code
150 | * @param string|int $iblockId
151 | *
152 | * @throws MigrationException
153 | */
154 | public function deleteIblockElementPropertyByCode($iblockId, $code)
155 | {
156 | if (!$iblockId) {
157 | throw new MigrationException('Не задан ID инфоблока');
158 | }
159 |
160 | if (!$code) {
161 | throw new MigrationException('Не задан код свойства');
162 | }
163 |
164 | $id = $this->getIblockPropIdByCode($code, $iblockId);
165 |
166 | CIBlockProperty::Delete($id);
167 | }
168 |
169 | /**
170 | * Add User Field.
171 | *
172 | * @param $fields
173 | *
174 | * @throws MigrationException
175 | *
176 | * @return int
177 | */
178 | public function addUF($fields)
179 | {
180 | if (!$fields['FIELD_NAME']) {
181 | throw new MigrationException('Не заполнен FIELD_NAME');
182 | }
183 |
184 | if (!$fields['ENTITY_ID']) {
185 | throw new MigrationException('Не заполнен код ENTITY_ID');
186 | }
187 |
188 | $oUserTypeEntity = new CUserTypeEntity();
189 |
190 | $fieldId = $oUserTypeEntity->Add($fields);
191 |
192 | if (!$fieldId) {
193 | throw new MigrationException("Не удалось создать пользовательское свойство с FIELD_NAME = {$fields['FIELD_NAME']} и ENTITY_ID = {$fields['ENTITY_ID']}");
194 | }
195 |
196 | return $fieldId;
197 | }
198 |
199 | /**
200 | * Get UF by its code.
201 | *
202 | * @param string $entity
203 | * @param string $code
204 | *
205 | * @throws MigrationException
206 | */
207 | public function getUFIdByCode($entity, $code)
208 | {
209 | if (!$entity) {
210 | throw new MigrationException('Не задана сущность свойства');
211 | }
212 |
213 | if (!$code) {
214 | throw new MigrationException('Не задан код свойства');
215 | }
216 |
217 | $filter = [
218 | 'ENTITY_ID' => $entity,
219 | 'FIELD_NAME' => $code,
220 | ];
221 |
222 | $arField = CUserTypeEntity::GetList(['ID' => 'ASC'], $filter)->fetch();
223 | if (!$arField || !$arField['ID']) {
224 | throw new MigrationException("Не найдено свойство с FIELD_NAME = {$filter['FIELD_NAME']} и ENTITY_ID = {$filter['ENTITY_ID']}");
225 | }
226 |
227 | return $arField['ID'];
228 | }
229 |
230 | /**
231 | * @param $code
232 | * @param $iblockId
233 | *
234 | * @throws MigrationException
235 | *
236 | * @return array
237 | */
238 | protected function getIblockPropIdByCode($code, $iblockId)
239 | {
240 | $filter = [
241 | 'CODE' => $code,
242 | 'IBLOCK_ID' => $iblockId,
243 | ];
244 |
245 | $prop = CIBlockProperty::getList(['sort' => 'asc', 'name' => 'asc'], $filter)->getNext();
246 | if (!$prop || !$prop['ID']) {
247 | throw new MigrationException("Не удалось найти свойство с кодом '{$code}'");
248 | }
249 |
250 | return $prop['ID'];
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/src/Commands/AbstractCommand.php:
--------------------------------------------------------------------------------
1 | error($message);
32 | }
33 |
34 | $this->error('Abort!');
35 |
36 | throw new DomainException();
37 | }
38 |
39 | /**
40 | * Executes the current command.
41 | *
42 | * @param InputInterface $input An InputInterface instance
43 | * @param OutputInterface $output An OutputInterface instance
44 | *
45 | * @return null|int null or 0 if everything went fine, or an error code.
46 | */
47 | protected function execute(InputInterface $input, OutputInterface $output)
48 | {
49 | $this->input = $input;
50 | $this->output = $output;
51 |
52 | try {
53 | return $this->fire();
54 | } catch (DomainException $e) {
55 | return 1;
56 | } catch (Exception $e) {
57 | $this->error($e->getMessage());
58 | $this->error('Abort!');
59 |
60 | return $e->getCode();
61 | }
62 | }
63 |
64 | /**
65 | * Echo an error message.
66 | *
67 | * @param string$message
68 | */
69 | protected function error($message)
70 | {
71 | $this->output->writeln("{$message}");
72 | }
73 |
74 | /**
75 | * Echo an info.
76 | *
77 | * @param string $message
78 | */
79 | protected function info($message)
80 | {
81 | $this->output->writeln("{$message}");
82 | }
83 |
84 | /**
85 | * Echo a message.
86 | *
87 | * @param string $message
88 | */
89 | protected function message($message)
90 | {
91 | $this->output->writeln("{$message}");
92 | }
93 |
94 | /**
95 | * Execute the console command.
96 | *
97 | * @return null|int
98 | */
99 | abstract protected function fire();
100 | }
101 |
--------------------------------------------------------------------------------
/src/Commands/ArchiveCommand.php:
--------------------------------------------------------------------------------
1 | migrator = $migrator;
27 |
28 | parent::__construct($name);
29 | }
30 |
31 | /**
32 | * Configures the current command.
33 | */
34 | protected function configure()
35 | {
36 | $this->setDescription('Move migration into archive')
37 | ->addOption('without', 'w', InputOption::VALUE_REQUIRED, 'Archive without last N migration');
38 | }
39 |
40 | /**
41 | * Execute the console command.
42 | *
43 | * @return null|int
44 | */
45 | protected function fire()
46 | {
47 | $files = $this->migrator->getAllMigrations();
48 | $without = $this->input->getOption('without') ?: 0;
49 | if ($without > 0) {
50 | $files = array_slice($files, 0, $without * -1);
51 | }
52 |
53 | $count = $this->migrator->moveMigrationFiles($files);
54 |
55 | if ($count) {
56 | $this->message("Moved to archive: {$count}");
57 | } else {
58 | $this->info('Nothing to move');
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Commands/InstallCommand.php:
--------------------------------------------------------------------------------
1 | table = $table;
35 | $this->database = $database;
36 |
37 | parent::__construct($name);
38 | }
39 |
40 | /**
41 | * Configures the current command.
42 | */
43 | protected function configure()
44 | {
45 | $this->setDescription('Create the migration database table');
46 | }
47 |
48 | /**
49 | * Execute the console command.
50 | *
51 | * @return null|int
52 | */
53 | protected function fire()
54 | {
55 | if ($this->database->checkMigrationTableExistence()) {
56 | $this->abort("Table \"{$this->table}\" already exists");
57 | }
58 |
59 | $this->database->createMigrationTable();
60 |
61 | $this->info('Migration table has been successfully created!');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Commands/MakeCommand.php:
--------------------------------------------------------------------------------
1 | migrator = $migrator;
29 |
30 | parent::__construct($name);
31 | }
32 |
33 | /**
34 | * Configures the current command.
35 | */
36 | protected function configure()
37 | {
38 | $this->setDescription('Create a new migration file')
39 | ->addArgument(
40 | 'name',
41 | InputArgument::REQUIRED,
42 | 'The name of the migration'
43 | )
44 | ->addOption(
45 | 'template',
46 | 't',
47 | InputOption::VALUE_REQUIRED,
48 | 'Migration template'
49 | )
50 | ->addOption(
51 | 'directory',
52 | 'd',
53 | InputOption::VALUE_REQUIRED,
54 | 'Migration directory'
55 | );
56 | }
57 |
58 | /**
59 | * Execute the console command.
60 | *
61 | * @return null|int
62 | */
63 | protected function fire()
64 | {
65 | $migration = $this->migrator->createMigration(
66 | $this->input->getArgument('name'),
67 | $this->input->getOption('template'),
68 | [],
69 | $this->input->getOption('directory')
70 | );
71 |
72 | $this->message("Migration created: {$migration}.php");
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Commands/MigrateCommand.php:
--------------------------------------------------------------------------------
1 | migrator = $migrator;
26 |
27 | parent::__construct($name);
28 | }
29 |
30 | /**
31 | * Configures the current command.
32 | */
33 | protected function configure()
34 | {
35 | $this->setDescription('Run all outstanding migrations');
36 | }
37 |
38 | /**
39 | * Execute the console command.
40 | *
41 | * @return null|int
42 | */
43 | protected function fire()
44 | {
45 | $toRun = $this->migrator->getMigrationsToRun();
46 |
47 | if (!empty($toRun)) {
48 | foreach ($toRun as $migration) {
49 | $this->migrator->runMigration($migration);
50 | $this->message("Migrated: {$migration}.php");
51 | }
52 | } else {
53 | $this->info('Nothing to migrate');
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Commands/RollbackCommand.php:
--------------------------------------------------------------------------------
1 | migrator = $migrator;
29 |
30 | parent::__construct($name);
31 | }
32 |
33 | /**
34 | * Configures the current command.
35 | */
36 | protected function configure()
37 | {
38 | $this->setDescription('Rollback the last migration')
39 | ->addOption('hard', null, InputOption::VALUE_NONE, 'Rollback without running down()')
40 | ->addOption('delete', null, InputOption::VALUE_NONE, 'Delete migration file after rolling back');
41 | }
42 |
43 | /**
44 | * Execute the console command.
45 | *
46 | * @return null|int
47 | */
48 | protected function fire()
49 | {
50 | $ran = $this->migrator->getRanMigrations();
51 |
52 | if (empty($ran)) {
53 | return $this->info('Nothing to rollback');
54 | }
55 |
56 | $migration = $ran[count($ran) - 1];
57 |
58 | $this->input->getOption('hard')
59 | ? $this->hardRollbackMigration($migration)
60 | : $this->rollbackMigration($migration);
61 |
62 | return $this->deleteIfNeeded($migration);
63 | }
64 |
65 | /**
66 | * Call rollback.
67 | *
68 | * @param $migration
69 | *
70 | * @return null
71 | */
72 | protected function rollbackMigration($migration)
73 | {
74 | if ($this->migrator->doesMigrationFileExist($migration)) {
75 | $this->migrator->rollbackMigration($migration);
76 | } else {
77 | $this->markRolledBackWithConfirmation($migration);
78 | }
79 |
80 | $this->message("Rolled back: {$migration}.php");
81 | }
82 |
83 | /**
84 | * Call hard rollback.
85 | *
86 | * @param $migration
87 | *
88 | * @return null
89 | */
90 | protected function hardRollbackMigration($migration)
91 | {
92 | $this->migrator->removeSuccessfulMigrationFromLog($migration);
93 |
94 | $this->message("Rolled back with --hard: {$migration}.php");
95 | }
96 |
97 | /**
98 | * Ask a user to confirm rolling back non-existing migration and remove it from log.
99 | *
100 | * @param $migration
101 | *
102 | * @return void
103 | */
104 | protected function markRolledBackWithConfirmation($migration)
105 | {
106 | $helper = $this->getHelper('question');
107 | $question = new ConfirmationQuestion("Migration $migration was not found.\r\nDo you want to mark it as rolled back? (y/n)\r\n", false);
108 |
109 | if (!$helper->ask($this->input, $this->output, $question)) {
110 | $this->abort();
111 | }
112 |
113 | $this->migrator->removeSuccessfulMigrationFromLog($migration);
114 | }
115 |
116 | /**
117 | * Delete migration file if options is set
118 | *
119 | * @param string $migration
120 | *
121 | * @return null
122 | */
123 | protected function deleteIfNeeded($migration)
124 | {
125 | if (!$this->input->getOption('delete')) {
126 | return;
127 | }
128 |
129 | if ($this->migrator->deleteMigrationFile($migration)) {
130 | $this->message("Deleted: {$migration}.php");
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Commands/StatusCommand.php:
--------------------------------------------------------------------------------
1 | migrator = $migrator;
27 |
28 | parent::__construct($name);
29 | }
30 |
31 | /**
32 | * Configures the current command.
33 | */
34 | protected function configure()
35 | {
36 | $this->setDescription('Show status about last migrations');
37 | }
38 |
39 | /**
40 | * Execute the console command.
41 | *
42 | * @return null|int
43 | */
44 | protected function fire()
45 | {
46 | $this->showOldMigrations();
47 |
48 | $this->output->write("\r\n");
49 |
50 | $this->showNewMigrations();
51 | }
52 |
53 | /**
54 | * Show old migrations.
55 | *
56 | * @return void
57 | */
58 | protected function showOldMigrations()
59 | {
60 | $old = collect($this->migrator->getRanMigrations());
61 |
62 | $this->output->writeln("Old migrations:\r\n>");
63 |
64 | $max = 5;
65 | if ($old->count() > $max) {
66 | $this->output->writeln('...>');
67 |
68 | $old = $old->take(-$max);
69 | }
70 |
71 | foreach ($old as $migration) {
72 | $this->output->writeln("{$migration}.php>");
73 | }
74 | }
75 |
76 | /**
77 | * Show new migrations.
78 | *
79 | * @return void
80 | */
81 | protected function showNewMigrations()
82 | {
83 | $new = collect($this->migrator->getMigrationsToRun());
84 |
85 | $this->output->writeln("New migrations:\r\n>");
86 |
87 | foreach ($new as $migration) {
88 | $this->output->writeln("{$migration}.php>");
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Commands/TemplatesCommand.php:
--------------------------------------------------------------------------------
1 | collection = $collection;
29 |
30 | parent::__construct($name);
31 | }
32 |
33 | /**
34 | * Configures the current command.
35 | */
36 | protected function configure()
37 | {
38 | $this->setDescription('Show the list of available migration templates');
39 | }
40 |
41 | /**
42 | * Execute the console command.
43 | *
44 | * @return null|int
45 | */
46 | protected function fire()
47 | {
48 | $table = new Table($this->output);
49 | $table->setHeaders(['Name', 'Path', 'Description'])->setRows($this->collectRows());
50 | $table->setStyle('borderless');
51 | $table->render();
52 | }
53 |
54 | /**
55 | * Collect and return templates from a Migrator.
56 | *
57 | * @return array
58 | */
59 | protected function collectRows()
60 | {
61 | $rows = collect($this->collection->all())
62 | ->filter(function ($template) {
63 | return $template['is_alias'] == false;
64 | })
65 | ->sortBy('name')
66 | ->map(function ($template) {
67 | $row = [];
68 |
69 | $names = array_merge([$template['name']], $template['aliases']);
70 | $row[] = implode("\n/ ", $names);
71 | $row[] = wordwrap($template['path'], 65, "\n", true);
72 | $row[] = wordwrap($template['description'], 25, "\n", true);
73 |
74 | return $row;
75 | });
76 |
77 | return $this->separateRows($rows);
78 | }
79 |
80 | /**
81 | * Separate rows with a separator.
82 | *
83 | * @param $templates
84 | *
85 | * @return array
86 | */
87 | protected function separateRows($templates)
88 | {
89 | $rows = [];
90 | foreach ($templates as $template) {
91 | $rows[] = $template;
92 | $rows[] = new TableSeparator();
93 | }
94 | unset($rows[count($rows) - 1]);
95 |
96 | return $rows;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Constructors/Constructor.php:
--------------------------------------------------------------------------------
1 | fields);
20 | }
21 | }
--------------------------------------------------------------------------------
/src/Constructors/HighloadBlock.php:
--------------------------------------------------------------------------------
1 | getFieldsWithDefault());
25 |
26 | if (!$result->isSuccess()) {
27 | throw new \Exception(join(', ', $result->getErrorMessages()));
28 | }
29 |
30 | foreach ($this->lang as $lid => $name) {
31 | HighloadBlockLangTable::add([
32 | "ID" => $result->getId(),
33 | "LID" => $lid,
34 | "NAME" => $name
35 | ]);
36 | }
37 |
38 | Logger::log("Добавлен HL {$this->fields['NAME']}", Logger::COLOR_GREEN);
39 |
40 | return $result->getId();
41 | }
42 |
43 | /**
44 | * Обновить HL
45 | * @param $table_name
46 | * @throws \Exception
47 | */
48 | public function update($table_name)
49 | {
50 | $id = Helpers::getHlId($table_name);
51 | $result = HighloadBlockTable::update($id, $this->fields);
52 |
53 | if (!$result->isSuccess()) {
54 | throw new \Exception(join(', ', $result->getErrorMessages()));
55 | }
56 |
57 | Logger::log("Обновлен HL {$table_name}", Logger::COLOR_GREEN);
58 | }
59 |
60 | /**
61 | * Удалить HL
62 | * @param $table_name
63 | * @throws \Exception
64 | */
65 | public static function delete($table_name)
66 | {
67 | $id = Helpers::getHlId($table_name);
68 | $result = HighloadBlockTable::delete($id);
69 |
70 | if (!$result->isSuccess()) {
71 | throw new \Exception(join(', ', $result->getErrorMessages()));
72 | }
73 |
74 | Logger::log("Удален HL {$table_name}", Logger::COLOR_GREEN);
75 | }
76 |
77 | /**
78 | * Установить настройки для добавления HL по умолчанию
79 | * @param string $name Название highload-блока
80 | * @param string $table_name Название таблицы с элементами highload-блока.
81 | * @return $this
82 | */
83 | public function constructDefault($name, $table_name)
84 | {
85 | return $this->setName($name)->setTableName($table_name);
86 | }
87 |
88 | /**
89 | * Название highload-блока.
90 | * @param string $name
91 | * @return $this
92 | */
93 | public function setName($name)
94 | {
95 | $this->fields['NAME'] = $name;
96 |
97 | return $this;
98 | }
99 |
100 | /**
101 | * Название таблицы с элементами highload-блока.
102 | * @param string $table_name
103 | * @return $this
104 | */
105 | public function setTableName($table_name)
106 | {
107 | $this->fields['TABLE_NAME'] = $table_name;
108 |
109 | return $this;
110 | }
111 |
112 | /**
113 | * Установить локализацию
114 | * @param $lang
115 | * @param $text
116 | * @return HighloadBlock
117 | */
118 | public function setLang($lang, $text)
119 | {
120 | $this->lang[$lang] = $text;
121 |
122 | return $this;
123 | }
124 | }
--------------------------------------------------------------------------------
/src/Constructors/IBlock.php:
--------------------------------------------------------------------------------
1 | Add($this->getFieldsWithDefault());
23 | if (!$iblockId) {
24 | throw new \Exception($obj->LAST_ERROR);
25 | }
26 |
27 | Logger::log("Добавлен инфоблок {$this->fields['CODE']}", Logger::COLOR_GREEN);
28 |
29 | return $iblockId;
30 | }
31 |
32 | /**
33 | * Обновить инфоблок
34 | * @param $id
35 | * @throws \Exception
36 | */
37 | public function update($id)
38 | {
39 | $obj = new \CIBlock();
40 | if (!$obj->Update($id, $this->fields)) {
41 | throw new \Exception($obj->LAST_ERROR);
42 | }
43 |
44 | Logger::log("Обновлен инфоблок {$id}", Logger::COLOR_GREEN);
45 | }
46 |
47 | /**
48 | * Удалить инфоблок
49 | * @param $id
50 | * @throws \Exception
51 | */
52 | public static function delete($id)
53 | {
54 | if (!\CIBlock::Delete($id)) {
55 | throw new \Exception('Ошибка при удалении инфоблока');
56 | }
57 |
58 | Logger::log("Удален инфоблок {$id}", Logger::COLOR_GREEN);
59 | }
60 |
61 | /**
62 | * Установить настройки для добавления инфоблока по умолчанию
63 | * @param $name
64 | * @param $code
65 | * @param $iblock_type_id
66 | * @return $this
67 | */
68 | public function constructDefault($name, $code, $iblock_type_id)
69 | {
70 | return $this->setName($name)->setCode($code)->setIblockTypeId($iblock_type_id);
71 | }
72 |
73 | /**
74 | * ID сайта.
75 | * @param string $siteId
76 | * @return $this
77 | */
78 | public function setSiteId($siteId)
79 | {
80 | $this->fields['SITE_ID'] = $siteId;
81 |
82 | return $this;
83 | }
84 |
85 | /**
86 | * Символьный идентификатор.
87 | * @param string $code
88 | * @return $this
89 | */
90 | public function setCode($code)
91 | {
92 | $this->fields['CODE'] = $code;
93 |
94 | return $this;
95 | }
96 |
97 | /**
98 | * Внешний код.
99 | * @param string $xml_id
100 | * @return $this
101 | */
102 | public function setXmlId($xml_id)
103 | {
104 | $this->fields['XML_ID'] = $xml_id;
105 |
106 | return $this;
107 | }
108 |
109 | /**
110 | * Код типа инфоблока
111 | * @param string $iblockTypeId
112 | * @return $this
113 | */
114 | public function setIblockTypeId($iblockTypeId)
115 | {
116 | $this->fields['IBLOCK_TYPE_ID'] = $iblockTypeId;
117 |
118 | return $this;
119 | }
120 |
121 | /**
122 | * Название.
123 | * @param string $name
124 | * @return $this
125 | */
126 | public function setName($name)
127 | {
128 | $this->fields['NAME'] = $name;
129 |
130 | return $this;
131 | }
132 |
133 | /**
134 | * Флаг активности
135 | * @param bool $active
136 | * @return $this
137 | */
138 | public function setActive($active = true)
139 | {
140 | $this->fields['ACTIVE'] = $active ? 'Y' : 'N';
141 |
142 | return $this;
143 | }
144 |
145 | /**
146 | * Индекс сортировки.
147 | * @param int $sort
148 | * @return $this
149 | */
150 | public function setSort($sort = 500)
151 | {
152 | $this->fields['SORT'] = $sort;
153 |
154 | return $this;
155 | }
156 |
157 | /**
158 | * Шаблон URL-а к странице для публичного просмотра списка элементов информационного блока.
159 | * @param string $listPageUrl
160 | * @return $this
161 | */
162 | public function setListPageUrl($listPageUrl)
163 | {
164 | $this->fields['LIST_PAGE_URL'] = $listPageUrl;
165 |
166 | return $this;
167 | }
168 |
169 | /**
170 | * Шаблон URL-а к странице для просмотра раздела.
171 | * @param string $sectionPageUrl
172 | * @return $this
173 | */
174 | public function setSectionPageUrl($sectionPageUrl)
175 | {
176 | $this->fields['SECTION_PAGE_URL'] = $sectionPageUrl;
177 |
178 | return $this;
179 | }
180 |
181 | /**
182 | * Канонический URL элемента.
183 | * @param string $canonicalPageUrl
184 | * @return $this
185 | */
186 | public function setCanonicalPageUrl($canonicalPageUrl)
187 | {
188 | $this->fields['CANONICAL_PAGE_URL'] = $canonicalPageUrl;
189 |
190 | return $this;
191 | }
192 |
193 | /**
194 | * URL детальной страницы элемента.
195 | *
196 | * @param string $detailPageUrl
197 | *
198 | * @return $this
199 | */
200 | public function setDetailPageUrl($detailPageUrl)
201 | {
202 | $this->fields['DETAIL_PAGE_URL'] = $detailPageUrl;
203 |
204 | return $this;
205 | }
206 |
207 | /**
208 | * Устанавливает значения по умолчанию для страниц инфоблока, раздела и деталей элемента
209 | * (как при создании через административный интерфейс или с ЧПУ).
210 | *
211 | * Для использовании ЧПУ рекомендуется сделать обязательными для заполнения символьный код
212 | * элементов и разделов инфоблока.
213 | *
214 | * @param bool sef Использовать ли ЧПУ (понадобится добавить правило в urlrewrite)
215 | *
216 | * @return IBlock
217 | */
218 | public function setDefaultUrls($sef = false)
219 | {
220 | if ($sef === true) {
221 | $prefix = "#SITE_DIR#/#IBLOCK_TYPE_ID#/#IBLOCK_CODE#/";
222 | $this
223 | ->setListPageUrl($prefix)
224 | ->setSectionPageUrl("$prefix#SECTION_CODE_PATH#/")
225 | ->setDetailPageUrl("$prefix#SECTION_CODE_PATH#/#ELEMENT_CODE#/");
226 | } else {
227 | $prefix = "#SITE_DIR#/#IBLOCK_TYPE_ID#";
228 | $this
229 | ->setListPageUrl("$prefix/index.php?ID=#IBLOCK_ID#")
230 | ->setSectionPageUrl("$prefix/list.php?SECTION_ID=#SECTION_ID#")
231 | ->setDetailPageUrl("$prefix/detail.php?ID=#ELEMENT_ID#");
232 | }
233 |
234 | return $this;
235 | }
236 |
237 | /**
238 | * Код картинки в таблице файлов.
239 | * @param array $picture
240 | * @return $this
241 | */
242 | public function setPicture($picture)
243 | {
244 | $this->fields['PICTURE'] = $picture;
245 |
246 | return $this;
247 | }
248 |
249 | /**
250 | * Описание.
251 | * @param string $description
252 | * @return $this
253 | */
254 | public function setDescription($description)
255 | {
256 | $this->fields['DESCRIPTION'] = $description;
257 |
258 | return $this;
259 | }
260 |
261 | /**
262 | * Тип описания (text/html)
263 | * @param string $descriptionType
264 | * @return $this
265 | */
266 | public function setDescriptionType($descriptionType = 'text')
267 | {
268 | $this->fields['DESCRIPTION_TYPE'] = $descriptionType;
269 |
270 | return $this;
271 | }
272 |
273 | /**
274 | * Разрешен экспорт в RSS динамически
275 | * @param bool $rssActive
276 | * @return $this
277 | */
278 | public function setRssActive($rssActive = true)
279 | {
280 | $this->fields['RSS_ACTIVE'] = $rssActive ? 'Y' : 'N';
281 |
282 | return $this;
283 | }
284 |
285 | /**
286 | * Время жизни RSS и интервал между генерациями файлов RSS (при включенном RSS_FILE_ACTIVE или RSS_YANDEX_ACTIVE) (часов).
287 | * @param int $rssTtl
288 | * @return $this
289 | */
290 | public function setRssTtl($rssTtl = 24)
291 | {
292 | $this->fields['RSS_TTL'] = $rssTtl;
293 |
294 | return $this;
295 | }
296 |
297 | /**
298 | * Прегенерировать выгрузку в файл.
299 | * @param bool $rssFileActive
300 | * @return $this
301 | */
302 | public function setRssFileActive($rssFileActive = false)
303 | {
304 | $this->fields['RSS_FILE_ACTIVE'] = $rssFileActive ? 'Y' : 'N';
305 |
306 | return $this;
307 | }
308 |
309 | /**
310 | * Количество экспортируемых в RSS файл элементов (при включенном RSS_FILE_ACTIVE)
311 | * @param int $rssFileLimit
312 | * @return $this
313 | */
314 | public function setRssFileLimit($rssFileLimit)
315 | {
316 | $this->fields['RSS_FILE_LIMIT'] = $rssFileLimit;
317 |
318 | return $this;
319 | }
320 |
321 | /**
322 | * За сколько последних дней экспортировать в RSS файл. (при включенном RSS_FILE_ACTIVE). -1 без ограничения по дням.
323 | * @param int $rssFileDays
324 | * @return $this
325 | */
326 | public function setRssFileDays($rssFileDays)
327 | {
328 | $this->fields['RSS_FILE_DAYS'] = $rssFileDays;
329 |
330 | return $this;
331 | }
332 |
333 | /**
334 | * Экспортировать в RSS файл в формате для yandex
335 | * @param bool $rssYandexActive
336 | * @return $this
337 | */
338 | public function setRssYandexActive($rssYandexActive = false)
339 | {
340 | $this->fields['RSS_YANDEX_ACTIVE'] = $rssYandexActive ? 'Y' : 'N';
341 |
342 | return $this;
343 | }
344 |
345 | /**
346 | * Индексировать для поиска элементы информационного блока.
347 | * @param bool $indexElement
348 | * @return $this
349 | */
350 | public function setIndexElement($indexElement = true)
351 | {
352 | $this->fields['INDEX_ELEMENT'] = $indexElement ? 'Y' : 'N';
353 |
354 | return $this;
355 | }
356 |
357 | /**
358 | * Индексировать для поиска разделы информационного блока.
359 | * @param bool $indexSection
360 | * @return $this
361 | */
362 | public function setIndexSection($indexSection = false)
363 | {
364 | $this->fields['INDEX_SECTION'] = $indexSection ? 'Y' : 'N';
365 |
366 | return $this;
367 | }
368 |
369 | /**
370 | * Режим отображения списка элементов в административном разделе (S|C).
371 | * @param string $listMode
372 | * @return $this
373 | */
374 | public function setListMode($listMode)
375 | {
376 | $this->fields['LIST_MODE'] = $listMode;
377 |
378 | return $this;
379 | }
380 |
381 | /**
382 | * Режим проверки прав доступа (S|E).
383 | * @param string $rightsMode
384 | * @return $this
385 | */
386 | public function setRightsMode($rightsMode = 'S')
387 | {
388 | $this->fields['RIGHTS_MODE'] = $rightsMode;
389 |
390 | return $this;
391 | }
392 |
393 | /**
394 | * Признак наличия привязки свойств к разделам (Y|N).
395 | * @param string $sectionProperty
396 | * @return $this
397 | */
398 | public function setSectionProperty($sectionProperty)
399 | {
400 | $this->fields['SECTION_PROPERTY'] = $sectionProperty;
401 |
402 | return $this;
403 | }
404 |
405 | /**
406 | * Признак наличия фасетного индекса (N|Y|I).
407 | * @param string $propertyIndex
408 | * @return $this
409 | */
410 | public function setPropertyIndex($propertyIndex)
411 | {
412 | $this->fields['PROPERTY_INDEX'] = $propertyIndex;
413 |
414 | return $this;
415 | }
416 |
417 | /**
418 | * Служебное поле для процедуры конвертации места хранения значений свойств инфоблока.
419 | * @param int $lastConvElement
420 | * @return $this
421 | */
422 | public function setLastConvElement($lastConvElement)
423 | {
424 | $this->fields['LAST_CONV_ELEMENT'] = $lastConvElement;
425 |
426 | return $this;
427 | }
428 |
429 | /**
430 | * Служебное поле для установки прав для разных групп на доступ к информационному блоку.
431 | * @param array $groupId Массив соответствий кодов групп правам доступа
432 | * @return $this
433 | */
434 | public function setGroupId($groupId)
435 | {
436 | $this->fields['GROUP_ID'] = $groupId;
437 |
438 | return $this;
439 | }
440 |
441 | /**
442 | * Служебное поле для привязки к группе социальной сети.
443 | * @param int $socnetGroupId
444 | * @return $this
445 | */
446 | public function setSocnetGroupId($socnetGroupId)
447 | {
448 | $this->fields['SOCNET_GROUP_ID'] = $socnetGroupId;
449 |
450 | return $this;
451 | }
452 |
453 | /**
454 | * Инфоблок участвует в документообороте (Y|N).
455 | * @param bool $workflow
456 | * @return $this
457 | */
458 | public function setWorkflow($workflow = true)
459 | {
460 | $this->fields['WORKFLOW'] = $workflow ? 'Y' : 'N';
461 |
462 | return $this;
463 | }
464 |
465 | /**
466 | * Инфоблок участвует в бизнес-процессах (Y|N).
467 | * @param bool $bizproc
468 | * @return $this
469 | */
470 | public function setBizProc($bizproc = false)
471 | {
472 | $this->fields['BIZPROC'] = $bizproc ? 'Y' : 'N';
473 |
474 | return $this;
475 | }
476 |
477 | /**
478 | * Флаг выбора интерфейса отображения привязки элемента к разделам (D|L|P).
479 | * @param string $sectionChooser
480 | * @return $this
481 | */
482 | public function setSectionChooser($sectionChooser)
483 | {
484 | $this->fields['SECTION_CHOOSER'] = $sectionChooser;
485 |
486 | return $this;
487 | }
488 |
489 | /**
490 | * Флаг хранения значений свойств элементов инфоблока (1 - в общей таблице | 2 - в отдельной).
491 | * @param int $version
492 | * @return $this
493 | */
494 | public function setVersion($version = 1)
495 | {
496 | $this->fields['VERSION'] = $version;
497 |
498 | return $this;
499 | }
500 |
501 | /**
502 | * Полный путь к файлу-обработчику массива полей элемента перед сохранением на странице редактирования элемента.
503 | * @param string $editFileBefore
504 | * @return $this
505 | */
506 | public function setEditFileBefore($editFileBefore)
507 | {
508 | $this->fields['EDIT_FILE_BEFORE'] = $editFileBefore;
509 |
510 | return $this;
511 | }
512 |
513 | /**
514 | * Полный путь к файлу-обработчику вывода интерфейса редактирования элемента.
515 | * @param string $editFileAfter
516 | * @return $this
517 | */
518 | public function setEditFileAfter($editFileAfter)
519 | {
520 | $this->fields['EDIT_FILE_AFTER'] = $editFileAfter;
521 |
522 | return $this;
523 | }
524 |
525 | /**
526 | * Название элемента в единственном числе
527 | * @param string $message
528 | * @return $this
529 | */
530 | public function setMessElementName($message = 'Элемент')
531 | {
532 | $this->fields['ELEMENT_NAME'] = $message;
533 |
534 | return $this;
535 | }
536 |
537 | /**
538 | * Название элемента во множнственном числе
539 | * @param string $message
540 | * @return $this
541 | */
542 | public function setMessElementsName($message = 'Элементы')
543 | {
544 | $this->fields['ELEMENTS_NAME'] = $message;
545 |
546 | return $this;
547 | }
548 |
549 | /**
550 | * Действие по добавлению элемента
551 | * @param string $message
552 | * @return $this
553 | */
554 | public function setMessElementAdd($message = 'Добавить элемент')
555 | {
556 | $this->fields['ELEMENT_ADD'] = $message;
557 |
558 | return $this;
559 | }
560 |
561 | /**
562 | * Действие по редактированию/изменению элемента
563 | * @param string $message
564 | * @return $this
565 | */
566 | public function setMessElementEdit($message = 'Изменить элемент')
567 | {
568 | $this->fields['ELEMENT_EDIT'] = $message;
569 |
570 | return $this;
571 | }
572 |
573 | /**
574 | * Действие по удалению элемента
575 | * @param string $message
576 | * @return $this
577 | */
578 | public function setMessElementDelete($message = 'Удалить элемент')
579 | {
580 | $this->fields['ELEMENT_DELETE'] = $message;
581 |
582 | return $this;
583 | }
584 |
585 | /**
586 | * Название раздела в единственном числе
587 | * @param string $message
588 | * @return $this
589 | */
590 | public function setMessSectionName($message = 'Раздел')
591 | {
592 | $this->fields['SECTION_NAME'] = $message;
593 |
594 | return $this;
595 | }
596 |
597 | /**
598 | * Название раздела во множнственном числе
599 | * @param string $message
600 | * @return $this
601 | */
602 | public function setMessSectionsName($message = 'Разделы')
603 | {
604 | $this->fields['SECTIONS_NAME'] = $message;
605 |
606 | return $this;
607 | }
608 |
609 | /**
610 | * Действие по добавлению раздела
611 | * @param string $message
612 | * @return $this
613 | */
614 | public function setMessSectionAdd($message = 'Добавить раздел')
615 | {
616 | $this->fields['SECTION_ADD'] = $message;
617 |
618 | return $this;
619 | }
620 |
621 | /**
622 | * Действие по редактированию/изменению раздела
623 | * @param string $message
624 | * @return $this
625 | */
626 | public function setMessSectionEdit($message = 'Изменить раздел')
627 | {
628 | $this->fields['SECTION_EDIT'] = $message;
629 |
630 | return $this;
631 | }
632 |
633 | /**
634 | * Действие по удалению раздела
635 | * @param string $message
636 | * @return $this
637 | */
638 | public function setMessSectionDelete($message = 'Удалить раздел')
639 | {
640 | $this->fields['SECTION_DELETE'] = $message;
641 |
642 | return $this;
643 | }
644 |
645 |
646 | }
647 |
--------------------------------------------------------------------------------
/src/Constructors/IBlockProperty.php:
--------------------------------------------------------------------------------
1 | Add($this->getFieldsWithDefault());
23 |
24 | if (!$property_id) {
25 | throw new \Exception($obj->LAST_ERROR);
26 | }
27 |
28 | Logger::log("Добавлено свойство инфоблока {$this->fields['CODE']}", Logger::COLOR_GREEN);
29 |
30 | return $property_id;
31 | }
32 |
33 | /**
34 | * Обновить свойство инфоблока
35 | * @param $id
36 | * @throws \Exception
37 | */
38 | public function update($id)
39 | {
40 | $obj = new \CIBlockProperty();
41 | if (!$obj->Update($id, $this->fields)) {
42 | throw new \Exception($obj->LAST_ERROR);
43 | }
44 |
45 | Logger::log("Обновлено свойство инфоблока {$id}", Logger::COLOR_GREEN);
46 | }
47 |
48 | /**
49 | * Удалить свойство инфоблока
50 | * @param $id
51 | * @throws \Exception
52 | */
53 | public static function delete($id)
54 | {
55 | if (!\CIBlockProperty::Delete($id)) {
56 | throw new \Exception('Ошибка при удалении свойства инфоблока');
57 | }
58 |
59 | Logger::log("Удалено свойство инфоблока {$id}", Logger::COLOR_GREEN);
60 | }
61 |
62 | /**
63 | * Установить настройки для добавления свойства инфоблока по умолчанию
64 | * @param string $code
65 | * @param string $name
66 | * @param int $iblockId
67 | * @return IBlockProperty
68 | */
69 | public function constructDefault($code, $name, $iblockId)
70 | {
71 | return $this->setPropertyType('S')->setCode($code)->setName($name)->setIblockId($iblockId);
72 | }
73 |
74 | /**
75 | * Символьный идентификатор.
76 | * @param string $code
77 | * @return $this
78 | */
79 | public function setCode($code)
80 | {
81 | $this->fields['CODE'] = $code;
82 |
83 | return $this;
84 | }
85 |
86 | /**
87 | * Внешний код.
88 | * @param string $xml_id
89 | * @return $this
90 | */
91 | public function setXmlId($xml_id)
92 | {
93 | $this->fields['XML_ID'] = $xml_id;
94 |
95 | return $this;
96 | }
97 |
98 | /**
99 | * Код информационного блока.
100 | * @param string $iblock_id
101 | * @return $this
102 | */
103 | public function setIblockId($iblock_id)
104 | {
105 | $this->fields['IBLOCK_ID'] = $iblock_id;
106 |
107 | return $this;
108 | }
109 |
110 | /**
111 | * Название.
112 | * @param string $name
113 | * @return $this
114 | */
115 | public function setName($name)
116 | {
117 | $this->fields['NAME'] = $name;
118 |
119 | return $this;
120 | }
121 |
122 | /**
123 | * Флаг активности
124 | * @param bool $active
125 | * @return $this
126 | */
127 | public function setActive($active = true)
128 | {
129 | $this->fields['ACTIVE'] = $active ? 'Y' : 'N';
130 |
131 | return $this;
132 | }
133 |
134 | /**
135 | * Обязательное (Y|N).
136 | * @param bool $isRequired
137 | * @return $this
138 | */
139 | public function setIsRequired($isRequired = true)
140 | {
141 | $this->fields['IS_REQUIRED'] = $isRequired ? 'Y' : 'N';
142 |
143 | return $this;
144 | }
145 |
146 | /**
147 | * Индекс сортировки.
148 | * @param int $sort
149 | * @return $this
150 | */
151 | public function setSort($sort = 500)
152 | {
153 | $this->fields['SORT'] = $sort;
154 |
155 | return $this;
156 | }
157 |
158 | /**
159 | * Тип свойства. Возможные значения: S - строка, N - число, F - файл, L - список, E - привязка к элементам, G - привязка к группам.
160 | * @param string $propertyType
161 | * @return $this
162 | */
163 | public function setPropertyType($propertyType = 'S')
164 | {
165 | $this->fields['PROPERTY_TYPE'] = $propertyType;
166 |
167 | return $this;
168 | }
169 |
170 | /**
171 | * Установить тип свойства "Список"
172 | * @param array $values массив доступных значений (можно собрать с помощью класса IBlockPropertyEnum)
173 | * @param string $listType Тип, может быть "L" - выпадающий список или "C" - флажки.
174 | * @param int $multipleCnt Количество строк в выпадающем списке
175 | * @return $this
176 | */
177 | public function setPropertyTypeList($values, $listType = null, $multipleCnt = null)
178 | {
179 | $this->setPropertyType('L');
180 | $this->fields['VALUES'] = $values;
181 |
182 | if (!is_null($listType)) {
183 | $this->setListType($listType);
184 | }
185 |
186 | if (!is_null($multipleCnt)) {
187 | $this->setMultipleCnt($multipleCnt);
188 | }
189 |
190 | return $this;
191 | }
192 |
193 | /**
194 | * Установить тип свойства "Файл"
195 | * @param string $fileType Список допустимых расширений (через запятую).
196 | * @return $this
197 | */
198 | public function setPropertyTypeFile($fileType = null)
199 | {
200 | $this->setPropertyType('F');
201 |
202 | if (!is_null($fileType)) {
203 | $this->setFileType($fileType);
204 | }
205 |
206 | return $this;
207 | }
208 |
209 | /**
210 | * Установить тип свойства "привязка к элементам" или "привязка к группам"
211 | * @param string $property_type Тип свойства. Возможные значения: E - привязка к элементам, G - привязка к группам.
212 | * @param string $linkIblockId код информационного блока с элементами/группами которого и будут связано значение.
213 | * @return $this
214 | */
215 | public function setPropertyTypeIblock($property_type, $linkIblockId)
216 | {
217 | $this->setPropertyType($property_type)->setLinkIblockId($linkIblockId);
218 |
219 | return $this;
220 | }
221 |
222 | /**
223 | * Установить тип свойства "справочник"
224 | * @param string $table_name таблица HL для связи
225 | * @return $this
226 | */
227 | public function setPropertyTypeHl($table_name)
228 | {
229 | $this->setPropertyType('S')->setUserType('directory')->setUserTypeSettings([
230 | 'TABLE_NAME' => $table_name
231 | ]);
232 |
233 | return $this;
234 | }
235 |
236 | /**
237 | * Множественность (Y|N).
238 | * @param bool $multiple
239 | * @return $this
240 | */
241 | public function setMultiple($multiple = false)
242 | {
243 | $this->fields['MULTIPLE'] = $multiple ? 'Y' : 'N';
244 |
245 | return $this;
246 | }
247 |
248 | /**
249 | * Количество строк в выпадающем списке для свойств типа "список".
250 | * @param int $multipleCnt
251 | * @return $this
252 | */
253 | public function setMultipleCnt($multipleCnt)
254 | {
255 | $this->fields['MULTIPLE_CNT'] = $multipleCnt;
256 |
257 | return $this;
258 | }
259 |
260 | /**
261 | * Значение свойства по умолчанию (кроме свойства типа список L).
262 | * @param string $defaultValue
263 | * @return $this
264 | */
265 | public function setDefaultValue($defaultValue)
266 | {
267 | $this->fields['DEFAULT_VALUE'] = $defaultValue;
268 |
269 | return $this;
270 | }
271 |
272 | /**
273 | * Количество строк в ячейке ввода значения свойства.
274 | * @param int $rowCount
275 | * @return $this
276 | */
277 | public function setRowCount($rowCount)
278 | {
279 | $this->fields['ROW_COUNT'] = $rowCount;
280 |
281 | return $this;
282 | }
283 |
284 | /**
285 | * Количество столбцов в ячейке ввода значения свойства.
286 | * @param int $colCount
287 | * @return $this
288 | */
289 | public function setColCount($colCount)
290 | {
291 | $this->fields['COL_COUNT'] = $colCount;
292 |
293 | return $this;
294 | }
295 |
296 | /**
297 | * Тип для свойства список (L). Может быть "L" - выпадающий список или "C" - флажки.
298 | * @param string $listType
299 | * @return $this
300 | */
301 | public function setListType($listType = 'L')
302 | {
303 | $this->fields['LIST_TYPE'] = $listType;
304 |
305 | return $this;
306 | }
307 |
308 | /**
309 | * Список допустимых расширений для свойств файл "F" (через запятую).
310 | * @param string $fileType
311 | * @return $this
312 | */
313 | public function setFileType($fileType)
314 | {
315 | $this->fields['FILE_TYPE'] = $fileType;
316 |
317 | return $this;
318 | }
319 |
320 | /**
321 | * Индексировать значения данного свойства.
322 | * @param bool $searchable
323 | * @return $this
324 | */
325 | public function setSearchable($searchable = false)
326 | {
327 | $this->fields['SEARCHABLE'] = $searchable ? 'Y' : 'N';
328 |
329 | return $this;
330 | }
331 |
332 | /**
333 | * Выводить поля для фильтрации по данному свойству на странице списка элементов в административном разделе.
334 | * @param bool $filtrable
335 | * @return $this
336 | */
337 | public function setFiltrable($filtrable = false)
338 | {
339 | $this->fields['FILTRABLE'] = $filtrable ? 'Y' : 'N';
340 |
341 | return $this;
342 | }
343 |
344 | /**
345 | * Для свойств типа привязки к элементам и группам задает код информационного блока с элементами/группами которого и будут связано значение.
346 | * @param int $linkIblockId
347 | * @return $this
348 | */
349 | public function setLinkIblockId($linkIblockId)
350 | {
351 | $this->fields['LINK_IBLOCK_ID'] = $linkIblockId;
352 |
353 | return $this;
354 | }
355 |
356 | /**
357 | * Признак наличия у значения свойства дополнительного поля описания. Только для типов S - строка, N - число и F - файл (Y|N).
358 | * @param bool $withDescription
359 | * @return $this
360 | */
361 | public function setWithDescription($withDescription)
362 | {
363 | $this->fields['WITH_DESCRIPTION'] = $withDescription ? 'Y' : 'N';
364 |
365 | return $this;
366 | }
367 |
368 | /**
369 | * Идентификатор пользовательского типа свойства.
370 | * @param string $user_type
371 | * @return $this
372 | */
373 | public function setUserType($user_type)
374 | {
375 | $this->fields['USER_TYPE'] = $user_type;
376 |
377 | return $this;
378 | }
379 |
380 | /**
381 | * Идентификатор пользовательского типа свойства.
382 | * @param array $user_type_settings
383 | * @return $this
384 | */
385 | public function setUserTypeSettings($user_type_settings)
386 | {
387 | $this->fields['USER_TYPE_SETTINGS'] = array_merge((array)$this->fields['USER_TYPE_SETTINGS'], $user_type_settings);
388 |
389 | return $this;
390 | }
391 |
392 | /**
393 | * Подсказка
394 | * @param string $hint
395 | * @return $this
396 | */
397 | public function setHint($hint)
398 | {
399 | $this->fields['HINT'] = $hint;
400 |
401 | return $this;
402 | }
403 | }
--------------------------------------------------------------------------------
/src/Constructors/IBlockPropertyEnum.php:
--------------------------------------------------------------------------------
1 | Add($this->getFieldsWithDefault());
23 |
24 | if (!$property_enum_id) {
25 | throw new \Exception("Ошибка добавления значения enum");
26 | }
27 |
28 | Logger::log("Добавлено значение списка enum {$this->fields['VALUE']}", Logger::COLOR_GREEN);
29 |
30 | return $property_enum_id;
31 | }
32 |
33 | /**
34 | * Обновить свойство инфоблока
35 | * @param $id
36 | * @throws \Exception
37 | */
38 | public function update($id)
39 | {
40 | $obj = new \CIBlockPropertyEnum();
41 | if (!$obj->Update($id, $this->fields)) {
42 | throw new \Exception("Ошибка обновления значения enum");
43 | }
44 |
45 | Logger::log("Обновлено значение списка enum {$id}", Logger::COLOR_GREEN);
46 | }
47 |
48 | /**
49 | * Удалить свойство инфоблока
50 | * @param $id
51 | * @throws \Exception
52 | */
53 | public static function delete($id)
54 | {
55 | if (!\CIBlockPropertyEnum::Delete($id)) {
56 | throw new \Exception('Ошибка при удалении значения enum');
57 | }
58 |
59 | Logger::log("Удалено значение списка enum {$id}", Logger::COLOR_GREEN);
60 | }
61 |
62 | /**
63 | * Установить настройки для добавления значения enum инфоблока по умолчанию
64 | * @param string $xml_id
65 | * @param string $value
66 | * @param int $propertyId
67 | * @return $this
68 | */
69 | public function constructDefault($xml_id, $value, $propertyId = null)
70 | {
71 | $this->setXmlId($xml_id)->setValue($value);
72 |
73 | if ($propertyId) {
74 | $this->setPropertyId($propertyId);
75 | }
76 |
77 | return $this;
78 | }
79 |
80 | /**
81 | * Код свойства.
82 | * @param string $propertyId
83 | * @return $this
84 | */
85 | public function setPropertyId($propertyId)
86 | {
87 | $this->fields['PROPERTY_ID'] = $propertyId;
88 |
89 | return $this;
90 | }
91 |
92 | /**
93 | * Внешний код.
94 | * @param string $xml_id
95 | * @return $this
96 | */
97 | public function setXmlId($xml_id)
98 | {
99 | $this->fields['XML_ID'] = $xml_id;
100 |
101 | return $this;
102 | }
103 |
104 | /**
105 | * Индекс сортировки.
106 | * @param int $sort
107 | * @return $this
108 | */
109 | public function setSort($sort = 500)
110 | {
111 | $this->fields['SORT'] = $sort;
112 |
113 | return $this;
114 | }
115 |
116 | /**
117 | * Значение варианта свойства.
118 | * @param string $value
119 | * @return $this
120 | */
121 | public function setValue($value)
122 | {
123 | $this->fields['VALUE'] = $value;
124 |
125 | return $this;
126 | }
127 |
128 | /**
129 | * Значение варианта свойства.
130 | * @param bool $def
131 | * @return $this
132 | */
133 | public function setDef($def)
134 | {
135 | $this->fields['DEF'] = $def ? 'Y' : 'N';
136 |
137 | return $this;
138 | }
139 | }
--------------------------------------------------------------------------------
/src/Constructors/IBlockType.php:
--------------------------------------------------------------------------------
1 | Add($this->getFieldsWithDefault())) {
22 | throw new \Exception($obj->LAST_ERROR);
23 | }
24 |
25 | Logger::log("Добавлен тип инфоблока {$this->fields['ID']}", Logger::COLOR_GREEN);
26 | }
27 |
28 | /**
29 | * Обновить тип инфоблока
30 | * @param $id
31 | * @throws \Exception
32 | */
33 | public function update($id)
34 | {
35 | $obj = new \CIBlockType();
36 | if (!$obj->Update($id, $this->fields)) {
37 | throw new \Exception($obj->LAST_ERROR);
38 | }
39 |
40 | Logger::log("Обновлен тип инфоблока {$id}", Logger::COLOR_GREEN);
41 | }
42 |
43 | /**
44 | * Удалить тип инфоблока
45 | * @param $id
46 | * @throws \Exception
47 | */
48 | public static function delete($id)
49 | {
50 | if (!\CIBlockType::Delete($id)) {
51 | throw new \Exception('Ошибка при удалении типа инфоблока');
52 | }
53 |
54 | Logger::log("Удален тип инфоблока {$id}", Logger::COLOR_GREEN);
55 | }
56 |
57 | /**
58 | * ID типа информационных блоков. Уникален.
59 | * @param string $id
60 | * @return $this
61 | */
62 | public function setId($id)
63 | {
64 | $this->fields['ID'] = $id;
65 |
66 | return $this;
67 | }
68 |
69 | /**
70 | * Разделяются ли элементы блока этого типа по разделам.
71 | * @param bool $has
72 | * @return $this
73 | */
74 | public function setSections($has = true)
75 | {
76 | $this->fields['SECTIONS'] = $has ? 'Y' : 'N';
77 |
78 | return $this;
79 | }
80 |
81 | /**
82 | * Полный путь к файлу-обработчику массива полей элемента перед сохранением на странице редактирования элемента.
83 | * @param string $editFileBefore
84 | * @return $this
85 | */
86 | public function setEditFileBefore($editFileBefore)
87 | {
88 | $this->fields['EDIT_FILE_BEFORE'] = $editFileBefore;
89 |
90 | return $this;
91 | }
92 |
93 | /**
94 | * Полный путь к файлу-обработчику вывода интерфейса редактирования элемента.
95 | * @param string $editFileAfter
96 | * @return $this
97 | */
98 | public function setEditFileAfter($editFileAfter)
99 | {
100 | $this->fields['EDIT_FILE_AFTER'] = $editFileAfter;
101 |
102 | return $this;
103 | }
104 |
105 | /**
106 | * Блоки данного типа экспортировать в RSS
107 | * @param bool $inRss
108 | * @return $this
109 | */
110 | public function setInRss($inRss = false)
111 | {
112 | $this->fields['IN_RSS'] = $inRss ? 'Y' : 'N';
113 |
114 | return $this;
115 | }
116 |
117 | /**
118 | * Порядок сортировки типа
119 | * @param int $sort
120 | * @return $this
121 | */
122 | public function setSort($sort = 500)
123 | {
124 | $this->fields['SORT'] = $sort;
125 |
126 | return $this;
127 | }
128 |
129 | /**
130 | * Указать языковые фразы
131 | * @param string $lang ключ языка (ru)
132 | * @param string $name
133 | * @param string $sectionName
134 | * @param string $elementName
135 | * @return $this
136 | */
137 | public function setLang($lang, $name, $sectionName = null, $elementName = null)
138 | {
139 | $setting = ['NAME' => $name];
140 |
141 | if ($sectionName) {
142 | $setting['SECTION_NAME'] = $sectionName;
143 | }
144 | if ($elementName) {
145 | $setting['ELEMENT_NAME'] = $elementName;
146 | }
147 |
148 | $this->fields['LANG'][$lang] = $setting;
149 |
150 | return $this;
151 | }
152 | }
--------------------------------------------------------------------------------
/src/Constructors/UserField.php:
--------------------------------------------------------------------------------
1 | Add($this->getFieldsWithDefault());
24 |
25 | if (!$result) {
26 | global $APPLICATION;
27 | throw new \Exception($APPLICATION->GetException());
28 | }
29 |
30 | Logger::log("Добавлен UF {$this->fields['FIELD_NAME']} для {$this->fields['ENTITY_ID']}", Logger::COLOR_GREEN);
31 |
32 | return $result;
33 | }
34 |
35 | /**
36 | * Обновить UF
37 | * @param $id
38 | * @throws \Exception
39 | */
40 | public function update($id)
41 | {
42 | $uf = new \CUserTypeEntity();
43 | $result = $uf->Update($id, $this->fields);
44 |
45 | if (!$result) {
46 | global $APPLICATION;
47 | throw new \Exception($APPLICATION->GetException());
48 | }
49 |
50 | Logger::log("Обновлен UF {$id}", Logger::COLOR_GREEN);
51 | }
52 |
53 | /**
54 | * Удалить UF
55 | * @param $id
56 | * @throws \Exception
57 | */
58 | public static function delete($id)
59 | {
60 | $result = (new \CUserTypeEntity())->Delete($id);
61 |
62 | if (!$result) {
63 | global $APPLICATION;
64 | throw new \Exception($APPLICATION->GetException());
65 | }
66 |
67 | Logger::log("Удален UF {$id}", Logger::COLOR_GREEN);
68 | }
69 |
70 | /**
71 | * Установить настройки для добавления UF по умолчанию
72 | * @param string $entityId Идентификатор сущности
73 | * @param string $fieldName Код поля.
74 | * @return $this
75 | */
76 | public function constructDefault($entityId, $fieldName)
77 | {
78 | return $this->setEntityId($entityId)->setFieldName($fieldName)->setUserType('string');
79 | }
80 |
81 | /**
82 | * Идентификатор сущности, к которой будет привязано свойство.
83 | * @param string $entityId
84 | * @return $this
85 | */
86 | public function setEntityId($entityId)
87 | {
88 | $this->fields['ENTITY_ID'] = $entityId;
89 |
90 | return $this;
91 | }
92 |
93 | /**
94 | * Код поля. Всегда должно начинаться с UF_
95 | * @param string $fieldName
96 | * @return $this
97 | */
98 | public function setFieldName($fieldName)
99 | {
100 | $this->fields['FIELD_NAME'] = static::prepareUf($fieldName);
101 |
102 | return $this;
103 | }
104 |
105 | /**
106 | * тип пользовательского свойства
107 | * @param string $userType
108 | * @return $this
109 | */
110 | public function setUserType($userType)
111 | {
112 | $this->fields['USER_TYPE_ID'] = $userType;
113 |
114 | return $this;
115 | }
116 |
117 | /**
118 | * тип нового пользовательского свойства HL
119 | * @param string $table_name
120 | * @param string $showField
121 | * @return $this
122 | */
123 | public function setUserTypeHL($table_name, $showField)
124 | {
125 | $linkId = Helpers::getHlId($table_name);
126 | $this->setUserType('hlblock')->setSettings([
127 | 'HLBLOCK_ID' => Helpers::getHlId($table_name),
128 | 'HLFIELD_ID' => Helpers::getFieldId(Constructor::objHLBlock($linkId), static::prepareUf($showField)),
129 | ]);
130 |
131 | return $this;
132 | }
133 |
134 | /**
135 | * тип нового пользовательского свойства "связь с разелом ИБ"
136 | * @param string $iblockId
137 | * @return $this
138 | */
139 | public function setUserTypeIblockSection($iblockId)
140 | {
141 | $this->setUserType('iblock_section')->setSettings([
142 | 'IBLOCK_ID' => $iblockId,
143 | ]);
144 |
145 | return $this;
146 | }
147 |
148 | /**
149 | * тип нового пользовательского свойства "связь с элементом ИБ"
150 | * @param string $iblockId
151 | * @return $this
152 | */
153 | public function setUserTypeIblockElement($iblockId)
154 | {
155 | $this->setUserType('iblock_element')->setSettings([
156 | 'IBLOCK_ID' => $iblockId,
157 | ]);
158 |
159 | return $this;
160 | }
161 |
162 | /**
163 | * XML_ID пользовательского свойства. Используется при выгрузке в качестве названия поля
164 | * @param string $xmlId
165 | * @return $this
166 | */
167 | public function setXmlId($xmlId)
168 | {
169 | $this->fields['XML_ID'] = $xmlId;
170 |
171 | return $this;
172 | }
173 |
174 | /**
175 | * Сортировка
176 | * @param int $sort
177 | * @return $this
178 | */
179 | public function setSort($sort)
180 | {
181 | $this->fields['SORT'] = $sort;
182 |
183 | return $this;
184 | }
185 |
186 | /**
187 | * Является поле множественным или нет
188 | * @param bool $multiple
189 | * @return $this
190 | */
191 | public function setMultiple($multiple)
192 | {
193 | $this->fields['MULTIPLE'] = $multiple ? 'Y' : 'N';
194 |
195 | return $this;
196 | }
197 |
198 | /**
199 | * Обязательное или нет свойство
200 | * @param bool $mandatory
201 | * @return $this
202 | */
203 | public function setMandatory($mandatory)
204 | {
205 | $this->fields['MANDATORY'] = $mandatory ? 'Y' : 'N';
206 |
207 | return $this;
208 | }
209 |
210 | /**
211 | * Показывать в фильтре списка. Возможные значения: не показывать = N, точное совпадение = I, поиск по маске = E, поиск по подстроке = S
212 | * @param string $showInFilter
213 | * @return $this
214 | */
215 | public function setShowFilter($showInFilter)
216 | {
217 | $this->fields['SHOW_FILTER'] = $showInFilter;
218 |
219 | return $this;
220 | }
221 |
222 | /**
223 | * Не показывать в списке. Если передать какое-либо значение, то будет считаться, что флаг выставлен.
224 | * @param bool $showInList
225 | * @return $this
226 | */
227 | public function setShowInList($showInList)
228 | {
229 | $this->fields['SHOW_IN_LIST'] = $showInList ? 'Y' : '';
230 |
231 | return $this;
232 | }
233 |
234 | /**
235 | * Пустая строка разрешает редактирование. Если передать какое-либо значение, то будет считаться, что флаг выставлен.
236 | * @param bool $editInList
237 | * @return $this
238 | */
239 | public function setEditInList($editInList)
240 | {
241 | $this->fields['EDIT_IN_LIST'] = $editInList ? 'Y' : '';
242 |
243 | return $this;
244 | }
245 |
246 | /**
247 | * Значения поля участвуют в поиске
248 | * @param bool $isSearchable
249 | * @return $this
250 | */
251 | public function setIsSearchable($isSearchable = false)
252 | {
253 | $this->fields['IS_SEARCHABLE'] = $isSearchable ? 'Y' : 'N';
254 |
255 | return $this;
256 | }
257 |
258 | /**
259 | * Дополнительные настройки поля (зависят от типа). В нашем случае для типа string
260 | * @param array $settings
261 | * @return $this
262 | */
263 | public function setSettings($settings)
264 | {
265 | $this->fields['SETTINGS'] = array_merge((array)$this->fields['SETTINGS'], $settings);
266 |
267 | return $this;
268 | }
269 |
270 | /**
271 | * Языковые фразы
272 | * @param string $lang
273 | * @param string $text
274 | * @return $this
275 | */
276 | public function setLangDefault($lang, $text)
277 | {
278 | $this->setLangForm($lang, $text);
279 | $this->setLangColumn($lang, $text);
280 | $this->setLangFilter($lang, $text);
281 |
282 | return $this;
283 | }
284 |
285 | /**
286 | * Текст "Заголовок в списке"
287 | * @param string $lang
288 | * @param string $text
289 | * @return $this
290 | */
291 | public function setLangForm($lang, $text)
292 | {
293 | $this->fields['EDIT_FORM_LABEL'][$lang] = $text;
294 |
295 | return $this;
296 | }
297 |
298 | /**
299 | * Текст "Заголовок в списке"
300 | * @param string $lang
301 | * @param string $text
302 | * @return $this
303 | */
304 | public function setLangColumn($lang, $text)
305 | {
306 | $this->fields['LIST_COLUMN_LABEL'][$lang] = $text;
307 |
308 | return $this;
309 | }
310 |
311 | /**
312 | * Текст "Подпись фильтра в списке"
313 | * @param string $lang
314 | * @param string $text
315 | * @return $this
316 | */
317 | public function setLangFilter($lang, $text)
318 | {
319 | $this->fields['LIST_FILTER_LABEL'][$lang] = $text;
320 |
321 | return $this;
322 | }
323 |
324 | /**
325 | * Текст "Помощь"
326 | * @param string $lang
327 | * @param string $text
328 | * @return $this
329 | */
330 | public function setLangHelp($lang, $text)
331 | {
332 | $this->fields['HELP_MESSAGE'][$lang] = $text;
333 |
334 | return $this;
335 | }
336 |
337 | /**
338 | * Текст "Сообщение об ошибке (не обязательное)"
339 | * @param string $lang
340 | * @param string $text
341 | * @return $this
342 | */
343 | public function setLangError($lang, $text)
344 | {
345 | $this->fields['ERROR_MESSAGE'][$lang] = $text;
346 |
347 | return $this;
348 | }
349 |
350 | protected static function prepareUf($name)
351 | {
352 | if (substr($name, 0, 3) != 'UF_') {
353 | $name = "UF_{$name}";
354 | }
355 |
356 | return $name;
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/src/Exceptions/MigrationException.php:
--------------------------------------------------------------------------------
1 | query('SELECT `ID`, `NAME`, `TABLE_NAME` FROM b_hlblock_entity');
48 | while ($block = $dbRes->fetch()) {
49 | static::$hls[$block['TABLE_NAME']] = $block;
50 | }
51 | }
52 |
53 | return static::$hls[$table_name]['ID'];
54 | }
55 |
56 | /**
57 | * Получить ID UF
58 | * @param $obj
59 | * @param $field_name
60 | * @return mixed
61 | */
62 | public static function getFieldId($obj, $field_name)
63 | {
64 | if (!isset(static::$ufs[$obj][$field_name])) {
65 | $dbRes = Application::getConnection()->query('SELECT * FROM b_user_field');
66 | while ($uf = $dbRes->fetch()) {
67 | static::$ufs[$uf['ENTITY_ID']][$uf['FIELD_NAME']] = $uf;
68 | }
69 | }
70 |
71 | return static::$ufs[$obj][$field_name]['ID'];
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Interfaces/DatabaseStorageInterface.php:
--------------------------------------------------------------------------------
1 | config = $config;
78 | $this->dir = $config['dir'];
79 | $this->dir_archive = isset($config['dir_archive']) ? $config['dir_archive'] : 'archive';
80 | $this->use_transaction = isset($config['use_transaction']) ? $config['use_transaction'] : false;
81 |
82 | if (isset($config['default_fields']) && is_array($config['default_fields'])) {
83 | foreach ($config['default_fields'] as $class => $default_fields) {
84 | FieldConstructor::$defaultFields[$class] = $default_fields;
85 | }
86 | }
87 |
88 | $this->templates = $templates;
89 | $this->database = $database ?: new BitrixDatabaseStorage($config['table']);
90 | $this->files = $files ?: new FileStorage();
91 | }
92 |
93 | /**
94 | * Create migration file.
95 | *
96 | * @param string $name - migration name
97 | * @param string $templateName
98 | * @param array $replace - array of placeholders that should be replaced with a given values.
99 | * @param string $subDir
100 | *
101 | * @return string
102 | */
103 | public function createMigration($name, $templateName, array $replace = [], $subDir = '')
104 | {
105 | $targetDir = $this->dir;
106 | $subDir = trim(str_replace('\\', '/', $subDir), '/');
107 | if ($subDir) {
108 | $targetDir .= '/' . $subDir;
109 | }
110 |
111 | $this->files->createDirIfItDoesNotExist($targetDir);
112 |
113 | $fileName = $this->constructFileName($name);
114 | $className = $this->getMigrationClassNameByFileName($fileName);
115 | $templateName = $this->templates->selectTemplate($templateName);
116 |
117 | $template = $this->files->getContent($this->templates->getTemplatePath($templateName));
118 | $template = $this->replacePlaceholdersInTemplate($template, array_merge($replace, ['className' => $className]));
119 |
120 | $this->files->putContent($targetDir.'/'.$fileName.'.php', $template);
121 |
122 | return $fileName;
123 | }
124 |
125 | /**
126 | * Run all migrations that were not run before.
127 | */
128 | public function runMigrations()
129 | {
130 | $migrations = $this->getMigrationsToRun();
131 | $ran = [];
132 |
133 | if (empty($migrations)) {
134 | return $ran;
135 | }
136 |
137 | foreach ($migrations as $migration) {
138 | $this->runMigration($migration);
139 | $ran[] = $migration;
140 | }
141 |
142 | return $ran;
143 | }
144 |
145 | /**
146 | * Run a given migration.
147 | *
148 | * @param string $file
149 | *
150 | * @throws Exception
151 | *
152 | * @return string
153 | */
154 | public function runMigration($file)
155 | {
156 | $migration = $this->getMigrationObjectByFileName($file);
157 |
158 | $this->disableBitrixIblockHelperCache();
159 |
160 | $this->checkTransactionAndRun($migration, function () use ($migration, $file) {
161 | if ($migration->up() === false) {
162 | throw new Exception("Migration up from {$file}.php returned false");
163 | }
164 | });
165 |
166 | $this->logSuccessfulMigration($file);
167 | }
168 |
169 | /**
170 | * Log successful migration.
171 | *
172 | * @param string $migration
173 | *
174 | * @return void
175 | */
176 | public function logSuccessfulMigration($migration)
177 | {
178 | $this->database->logSuccessfulMigration($migration);
179 | }
180 |
181 | /**
182 | * Get ran migrations.
183 | *
184 | * @return array
185 | */
186 | public function getRanMigrations()
187 | {
188 | return $this->database->getRanMigrations();
189 | }
190 |
191 | /**
192 | * Get all migrations.
193 | *
194 | * @return array
195 | */
196 | public function getAllMigrations()
197 | {
198 | return $this->files->getMigrationFiles($this->dir);
199 | }
200 |
201 | /**
202 | * Determine whether migration file for migration exists.
203 | *
204 | * @param string $migration
205 | *
206 | * @return bool
207 | */
208 | public function doesMigrationFileExist($migration)
209 | {
210 | return $this->files->exists($this->getMigrationFilePath($migration));
211 | }
212 |
213 | /**
214 | * Rollback a given migration.
215 | *
216 | * @param string $file
217 | *
218 | * @throws Exception
219 | *
220 | * @return mixed
221 | */
222 | public function rollbackMigration($file)
223 | {
224 | $migration = $this->getMigrationObjectByFileName($file);
225 |
226 | $this->checkTransactionAndRun($migration, function () use ($migration, $file) {
227 | if ($migration->down() === false) {
228 | throw new Exception("Can't rollback migration: {$file}.php");
229 | }
230 | });
231 |
232 | $this->removeSuccessfulMigrationFromLog($file);
233 | }
234 |
235 | /**
236 | * Remove a migration name from the database so it can be run again.
237 | *
238 | * @param string $file
239 | *
240 | * @return void
241 | */
242 | public function removeSuccessfulMigrationFromLog($file)
243 | {
244 | $this->database->removeSuccessfulMigrationFromLog($file);
245 | }
246 |
247 | /**
248 | * Delete migration file.
249 | *
250 | * @param string $migration
251 | *
252 | * @return bool
253 | */
254 | public function deleteMigrationFile($migration)
255 | {
256 | return $this->files->delete($this->getMigrationFilePath($migration));
257 | }
258 |
259 | /**
260 | * Get array of migrations that should be ran.
261 | *
262 | * @return array
263 | */
264 | public function getMigrationsToRun()
265 | {
266 | $allMigrations = $this->getAllMigrations();
267 |
268 | $ranMigrations = $this->getRanMigrations();
269 |
270 | return array_diff($allMigrations, $ranMigrations);
271 | }
272 |
273 | /**
274 | * Move migration files.
275 | *
276 | * @param array $files
277 | * @param string $toDir
278 | *
279 | * @return int
280 | */
281 | public function moveMigrationFiles($files = [], $toDir = '')
282 | {
283 | $toDir = trim($toDir ?: $this->dir_archive, '/');
284 | $files = $files ?: $this->getAllMigrations();
285 | $this->files->createDirIfItDoesNotExist("$this->dir/$toDir");
286 |
287 | $count = 0;
288 | foreach ($files as $migration) {
289 | $from = $this->getMigrationFilePath($migration);
290 | $to = "$this->dir/$toDir/$migration.php";
291 |
292 | if ($from == $to) {
293 | continue;
294 | }
295 |
296 | $flag = $this->files->move($from, $to);
297 |
298 | if ($flag) {
299 | $count++;
300 | }
301 | }
302 |
303 | return $count;
304 | }
305 |
306 | /**
307 | * Construct migration file name from migration name and current time.
308 | *
309 | * @param string $name
310 | *
311 | * @return string
312 | */
313 | protected function constructFileName($name)
314 | {
315 | list($usec, $sec) = explode(' ', microtime());
316 |
317 | $usec = substr($usec, 2, 6);
318 |
319 | return date('Y_m_d_His', $sec).'_'.$usec.'_'.$name;
320 | }
321 |
322 | /**
323 | * Get a migration class name by a migration file name.
324 | *
325 | * @param string $file
326 | *
327 | * @return string
328 | */
329 | protected function getMigrationClassNameByFileName($file)
330 | {
331 | $fileExploded = explode('_', $file);
332 |
333 | $datePart = implode('_', array_slice($fileExploded, 0, 5));
334 | $namePart = implode('_', array_slice($fileExploded, 5));
335 |
336 | return Helpers::studly($namePart.'_'.$datePart);
337 | }
338 |
339 | /**
340 | * Replace all placeholders in the stub.
341 | *
342 | * @param string $template
343 | * @param array $replace
344 | *
345 | * @return string
346 | */
347 | protected function replacePlaceholdersInTemplate($template, array $replace)
348 | {
349 | foreach ($replace as $placeholder => $value) {
350 | $template = str_replace("__{$placeholder}__", $value, $template);
351 | }
352 |
353 | return $template;
354 | }
355 |
356 | /**
357 | * Resolve a migration instance from a file.
358 | *
359 | * @param string $file
360 | *
361 | * @throws Exception
362 | *
363 | * @return MigrationInterface
364 | */
365 | protected function getMigrationObjectByFileName($file)
366 | {
367 | $class = $this->getMigrationClassNameByFileName($file);
368 |
369 | $this->requireMigrationFile($file);
370 |
371 | $object = new $class();
372 |
373 | if (!$object instanceof MigrationInterface) {
374 | throw new Exception("Migration class {$class} must implement Arrilot\\BitrixMigrations\\Interfaces\\MigrationInterface");
375 | }
376 |
377 | return $object;
378 | }
379 |
380 | /**
381 | * Require migration file.
382 | *
383 | * @param string $file
384 | *
385 | * @return void
386 | */
387 | protected function requireMigrationFile($file)
388 | {
389 | $this->files->requireFile($this->getMigrationFilePath($file));
390 | }
391 |
392 | /**
393 | * Get path to a migration file.
394 | *
395 | * @param string $migration
396 | *
397 | * @return string
398 | */
399 | protected function getMigrationFilePath($migration)
400 | {
401 | $files = Helpers::rGlob("$this->dir/$migration.php");
402 | if (count($files) != 1) {
403 | throw new \Exception("Not found migration file");
404 | }
405 |
406 | return $files[0];
407 | }
408 |
409 | /**
410 | * If package arrilot/bitrix-iblock-helper is loaded then we should disable its caching to avoid problems.
411 | */
412 | private function disableBitrixIblockHelperCache()
413 | {
414 | if (class_exists('\\Arrilot\\BitrixIblockHelper\\IblockId')) {
415 | IblockId::setCacheTime(0);
416 | if (method_exists('\\Arrilot\\BitrixIblockHelper\\IblockId', 'flushLocalCache')) {
417 | IblockId::flushLocalCache();
418 | }
419 | }
420 |
421 | if (class_exists('\\Arrilot\\BitrixIblockHelper\\HLBlock')) {
422 | HLBlock::setCacheTime(0);
423 | if (method_exists('\\Arrilot\\BitrixIblockHelper\\HLBlock', 'flushLocalCache')) {
424 | HLBlock::flushLocalCache();
425 | }
426 | }
427 | }
428 |
429 | /**
430 | * @param MigrationInterface $migration
431 | * @param callable $callback
432 | * @throws Exception
433 | */
434 | protected function checkTransactionAndRun($migration, $callback)
435 | {
436 | if ($migration->useTransaction($this->use_transaction)) {
437 | $this->database->startTransaction();
438 | Logger::log("Начало транзакции", Logger::COLOR_LIGHT_BLUE);
439 | try {
440 | $callback();
441 | } catch (\Exception $e) {
442 | $this->database->rollbackTransaction();
443 | Logger::log("Откат транзакции из-за ошибки '{$e->getMessage()}'", Logger::COLOR_LIGHT_RED);
444 | throw $e;
445 | }
446 | $this->database->commitTransaction();
447 | Logger::log("Конец транзакции", Logger::COLOR_LIGHT_BLUE);
448 | } else {
449 | $callback();
450 | }
451 | }
452 | }
453 |
--------------------------------------------------------------------------------
/src/Storages/BitrixDatabaseStorage.php:
--------------------------------------------------------------------------------
1 | db = $DB;
35 | $this->table = $table;
36 | }
37 |
38 | /**
39 | * Check if a given table already exists.
40 | *
41 | * @return bool
42 | */
43 | public function checkMigrationTableExistence()
44 | {
45 | return (bool) $this->db->query('SHOW TABLES LIKE "'.$this->table.'"')->fetch();
46 | }
47 |
48 | /**
49 | * Create migration table.
50 | *
51 | * @return void
52 | */
53 | public function createMigrationTable()
54 | {
55 | $this->db->query("CREATE TABLE {$this->table} (ID INT NOT NULL AUTO_INCREMENT, MIGRATION VARCHAR(255) NOT NULL, PRIMARY KEY (ID))");
56 | }
57 |
58 | /**
59 | * Get an array of migrations the have been ran previously.
60 | * Must be ordered by order asc.
61 | *
62 | * @return array
63 | */
64 | public function getRanMigrations()
65 | {
66 | $migrations = [];
67 |
68 | $dbRes = $this->db->query("SELECT MIGRATION FROM {$this->table} ORDER BY ID ASC");
69 | while ($result = $dbRes->fetch()) {
70 | $migrations[] = $result['MIGRATION'];
71 | }
72 |
73 | return $migrations;
74 | }
75 |
76 | /**
77 | * Save migration name to the database to prevent it from running again.
78 | *
79 | * @param string $name
80 | *
81 | * @return void
82 | */
83 | public function logSuccessfulMigration($name)
84 | {
85 | $this->db->insert($this->table, [
86 | 'MIGRATION' => "'".$this->db->forSql($name)."'",
87 | ]);
88 | }
89 |
90 | /**
91 | * Remove a migration name from the database so it can be run again.
92 | *
93 | * @param string $name
94 | *
95 | * @return void
96 | */
97 | public function removeSuccessfulMigrationFromLog($name)
98 | {
99 | $this->db->query("DELETE FROM {$this->table} WHERE MIGRATION = '".$this->db->forSql($name)."'");
100 | }
101 |
102 | /**
103 | * Start transaction
104 | */
105 | public function startTransaction()
106 | {
107 | $this->db->StartTransaction();
108 | }
109 |
110 | /**
111 | * Commit transaction
112 | */
113 | public function commitTransaction()
114 | {
115 | $this->db->Commit();
116 | }
117 |
118 | /**
119 | * Rollback transaction
120 | */
121 | public function rollbackTransaction()
122 | {
123 | $this->db->Rollback();
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/Storages/FileStorage.php:
--------------------------------------------------------------------------------
1 | exists($path) ? unlink($path) : false;
116 | }
117 |
118 | /**
119 | * Move file.
120 | *
121 | * @param string $path_from
122 | * @param string $path_to
123 | *
124 | * @return bool
125 | */
126 | public function move($path_from, $path_to)
127 | {
128 | return $this->exists($path_from) ? rename($path_from, $path_to) : false;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/TemplatesCollection.php:
--------------------------------------------------------------------------------
1 | dir = dirname(__DIR__).'/templates';
30 |
31 | $this->registerTemplate([
32 | 'name' => 'default',
33 | 'path' => $this->dir.'/default.template',
34 | 'description' => 'Default migration template',
35 | ]);
36 | }
37 |
38 | /**
39 | * Register basic templates.
40 | */
41 | public function registerBasicTemplates()
42 | {
43 | $templates = [
44 | [
45 | 'name' => 'add_iblock',
46 | 'path' => $this->dir.'/add_iblock.template',
47 | 'description' => 'Add iblock',
48 | ],
49 | [
50 | 'name' => 'add_iblock_type',
51 | 'path' => $this->dir.'/add_iblock_type.template',
52 | 'description' => 'Add iblock type',
53 | ],
54 | [
55 | 'name' => 'add_iblock_element_property',
56 | 'path' => $this->dir.'/add_iblock_element_property.template',
57 | 'description' => 'Add iblock element property',
58 | 'aliases' => [
59 | 'add_iblock_prop',
60 | 'add_iblock_element_prop',
61 | 'add_element_prop',
62 | 'add_element_property',
63 | ],
64 | ],
65 | [
66 | 'name' => 'add_uf',
67 | 'path' => $this->dir.'/add_uf.template',
68 | 'description' => 'Add user field (for sections, users e.t.c)',
69 | ],
70 | [
71 | 'name' => 'add_table',
72 | 'path' => $this->dir.'/add_table.template',
73 | 'description' => 'Create table',
74 | 'aliases' => [
75 | 'create_table',
76 | ],
77 | ],
78 | [
79 | 'name' => 'delete_table',
80 | 'path' => $this->dir.'/delete_table.template',
81 | 'description' => 'Drop table',
82 | 'aliases' => [
83 | 'drop_table',
84 | ],
85 | ],
86 | [
87 | 'name' => 'query',
88 | 'path' => $this->dir.'/query.template',
89 | 'description' => 'Simple database query',
90 | ],
91 | ];
92 |
93 | foreach ($templates as $template) {
94 | $this->registerTemplate($template);
95 | }
96 | }
97 |
98 | /**
99 | * Register templates for automigrations.
100 | */
101 | public function registerAutoTemplates()
102 | {
103 | $templates = [
104 | 'add_iblock',
105 | 'update_iblock',
106 | 'delete_iblock',
107 | 'add_iblock_element_property',
108 | 'update_iblock_element_property',
109 | 'delete_iblock_element_property',
110 | 'add_uf',
111 | 'update_uf',
112 | 'delete_uf',
113 | 'add_hlblock',
114 | 'update_hlblock',
115 | 'delete_hlblock',
116 | 'add_group',
117 | 'update_group',
118 | 'delete_group',
119 | ];
120 |
121 | foreach ($templates as $template) {
122 | $this->registerTemplate([
123 | 'name' => 'auto_'.$template,
124 | 'path' => $this->dir.'/auto/'.$template.'.template',
125 | ]);
126 | }
127 | }
128 |
129 | /**
130 | * Getter for registered templates.
131 | *
132 | * @return array
133 | */
134 | public function all()
135 | {
136 | return $this->templates;
137 | }
138 |
139 | /**
140 | * Dynamically register migration template.
141 | *
142 | * @param array $template
143 | *
144 | * @return void
145 | */
146 | public function registerTemplate($template)
147 | {
148 | $template = $this->normalizeTemplateDuringRegistration($template);
149 |
150 | $this->templates[$template['name']] = $template;
151 |
152 | $this->registerTemplateAliases($template, $template['aliases']);
153 | }
154 |
155 | /**
156 | * Path to the file where a template is located.
157 | *
158 | * @param string $name
159 | *
160 | * @return string
161 | */
162 | public function getTemplatePath($name)
163 | {
164 | return $this->templates[$name]['path'];
165 | }
166 |
167 | /**
168 | * Find out template name from user input.
169 | *
170 | * @param string|null $template
171 | *
172 | * @return string
173 | */
174 | public function selectTemplate($template)
175 | {
176 | if (is_null($template)) {
177 | return 'default';
178 | }
179 |
180 | if (!array_key_exists($template, $this->templates)) {
181 | throw new RuntimeException("Template \"{$template}\" is not registered");
182 | }
183 |
184 | return $template;
185 | }
186 |
187 | /**
188 | * Check template fields and normalize them.
189 | *
190 | * @param $template
191 | *
192 | * @return array
193 | */
194 | protected function normalizeTemplateDuringRegistration($template)
195 | {
196 | if (empty($template['name'])) {
197 | throw new InvalidArgumentException('Impossible to register a template without "name"');
198 | }
199 |
200 | if (empty($template['path'])) {
201 | throw new InvalidArgumentException('Impossible to register a template without "path"');
202 | }
203 |
204 | $template['description'] = isset($template['description']) ? $template['description'] : '';
205 | $template['aliases'] = isset($template['aliases']) ? $template['aliases'] : [];
206 | $template['is_alias'] = false;
207 |
208 | return $template;
209 | }
210 |
211 | /**
212 | * Register template aliases.
213 | *
214 | * @param array $template
215 | * @param array $aliases
216 | *
217 | * @return void
218 | */
219 | protected function registerTemplateAliases($template, array $aliases = [])
220 | {
221 | foreach ($aliases as $alias) {
222 | $template['is_alias'] = true;
223 | $template['name'] = $alias;
224 | $template['aliases'] = [];
225 |
226 | $this->templates[$template['name']] = $template;
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/templates/add_iblock.template:
--------------------------------------------------------------------------------
1 | add([
19 | 'NAME' => '__',
20 | 'CODE' => '__',
21 | 'SITE_ID' => 's1',
22 | 'IBLOCK_TYPE_ID' => '__', //символьный код группы инфоблока,
23 | 'VERSION' => 2,
24 | 'GROUP_ID' => ['2' =>'R'],
25 | 'LIST_PAGE_URL' => '__',
26 | 'DETAIL_PAGE_URL' => '__',
27 | ]);
28 |
29 | if (!$iblockId) {
30 | throw new MigrationException('Ошибка при добавлении инфоблока '.$ib->LAST_ERROR);
31 | }
32 |
33 | // свойства
34 | $propId = $this->addIblockElementProperty([
35 | 'NAME' => '__',
36 | 'SORT' => 500,
37 | 'CODE' => '',
38 | 'PROPERTY_TYPE' => 'L', // Список
39 | 'LIST_TYPE' => 'C', // Тип списка - 'флажки'
40 | 'VALUES' => [
41 | 'VALUE' => 'да',
42 | ],
43 | 'MULTIPLE' => 'N',
44 | 'IS_REQUIRED' => 'N',
45 | 'IBLOCK_ID' => $iblockId
46 | ]);
47 | }
48 |
49 | /**
50 | * Reverse the migration.
51 | *
52 | * @return mixed
53 | */
54 | public function down()
55 | {
56 | $this->deleteIblockByCode('__');
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/templates/add_iblock_element_property.template:
--------------------------------------------------------------------------------
1 | getIblockIdByCode('__');
17 |
18 | $propId = $this->addIblockElementProperty([
19 | 'NAME' => '__',
20 | 'SORT' => 500,
21 | 'CODE' => '__',
22 | 'PROPERTY_TYPE' => 'L', // Список
23 | 'LIST_TYPE' => 'C', // Тип списка - 'флажки'
24 | 'VALUES' => [
25 | 'VALUE' => 'да',
26 | ],
27 | 'MULTIPLE' => 'N',
28 | 'IS_REQUIRED' => 'N',
29 | 'IBLOCK_ID' => $iblockId
30 | ]);
31 | }
32 |
33 | /**
34 | * Reverse the migration.
35 | *
36 | * @return mixed
37 | */
38 | public function down()
39 | {
40 | $iblockId = $this->getIblockIdByCode('__');
41 |
42 | $this->deleteIblockElementPropertyByCode($iblockId, '__');
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/templates/add_iblock_type.template:
--------------------------------------------------------------------------------
1 | Add([
18 | 'ID'=>'__',
19 | 'SECTIONS'=>'Y',
20 | 'IN_RSS'=>'N',
21 | 'SORT'=>100,
22 | 'LANG'=>array(
23 | 'ru'=>array(
24 | 'NAME'=>'__',
25 | )
26 | )
27 | ]);
28 |
29 | if (!$cbtRes) {
30 | throw new MigrationException('Ошибка при добавлении типа инфоблока '.$cbt->LAST_ERROR);
31 | }
32 | }
33 |
34 | /**
35 | * Reverse the migration.
36 | *
37 | * @return mixed
38 | */
39 | public function down()
40 | {
41 | CModule::IncludeModule('iblock');
42 |
43 | global $DB;
44 | $DB->StartTransaction();
45 |
46 | if (!CIBlockType::Delete('__')) {
47 | $DB->Rollback();
48 | throw new MigrationException('Ошибка при удалении типа инфоблока __');
49 | }
50 |
51 | $DB->Commit();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/templates/add_table.template:
--------------------------------------------------------------------------------
1 | new \Bitrix\Main\Entity\IntegerField("ID", [
21 | 'primary' => true,
22 | 'unique' => true,
23 | 'required' => true,
24 | ]),
25 | "VALUE" => new \Bitrix\Main\Entity\StringField("VALUE", [
26 | 'required' => true,
27 | ]),
28 | ];
29 | $primary = ["ID"];
30 | $autoincrement = ["ID"];
31 |
32 | $this->db->createTable($this->table, $fields, $primary, $autoincrement);
33 | }
34 |
35 | /**
36 | * Reverse the migration.
37 | *
38 | * @return mixed
39 | * @throws MigrationException
40 | */
41 | public function down()
42 | {
43 | $this->db->dropTable($this->table);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/templates/add_uf.template:
--------------------------------------------------------------------------------
1 | addUF([
17 | /*
18 | * Идентификатор сущности, к которой будет привязано свойство.
19 | * Для секция формат следующий - IBLOCK_{IBLOCK_ID}_SECTION
20 | */
21 | 'ENTITY_ID' => 'USER',
22 | /* Код поля. Всегда должно начинаться с UF_ */
23 | 'FIELD_NAME' => '__',
24 | /* Указываем, что тип нового пользовательского свойства строка */
25 | 'USER_TYPE_ID' => 'string',
26 | /*
27 | * XML_ID пользовательского свойства.
28 | * Используется при выгрузке в качестве названия поля
29 | */
30 | 'XML_ID' => '',
31 | /* Сортировка */
32 | 'SORT' => 500,
33 | /* Является поле множественным или нет */
34 | 'MULTIPLE' => 'N',
35 | /* Обязательное или нет свойство */
36 | 'MANDATORY' => 'N',
37 | /*
38 | * Показывать в фильтре списка. Возможные значения:
39 | * не показывать = N, точное совпадение = I,
40 | * поиск по маске = E, поиск по подстроке = S
41 | */
42 | 'SHOW_FILTER' => 'N',
43 | /*
44 | * Не показывать в списке. Если передать какое-либо значение,
45 | * то будет считаться, что флаг выставлен (недоработка разработчиков битрикс).
46 | */
47 | 'SHOW_IN_LIST' => '',
48 | /*
49 | * Не разрешать редактирование пользователем.
50 | * Если передать какое-либо значение, то будет считаться,
51 | * что флаг выставлен (недоработка разработчиков битрикс).
52 | */
53 | 'EDIT_IN_LIST' => '',
54 | /* Значения поля участвуют в поиске */
55 | 'IS_SEARCHABLE' => 'N',
56 | /*
57 | * Дополнительные настройки поля (зависят от типа).
58 | * В нашем случае для типа string
59 | */
60 | 'SETTINGS' => array(
61 | /* Значение по умолчанию */
62 | 'DEFAULT_VALUE' => '',
63 | /* Размер поля ввода для отображения */
64 | 'SIZE' => '20',
65 | /* Количество строчек поля ввода */
66 | 'ROWS' => '1',
67 | /* Минимальная длина строки (0 - не проверять) */
68 | 'MIN_LENGTH' => '0',
69 | /* Максимальная длина строки (0 - не проверять) */
70 | 'MAX_LENGTH' => '0',
71 | /* Регулярное выражение для проверки */
72 | 'REGEXP' => '',
73 | ),
74 | /* Подпись в форме редактирования */
75 | 'EDIT_FORM_LABEL' => array(
76 | 'ru' => 'Пользовательское свойство',
77 | 'en' => 'User field',
78 | ),
79 | /* Заголовок в списке */
80 | 'LIST_COLUMN_LABEL' => array(
81 | 'ru' => 'Пользовательское свойство',
82 | 'en' => 'User field',
83 | ),
84 | /* Подпись фильтра в списке */
85 | 'LIST_FILTER_LABEL' => array(
86 | 'ru' => 'Пользовательское свойство',
87 | 'en' => 'User field',
88 | ),
89 | /* Сообщение об ошибке (не обязательное) */
90 | 'ERROR_MESSAGE' => array(
91 | 'ru' => 'Ошибка при заполнении пользовательского свойства',
92 | 'en' => 'An error in completing the user field',
93 | ),
94 | /* Помощь */
95 | 'HELP_MESSAGE' => [
96 | 'ru' => '',
97 | 'en' => '',
98 | ],
99 | ]);
100 | }
101 |
102 | /**
103 | * Reverse the migration.
104 | *
105 | * @return mixed
106 | * @throws MigrationException
107 | */
108 | public function down()
109 | {
110 | $code = '__';
111 |
112 | $id = $this->getUFIdByCode('USER', $code);
113 | if (!$id) {
114 | throw new MigrationException('Не найдено пользовательское свойство для удаления');
115 | }
116 |
117 | (new CUserTypeEntity())->delete($id);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/templates/auto/add_group.template:
--------------------------------------------------------------------------------
1 | add($fields);
21 |
22 | if ($group->LAST_ERROR) {
23 | throw new MigrationException('Ошибка при добавлении группы '.$group->LAST_ERROR);
24 | }
25 | }
26 |
27 | /**
28 | * Reverse the migration.
29 | *
30 | * @return mixed
31 | * @throws MigrationException
32 | */
33 | public function down()
34 | {
35 | return false;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/templates/auto/add_hlblock.template:
--------------------------------------------------------------------------------
1 | isSuccess()) {
22 | $errors = $result->getErrorMessages();
23 | throw new MigrationException('Ошибка при добавлении hl-блока '.implode(', ', $errors));
24 | }
25 | }
26 |
27 | /**
28 | * Reverse the migration.
29 | *
30 | * @return mixed
31 | * @throws MigrationException
32 | */
33 | public function down()
34 | {
35 | return false;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/templates/auto/add_iblock.template:
--------------------------------------------------------------------------------
1 | add($fields);
20 |
21 | if (!$id) {
22 | throw new MigrationException('Ошибка при добавлении инфоблока '.$ib->LAST_ERROR);
23 | }
24 | }
25 |
26 | /**
27 | * Reverse the migration.
28 | *
29 | * @return mixed
30 | * @throws MigrationException
31 | */
32 | public function down()
33 | {
34 | $id = $this->getIblockIdByCode(__code__);
35 |
36 | $this->db->startTransaction();
37 | if (!CIBlock::delete($id)) {
38 | $this->db->rollbackTransaction();
39 | throw new MigrationException('Ошибка при удалении инфоблока');
40 | }
41 |
42 | $this->db->commitTransaction();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/templates/auto/add_iblock_element_property.template:
--------------------------------------------------------------------------------
1 | add($fields);
20 |
21 | if (!$propId) {
22 | throw new MigrationException('Ошибка при добавлении свойства инфоблока '.$ibp->LAST_ERROR);
23 | }
24 | }
25 |
26 | /**
27 | * Reverse the migration.
28 | *
29 | * @return mixed
30 | * @throws MigrationException
31 | */
32 | public function down()
33 | {
34 | $id = $this->getIblockPropIdByCode(__code__, __iblockId__);
35 |
36 | $ibp = new CIBlockProperty();
37 | $deleted = $ibp->delete($id);
38 |
39 | if (!$deleted) {
40 | throw new MigrationException('Ошибка при удалении свойства инфоблока '.$ibp->LAST_ERROR);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/templates/auto/add_uf.template:
--------------------------------------------------------------------------------
1 | addUF($fields);
19 | }
20 |
21 | /**
22 | * Reverse the migration.
23 | *
24 | * @return mixed
25 | * @throws MigrationException
26 | */
27 | public function down()
28 | {
29 | $id = $this->getUFIdByCode(__entity__, __code__);
30 | if (!$id) {
31 | throw new MigrationException('Не найдено пользовательское свойство для удаления');
32 | }
33 |
34 | (new CUserTypeEntity())->delete($id);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/templates/auto/delete_group.template:
--------------------------------------------------------------------------------
1 | db->startTransaction();
20 | if (!$group->delete(__id__)) {
21 | $this->db->rollbackTransaction();
22 | throw new MigrationException('Ошибка при удалении группы '.$group->LAST_ERROR);
23 | }
24 | $this->db->commitTransaction();
25 | }
26 |
27 | /**
28 | * Reverse the migration.
29 | *
30 | * @return mixed
31 | * @throws MigrationException
32 | */
33 | public function down()
34 | {
35 | return false;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/templates/auto/delete_hlblock.template:
--------------------------------------------------------------------------------
1 | isSuccess()) {
20 | $errors = $result->getErrorMessages();
21 | throw new MigrationException('Ошибка при удалении hl-блока '.implode(', ', $errors));
22 | }
23 | }
24 |
25 | /**
26 | * Reverse the migration.
27 | *
28 | * @return mixed
29 | * @throws MigrationException
30 | */
31 | public function down()
32 | {
33 | return false;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/templates/auto/delete_iblock.template:
--------------------------------------------------------------------------------
1 | getIblockIdByCode(__code__);
17 |
18 | $this->db->startTransaction();
19 | if (!CIBlock::delete($id)) {
20 | $this->db->rollbackTransaction();
21 | throw new MigrationException('Ошибка при удалении инфоблока');
22 | }
23 |
24 | $this->db->commitTransaction();
25 | }
26 |
27 | /**
28 | * Reverse the migration.
29 | *
30 | * @return mixed
31 | * @throws MigrationException
32 | */
33 | public function down()
34 | {
35 | return false;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/templates/auto/delete_iblock_element_property.template:
--------------------------------------------------------------------------------
1 | getIblockPropIdByCode(__code__, __iblockId__);
17 |
18 | $ibp = new CIBlockProperty();
19 | $deleted = $ibp->delete($id);
20 |
21 | if (!$deleted) {
22 | throw new MigrationException('Ошибка при удалении свойства инфоблока '.$ibp->LAST_ERROR);
23 | }
24 | }
25 |
26 | /**
27 | * Reverse the migration.
28 | *
29 | * @return mixed
30 | */
31 | public function down()
32 | {
33 | return false;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/templates/auto/delete_uf.template:
--------------------------------------------------------------------------------
1 | getUFIdByCode(__entity__, __code__);
18 |
19 | $oUserTypeEntity = new CUserTypeEntity();
20 |
21 | $dbResult = $oUserTypeEntity->delete($id);
22 | if (!$dbResult->result) {
23 | throw new MigrationException("Не удалось обновить удалить свойство с FIELD_NAME = {$fields['FIELD_NAME']} и ENTITY_ID = {$fields['ENTITY_ID']}");
24 | }
25 | }
26 |
27 | /**
28 | * Reverse the migration.
29 | *
30 | * @return mixed
31 | * @throws MigrationException
32 | */
33 | public function down()
34 | {
35 | return false;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/templates/auto/update_group.template:
--------------------------------------------------------------------------------
1 | update(__id__, $fields);
21 |
22 | if ($group->LAST_ERROR) {
23 | throw new MigrationException('Ошибка при обновлении группы '.$group->LAST_ERROR);
24 | }
25 | }
26 |
27 | /**
28 | * Reverse the migration.
29 | *
30 | * @return mixed
31 | * @throws MigrationException
32 | */
33 | public function down()
34 | {
35 | return false;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/templates/auto/update_hlblock.template:
--------------------------------------------------------------------------------
1 | isSuccess()) {
22 | $errors = $result->getErrorMessages();
23 | throw new MigrationException('Ошибка при обновлении hl-блока '.implode(', ', $errors));
24 | }
25 | }
26 |
27 | /**
28 | * Reverse the migration.
29 | *
30 | * @return mixed
31 | * @throws MigrationException
32 | */
33 | public function down()
34 | {
35 | return false;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/templates/auto/update_iblock.template:
--------------------------------------------------------------------------------
1 | getIblockIdByCode($fields['CODE']);
19 | $fields['ID'] = $iblockId;
20 |
21 | $ib = new CIBlock();
22 | $updated = $ib->update($iblockId, $fields);
23 |
24 | if (!$updated) {
25 | throw new MigrationException('Ошибка при обновлении инфоблока '.$ib->LAST_ERROR);
26 | }
27 | }
28 |
29 | /**
30 | * Reverse the migration.
31 | *
32 | * @return mixed
33 | * @throws MigrationException
34 | */
35 | public function down()
36 | {
37 | return false;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/templates/auto/update_iblock_element_property.template:
--------------------------------------------------------------------------------
1 | getIblockPropIdByCode(__code__, __iblockId__);
19 | $fields['ID'] = $id;
20 |
21 | $ibp = new CIBlockProperty();
22 | $updated = $ibp->update($id, $fields);
23 |
24 | if (!$updated) {
25 | throw new MigrationException('Ошибка при изменении свойства инфоблока '.$ibp->LAST_ERROR);
26 | }
27 | }
28 |
29 | /**
30 | * Reverse the migration.
31 | *
32 | * @return mixed
33 | */
34 | public function down()
35 | {
36 | return false;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/templates/auto/update_uf.template:
--------------------------------------------------------------------------------
1 | getUFIdByCode(__entity__, __code__);
18 |
19 | $oUserTypeEntity = new CUserTypeEntity();
20 |
21 | $result = $oUserTypeEntity->update($id, $fields);
22 | if (!$result) {
23 | throw new MigrationException("Не удалось обновить пользовательское свойство с FIELD_NAME = {$fields['FIELD_NAME']} и ENTITY_ID = {$fields['ENTITY_ID']}");
24 | }
25 | }
26 |
27 | /**
28 | * Reverse the migration.
29 | *
30 | * @return mixed
31 | * @throws MigrationException
32 | */
33 | public function down()
34 | {
35 | return false;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/templates/default.template:
--------------------------------------------------------------------------------
1 | db->dropTable($this->table);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/templates/query.template:
--------------------------------------------------------------------------------
1 | db->query($sql);
20 | }
21 |
22 | /**
23 | * Reverse the migration.
24 | *
25 | * @return mixed
26 | * @throws MigrationException
27 | */
28 | public function down()
29 | {
30 | return false;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/CommandTestCase.php:
--------------------------------------------------------------------------------
1 | run(new ArrayInput($input), new NullOutput());
30 | }
31 |
32 | /**
33 | * @return array
34 | */
35 | protected function getConfig()
36 | {
37 | return [
38 | 'table' => 'migrations',
39 | 'dir' => 'migrations',
40 | ];
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/InstallCommandTest.php:
--------------------------------------------------------------------------------
1 | shouldAllowMockingProtectedMethods();
13 | }
14 |
15 | public function testItCreatesMigrationTable()
16 | {
17 | $database = m::mock('Arrilot\BitrixMigrations\Interfaces\DatabaseStorageInterface');
18 | $database->shouldReceive('checkMigrationTableExistence')->once()->andReturn(false);
19 | $database->shouldReceive('createMigrationTable')->once();
20 |
21 | $command = $this->mockCommand($database);
22 |
23 | $this->runCommand($command);
24 | }
25 |
26 | public function testItDoesNotCreateATableIfItExists()
27 | {
28 | $database = m::mock('Arrilot\BitrixMigrations\Interfaces\DatabaseStorageInterface');
29 | $database->shouldReceive('checkMigrationTableExistence')->once()->andReturn(true);
30 | $database->shouldReceive('createMigrationTable')->never();
31 |
32 | $command = $this->mockCommand($database);
33 | $command->shouldReceive('abort')->once()->andThrow('DomainException');
34 |
35 | $this->runCommand($command);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/MakeCommandTest.php:
--------------------------------------------------------------------------------
1 | shouldAllowMockingProtectedMethods();
13 | }
14 |
15 | public function testItCreatesAMigrationFile()
16 | {
17 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator');
18 | $migrator->shouldReceive('createMigration')->once()->andReturn('2015_11_26_162220_bar');
19 |
20 | $command = $this->mockCommand($migrator);
21 | $command->shouldReceive('message')->once();
22 |
23 | $this->runCommand($command, ['name' => 'test_migration']);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/MigrateCommandTest.php:
--------------------------------------------------------------------------------
1 | shouldAllowMockingProtectedMethods();
13 | }
14 |
15 | public function testItMigratesNothingIfThereIsNoOutstandingMigrations()
16 | {
17 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator');
18 | $migrator->shouldReceive('getMigrationsToRun')->once()->andReturn([]);
19 | $migrator->shouldReceive('runMigration')->never();
20 |
21 | $command = $this->mockCommand($migrator);
22 | $command->shouldReceive('info')->with('Nothing to migrate')->once();
23 |
24 | $this->runCommand($command);
25 | }
26 |
27 | public function testItMigratesOutstandingMigrations()
28 | {
29 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator');
30 | $migrator->shouldReceive('getMigrationsToRun')->once()->andReturn([
31 | '2015_11_26_162220_bar',
32 | ]);
33 | $migrator->shouldReceive('runMigration')->with('2015_11_26_162220_bar')->once();
34 |
35 | $command = $this->mockCommand($migrator);
36 | $command->shouldReceive('message')->with('Migrated: 2015_11_26_162220_bar.php')->once();
37 | $command->shouldReceive('info')->with('Nothing to migrate')->never();
38 |
39 | $this->runCommand($command);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/MigratorTest.php:
--------------------------------------------------------------------------------
1 | mockDatabase();
22 | $files = $this->mockFiles();
23 |
24 | $files->shouldReceive('createDirIfItDoesNotExist')->once();
25 | $files->shouldReceive('getContent')->once()->andReturn('some content');
26 | $files->shouldReceive('putContent')->once()->andReturn(1000);
27 |
28 | $migrator = $this->createMigrator($database, $files);
29 |
30 | $this->assertRegExp('/[0-9]{4}_[0-9]{2}_[0-9]{2}_[0-9]{6}_[0-9]{6}_test_migration/', $migrator->createMigration('test_migration', null));
31 | }
32 |
33 | /**
34 | * Create migrator.
35 | */
36 | protected function createMigrator($database, $files)
37 | {
38 | $config = [
39 | 'table' => 'migrations',
40 | 'dir' => 'migrations',
41 | ];
42 |
43 | $templatesCollection = new TemplatesCollection($config);
44 | $templatesCollection->registerBasicTemplates();
45 |
46 | return new Migrator($config, $templatesCollection, $database, $files);
47 | }
48 |
49 | /**
50 | * @return m\MockInterface
51 | */
52 | protected function mockDatabase()
53 | {
54 | return m::mock('Arrilot\BitrixMigrations\Interfaces\DatabaseStorageInterface');
55 | }
56 |
57 | /**
58 | * @return m\MockInterface
59 | */
60 | protected function mockFiles()
61 | {
62 | return m::mock('Arrilot\BitrixMigrations\Interfaces\FileStorageInterface');
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/RollbackCommandTest.php:
--------------------------------------------------------------------------------
1 | shouldAllowMockingProtectedMethods();
14 | }
15 |
16 | public function testItRollbacksNothingIfThereIsNoMigrations()
17 | {
18 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator');
19 | $migrator->shouldReceive('getRanMigrations')->once()->andReturn([]);
20 | $migrator->shouldReceive('rollbackMigration')->never();
21 | $migrator->shouldReceive('hardRollbackMigration')->never();
22 |
23 | $command = $this->mockCommand($migrator);
24 | $command->shouldReceive('info')->with('Nothing to rollback')->once();
25 |
26 | $this->runCommand($command);
27 | }
28 |
29 | public function testItRollsBackTheLastMigration()
30 | {
31 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator');
32 | $migrator->shouldReceive('getRanMigrations')->once()->andReturn([
33 | '2014_11_26_162220_foo',
34 | '2015_11_26_162220_bar',
35 | ]);
36 | $migrator->shouldReceive('doesMigrationFileExist')->once()->andReturn(true);
37 | $migrator->shouldReceive('rollbackMigration')->once();
38 | $migrator->shouldReceive('hardRollbackMigration')->never();
39 |
40 | $command = $this->mockCommand($migrator);
41 | $command->shouldReceive('info')->with('Nothing to rollback')->never();
42 | $command->shouldReceive('message')->with('Rolled back: 2015_11_26_162220_bar.php')->once();
43 |
44 | $this->runCommand($command);
45 | }
46 |
47 | public function testItRollbackNonExistingMigration()
48 | {
49 | $migrator = m::mock('Arrilot\BitrixMigrations\Migrator');
50 | $migrator->shouldReceive('getRanMigrations')->once()->andReturn([
51 | '2014_11_26_162220_foo',
52 | '2015_11_26_162220_bar',
53 | ]);
54 | $migrator->shouldReceive('doesMigrationFileExist')->once()->andReturn(false);
55 | $migrator->shouldReceive('rollbackMigration')->never();
56 | $migrator->shouldReceive('hardRollbackMigration')->never();
57 |
58 | $command = $this->mockCommand($migrator);
59 | $command->shouldReceive('markRolledBackWithConfirmation')->with('2015_11_26_162220_bar')->once();
60 | $command->shouldReceive('info')->with('Nothing to rollback')->never();
61 | $command->shouldReceive('message')->with('Rolled back: 2015_11_26_162220_bar.php')->once();
62 |
63 | $this->runCommand($command);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------