├── LICENSE
├── README.md
├── composer.json
├── resources
├── fias_entities.php
├── fias_entities_default.php
└── xsd
│ ├── AS_ADDR_OBJ_2_251_01_04_01_01.xsd
│ ├── AS_ADDR_OBJ_DIVISION_2_251_19_04_01_01.xsd
│ ├── AS_ADDR_OBJ_TYPES_2_251_03_04_01_01.xsd
│ ├── AS_ADM_HIERARCHY_2_251_04_04_01_01.xsd
│ ├── AS_APARTMENTS_2_251_05_04_01_01.xsd
│ ├── AS_APARTMENT_TYPES_2_251_07_04_01_01.xsd
│ ├── AS_CARPLACES_2_251_06_04_01_01.xsd
│ ├── AS_CHANGE_HISTORY_251_21_04_01_01.xsd
│ ├── AS_HOUSES_2_251_08_04_01_01.xsd
│ ├── AS_HOUSE_TYPES_2_251_13_04_01_01.xsd
│ ├── AS_MUN_HIERARCHY_2_251_10_04_01_01.xsd
│ ├── AS_NORMATIVE_DOCS_2_251_11_04_01_01.xsd
│ ├── AS_NORMATIVE_DOCS_KINDS_2_251_09_04_01_01.xsd
│ ├── AS_NORMATIVE_DOCS_TYPES_2_251_16_04_01_01.xsd
│ ├── AS_OBJECT_LEVELS_2_251_12_04_01_01.xsd
│ ├── AS_OPERATION_TYPES_2_251_14_04_01_01.xsd
│ ├── AS_PARAM_2_251_02_04_01_01.xsd
│ ├── AS_PARAM_TYPES_2_251_20_04_01_01.xsd
│ ├── AS_REESTR_OBJECTS_2_251_22_04_01_01.xsd
│ ├── AS_ROOMS_2_251_15_04_01_01.xsd
│ ├── AS_ROOM_TYPES_2_251_17_04_01_01.xsd
│ └── AS_STEADS_2_251_18_04_01_01.xsd
└── src
├── Downloader
├── Downloader.php
└── DownloaderImpl.php
├── EntityDescriptor
├── BaseEntityDescriptor.php
└── EntityDescriptor.php
├── EntityField
├── BaseEntityField.php
└── EntityField.php
├── EntityManager
├── BaseEntityManager.php
└── EntityManager.php
├── EntityRegistry
├── AbstractEntityRegistry.php
├── ArrayEntityRegistry.php
├── EntityRegistry.php
└── PhpArrayFileRegistry.php
├── Exception
├── DownloaderException.php
├── EntityRegistryException.php
├── Exception.php
├── FiasInformerException.php
├── HttpTransportException.php
├── PipeException.php
├── StatusCheckerException.php
├── StorageException.php
├── TaskException.php
├── UnpackerException.php
└── XmlException.php
├── FiasFile
├── FiasFile.php
├── FiasFileFactory.php
└── FiasFileImpl.php
├── FiasFileSelector
├── FiasFileSelector.php
├── FiasFileSelectorArchive.php
├── FiasFileSelectorComposite.php
└── FiasFileSelectorDir.php
├── FiasInformer
├── FiasInformer.php
├── FiasInformerImpl.php
├── FiasInformerResponse.php
├── FiasInformerResponseFactory.php
└── FiasInformerResponseImpl.php
├── FiasStatusChecker
├── FiasStatusChecker.php
├── FiasStatusCheckerImpl.php
├── FiasStatusCheckerResult.php
├── FiasStatusCheckerResultForService.php
├── FiasStatusCheckerResultForServiceImpl.php
├── FiasStatusCheckerResultImpl.php
├── FiasStatusCheckerService.php
└── FiasStatusCheckerStatus.php
├── FilesDispatcher
├── FilesDispatcher.php
└── FilesDispatcherImpl.php
├── Filter
├── Filter.php
└── RegexpFilter.php
├── Helper
├── ArrayHelper.php
├── FiasLink.php
├── IdHelper.php
└── PathHelper.php
├── HttpTransport
├── HttpTransport.php
├── HttpTransportCurl.php
├── HttpTransportResponse.php
├── HttpTransportResponseFactory.php
└── HttpTransportResponseImpl.php
├── Pipeline
├── Pipe
│ ├── ArrayPipe.php
│ └── Pipe.php
├── State
│ ├── ArrayState.php
│ ├── State.php
│ └── StateParameter.php
└── Task
│ ├── ApplyNestedPipelineToFileTask.php
│ ├── CheckStatusTask.php
│ ├── CleanupFilesUnpacked.php
│ ├── CleanupTask.php
│ ├── DataAbstractTask.php
│ ├── DataDeleteTask.php
│ ├── DataInsertTask.php
│ ├── DataUpsertTask.php
│ ├── DownloadTask.php
│ ├── InformDeltaTask.php
│ ├── InformFullTask.php
│ ├── LoggableTask.php
│ ├── LoggableTaskTrait.php
│ ├── PrepareFolderTask.php
│ ├── ProcessSwitchTask.php
│ ├── SaveFiasFilesTask.php
│ ├── SelectFilesToProceedTask.php
│ ├── Task.php
│ ├── TruncateTask.php
│ ├── UnpackTask.php
│ ├── VersionGetTask.php
│ └── VersionSetTask.php
├── Serializer
├── FiasFileDenormalizer.php
├── FiasFileNormalizer.php
├── FiasFilterEmptyStringsDenormalizer.php
├── FiasNameConverter.php
├── FiasPipelineStateDenormalizer.php
├── FiasPipelineStateNormalizer.php
├── FiasSerializer.php
├── FiasSerializerContextParam.php
├── FiasSerializerFormat.php
├── FiasUnpackerFileDenormalizer.php
└── FiasUnpackerFileNormalizer.php
├── Storage
├── CompositeStorage.php
└── Storage.php
├── Unpacker
├── Unpacker.php
├── UnpackerFile.php
├── UnpackerFileFactory.php
├── UnpackerFileImpl.php
└── UnpackerZip.php
├── VersionManager
└── VersionManager.php
└── XmlReader
├── BaseXmlReader.php
└── XmlReader.php
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 liquetsoft
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | FiasComponent
2 | =============
3 |
4 | [](https://packagist.org/packages/liquetsoft/fias-component)
5 | [](https://packagist.org/packages/liquetsoft/fias-component)
6 | [](https://packagist.org/packages/liquetsoft/fias-component)
7 | [](https://github.com/liquetsoft/fias-component/actions?query=workflow%3A%22liquetsoft_fias%22)
8 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "liquetsoft/fias-component",
3 | "description": "FIAS database parser for php.",
4 | "type": "library",
5 | "keywords": ["php", "fias"],
6 | "license": "MIT",
7 | "require": {
8 | "php": ">=8.2",
9 | "ext-libxml": "*",
10 | "ext-xmlreader": "*",
11 | "ext-zip": "*",
12 | "ext-json": "*",
13 | "symfony/serializer": "^5.0|^6.0|^7.0",
14 | "symfony/property-access": "^5.0|^6.0|^7.0",
15 | "symfony/property-info": "^5.0|^6.0|^7.0",
16 | "symfony/process": "^5.0|^6.0|^7.0",
17 | "psr/log": "^1.0|^2.0|^3.0",
18 | "marvin255/file-system-helper": "^5.0|^6.0"
19 | },
20 | "require-dev": {
21 | "phpunit/phpunit": "^11.0",
22 | "fakerphp/faker": "^1.7",
23 | "friendsofphp/php-cs-fixer": "^3.0",
24 | "vimeo/psalm": "^5.0|^6.0"
25 | },
26 | "autoload": {
27 | "psr-4": {
28 | "Liquetsoft\\Fias\\Component\\": "src/"
29 | }
30 | },
31 | "autoload-dev": {
32 | "psr-4": {
33 | "Liquetsoft\\Fias\\Component\\Tests\\": "tests/src",
34 | "Liquetsoft\\Fias\\Component\\Tests\\Mock\\": "tests/mock",
35 | "Liquetsoft\\Fias\\Component\\Generator\\": "generator"
36 | }
37 | },
38 | "scripts": {
39 | "test": "vendor/bin/phpunit --configuration phpunit.xml.dist --display-deprecations --display-phpunit-deprecations",
40 | "coverage": "vendor/bin/phpunit --configuration phpunit.xml.dist --coverage-html=tests/coverage",
41 | "fixer": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --allow-risky=yes -vvv",
42 | "linter": [
43 | "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --dry-run --stop-on-violation --allow-risky=yes -vvv",
44 | "vendor/bin/psalm --show-info=true --php-version=$(php -r \"echo phpversion();\")"
45 | ],
46 | "xsd": "php -f generator/download_entities.php",
47 | "entities": "php -f generator/generate_entities.php && vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --allow-risky=yes -q"
48 | },
49 | "repositories": [
50 | {
51 | "type": "git",
52 | "url": "https://github.com/liquetsoft/fias-component"
53 | }
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/resources/fias_entities_default.php:
--------------------------------------------------------------------------------
1 | [
7 | 'fields' => [
8 | 'CHANGEID' => [
9 | 'isPrimary' => true,
10 | ],
11 | ],
12 | ],
13 | ];
14 |
--------------------------------------------------------------------------------
/resources/xsd/AS_ADDR_OBJ_DIVISION_2_251_19_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями по операциям переподчинения
7 |
8 |
9 |
10 |
11 |
12 | Сведения по операциям переподчинения
13 |
14 |
15 |
16 |
17 | Уникальный идентификатор записи. Ключевое поле
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Родительский ID
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Дочерний ID
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ID изменившей транзакции
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/resources/xsd/AS_ADDR_OBJ_TYPES_2_251_03_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Состав и структура файла со сведениями по типам адресных объектов
6 |
7 |
8 |
9 |
10 |
11 | Сведения по типам адресных объектов
12 |
13 |
14 |
15 |
16 | Идентификатор записи
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Уровень адресного объекта
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Краткое наименование типа объекта
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | Полное наименование типа объекта
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Описание
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Дата внесения (обновления) записи
68 |
69 |
70 |
71 |
72 | Начало действия записи
73 |
74 |
75 |
76 |
77 | Окончание действия записи
78 |
79 |
80 |
81 |
82 | Статус активности
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/resources/xsd/AS_APARTMENT_TYPES_2_251_07_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями по типам помещений
7 |
8 |
9 |
10 |
11 |
12 | Сведения по типам помещений
13 |
14 |
15 |
16 |
17 | Идентификатор типа (ключ)
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Наименование
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Краткое наименование
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Описание
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Дата внесения (обновления) записи
61 |
62 |
63 |
64 |
65 | Начало действия записи
66 |
67 |
68 |
69 |
70 | Окончание действия записи
71 |
72 |
73 |
74 |
75 | Статус активности
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/resources/xsd/AS_CARPLACES_2_251_06_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями по машино-местам
7 |
8 |
9 |
10 |
11 |
12 | Сведения по машино-местам
13 |
14 |
15 |
16 |
17 | Уникальный идентификатор записи. Ключевое поле
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Глобальный уникальный идентификатор объекта типа INTEGER
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Глобальный уникальный идентификатор адресного объекта типа UUID
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ID изменившей транзакции
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Номер машиноместа
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | Статус действия над записью – причина появления записи
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Идентификатор записи связывания с предыдущей исторической записью
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Идентификатор записи связывания с последующей исторической записью
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | Дата внесения (обновления) записи
99 |
100 |
101 |
102 |
103 | Начало действия записи
104 |
105 |
106 |
107 |
108 | Окончание действия записи
109 |
110 |
111 |
112 |
113 | Статус актуальности адресного объекта ФИАС
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | Признак действующего адресного объекта
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/resources/xsd/AS_CHANGE_HISTORY_251_21_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями по истории изменений
7 |
8 |
9 |
10 |
11 |
12 | Сведения по истории изменений
13 |
14 |
15 |
16 |
17 | ID изменившей транзакции
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Уникальный ID объекта
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Уникальный ID изменившей транзакции (GUID)
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Тип операции
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | ID документа
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Дата изменения
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/resources/xsd/AS_HOUSE_TYPES_2_251_13_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями по типам домов
7 |
8 |
9 |
10 |
11 |
12 | Сведения по типам домов
13 |
14 |
15 |
16 |
17 | Идентификатор
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Наименование
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Краткое наименование
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Описание
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Дата внесения (обновления) записи
61 |
62 |
63 |
64 |
65 | Начало действия записи
66 |
67 |
68 |
69 |
70 | Окончание действия записи
71 |
72 |
73 |
74 |
75 | Статус активности
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/resources/xsd/AS_MUN_HIERARCHY_2_251_10_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями по иерархии в муниципальном делении
7 |
8 |
9 |
10 |
11 |
12 | Сведения по иерархии в муниципальном делении
13 |
14 |
15 |
16 |
17 | Уникальный идентификатор записи. Ключевое поле
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Глобальный уникальный идентификатор адресного объекта
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Идентификатор родительского объекта
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ID изменившей транзакции
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Код ОКТМО
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Идентификатор записи связывания с предыдущей исторической записью
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | Идентификатор записи связывания с последующей исторической записью
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | Дата внесения (обновления) записи
90 |
91 |
92 |
93 |
94 | Начало действия записи
95 |
96 |
97 |
98 |
99 | Окончание действия записи
100 |
101 |
102 |
103 |
104 | Признак действующего адресного объекта
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | Материализованный путь к объекту (полная иерархия)
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/resources/xsd/AS_NORMATIVE_DOCS_2_251_11_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями о нормативных документах, являющихся основанием присвоения адресному элементу наименования
7 |
8 |
9 |
10 |
11 |
12 | Сведения о нормативном документе, являющемся основанием присвоения адресному элементу наименования
13 |
14 |
15 |
16 |
17 | Уникальный идентификатор документа
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Наименование документа
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Дата документа
39 |
40 |
41 |
42 |
43 | Номер документа
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Тип документа
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | Вид документа
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Дата обновления
75 |
76 |
77 |
78 |
79 | Наименование органа создвшего нормативный документ
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | Номер государственной регистрации
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | Дата государственной регистрации
102 |
103 |
104 |
105 |
106 | Дата вступления в силу нормативного документа
107 |
108 |
109 |
110 |
111 | Комментарий
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/resources/xsd/AS_NORMATIVE_DOCS_KINDS_2_251_09_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями по видам нормативных документов
7 |
8 |
9 |
10 |
11 |
12 | Сведения по видам нормативных документов
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Идентификатор записи
23 |
24 |
25 |
26 |
27 | Наименование
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/resources/xsd/AS_NORMATIVE_DOCS_TYPES_2_251_16_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями по типам нормативных документов
7 |
8 |
9 |
10 |
11 |
12 | Сведения по типам нормативных документов
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Идентификатор записи
23 |
24 |
25 |
26 |
27 | Наименование
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Дата начала действия записи
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | Дата окончания действия записи
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/resources/xsd/AS_OBJECT_LEVELS_2_251_12_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Состав и структура файла со сведениями по уровням адресных объектов
6 |
7 |
8 |
9 |
10 |
11 | Сведения по уровням адресных объектов
12 |
13 |
14 |
15 |
16 | Уникальный идентификатор записи. Ключевое поле. Номер уровня объекта
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Наименование
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Краткое наименование
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Дата внесения (обновления) записи
49 |
50 |
51 |
52 |
53 | Начало действия записи
54 |
55 |
56 |
57 |
58 | Окончание действия записи
59 |
60 |
61 |
62 |
63 | Признак действующего адресного объекта
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/resources/xsd/AS_OPERATION_TYPES_2_251_14_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями по статусу действия
7 |
8 |
9 |
10 |
11 |
12 | Сведения по статусу действия
13 |
14 |
15 |
16 |
17 | Идентификатор статуса (ключ)
18 |
19 |
20 |
21 |
22 | Наименование
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Краткое наименование
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Описание
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Дата внесения (обновления) записи
56 |
57 |
58 |
59 |
60 | Начало действия записи
61 |
62 |
63 |
64 |
65 | Окончание действия записи
66 |
67 |
68 |
69 |
70 | Статус активности
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/resources/xsd/AS_PARAM_2_251_02_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями о классификаторе параметров адресообразующих элементов и объектов недвижимости
7 |
8 |
9 |
10 |
11 |
12 | Сведения о классификаторе параметров адресообразующих элементов и объектов недвижимости
13 |
14 |
15 |
16 |
17 | Идентификатор записи
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Глобальный уникальный идентификатор адресного объекта
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ID изменившей транзакции
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ID завершившей транзакции
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Тип параметра
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Значение параметра
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Дата внесения (обновления) записи
79 |
80 |
81 |
82 |
83 | Дата начала действия записи
84 |
85 |
86 |
87 |
88 | Дата окончания действия записи
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/resources/xsd/AS_PARAM_TYPES_2_251_20_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла с типами параметров
7 |
8 |
9 |
10 |
11 |
12 | Сведения по типу параметра
13 |
14 |
15 |
16 |
17 | Идентификатор типа параметра (ключ)
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Наименование
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | Краткое наименование
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Описание
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | Дата внесения (обновления) записи
61 |
62 |
63 |
64 |
65 | Начало действия записи
66 |
67 |
68 |
69 |
70 | Окончание действия записи
71 |
72 |
73 |
74 |
75 | Статус активности
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/resources/xsd/AS_REESTR_OBJECTS_2_251_22_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Состав и структура файла со сведениями о реестре GUID объектов
7 |
8 |
9 |
10 |
11 |
12 | Сведения об адресном элементе в части его идентификаторов
13 |
14 |
15 |
16 |
17 | Уникальный идентификатор объекта
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Дата создания
28 |
29 |
30 |
31 |
32 | ID изменившей транзакции
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | Уровень объекта
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Дата обновления
53 |
54 |
55 |
56 |
57 | GUID объекта
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Признак действующего объекта (1 - действующий, 0 - не действующий)
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/resources/xsd/AS_ROOM_TYPES_2_251_17_04_01_01.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Состав и структура файла со сведениями по типам комнат
6 |
7 |
8 |
9 |
10 |
11 | Сведения по типам комнат
12 |
13 |
14 |
15 |
16 | Идентификатор типа (ключ)
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Наименование
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Краткое наименование
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Описание
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Дата внесения (обновления) записи
60 |
61 |
62 |
63 |
64 | Начало действия записи
65 |
66 |
67 |
68 |
69 | Окончание действия записи
70 |
71 |
72 |
73 |
74 | Статус активности
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/Downloader/Downloader.php:
--------------------------------------------------------------------------------
1 | transport->head($url);
37 | } catch (\Throwable $e) {
38 | throw DownloaderException::wrap($e);
39 | }
40 |
41 | $fileHandler = $this->openLocalFile($localFile, self::FILE_MODE_NEW);
42 | for ($try = 1; $try <= $this->maxAttempts; ++$try) {
43 | try {
44 | $response = $this->transport->download($url, $fileHandler, $bytesFrom ?? null, $bytesTo ?? null);
45 | if ($response->isOk()) {
46 | break;
47 | } else {
48 | throw DownloaderException::create("Url '%s' returned status: %s", $url, $response->getStatusCode());
49 | }
50 | } catch (\Throwable $e) {
51 | if ($try === $this->maxAttempts) {
52 | throw DownloaderException::wrap($e);
53 | }
54 | } finally {
55 | $this->closeLocalFile($fileHandler);
56 | }
57 | // php запоминает описания файлов, поэтому чтобы получить
58 | // реальный размер, нужно очистить кэш
59 | clearstatcache(true, $localFile->getRealPath());
60 | // если уже скачали какие-то данные и сервер поддерживает Range,
61 | // пробуем продолжить с того же места
62 | $fileSize = filesize($localFile->getRealPath());
63 | if ($fileSize !== 0 && $fileSize !== false && $headResponse->isRangeSupported()) {
64 | $fileHandler = $this->openLocalFile($localFile, self::FILE_MODE_ADD);
65 | $bytesFrom = $fileSize;
66 | $bytesTo = $headResponse->getContentLength() - 1;
67 | } else {
68 | $fileHandler = $this->openLocalFile($localFile, self::FILE_MODE_NEW);
69 | }
70 | }
71 | }
72 |
73 | /**
74 | * Открывает локальный файл, в который будет запись, и возвращает его ресурс.
75 | *
76 | * @return resource
77 | */
78 | private function openLocalFile(\SplFileInfo $localFile, string $mode)
79 | {
80 | $hLocal = @fopen($localFile->getPathname(), $mode);
81 |
82 | if (empty($hLocal)) {
83 | throw DownloaderException::create("Can't open local file for writing: %s", $localFile->getPathname());
84 | }
85 |
86 | if (!flock($hLocal, \LOCK_EX)) {
87 | throw DownloaderException::create('Unable to obtain lock for file: %s', $localFile->getPathname());
88 | }
89 |
90 | return $hLocal;
91 | }
92 |
93 | /**
94 | * Правильно закрывает ресурс локального файла.
95 | *
96 | * @param resource $hLocal
97 | */
98 | private function closeLocalFile($hLocal): void
99 | {
100 | flock($hLocal, \LOCK_UN);
101 | fclose($hLocal);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/EntityDescriptor/EntityDescriptor.php:
--------------------------------------------------------------------------------
1 | name = $this->extractStringFromOptions($p, 'name', true);
38 | $this->description = $this->extractStringFromOptions($p, 'description');
39 | $this->type = $this->extractStringFromOptions($p, 'type', true);
40 | $this->subType = $this->extractStringFromOptions($p, 'subType');
41 | $this->length = isset($p['length']) ? (int) $p['length'] : null;
42 | $this->isNullable = !empty($p['isNullable']);
43 | $this->isPrimary = !empty($p['isPrimary']);
44 | $this->isIndex = !empty($p['isIndex']);
45 | $this->isPartition = !empty($p['isPartition']);
46 |
47 | if ($this->isPrimary && $this->isIndex) {
48 | throw new \InvalidArgumentException(
49 | 'Field is already primary, no needs to set index.'
50 | );
51 | }
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | #[\Override]
58 | public function getName(): string
59 | {
60 | return $this->name;
61 | }
62 |
63 | /**
64 | * {@inheritdoc}
65 | */
66 | #[\Override]
67 | public function getDescription(): string
68 | {
69 | return $this->description;
70 | }
71 |
72 | /**
73 | * {@inheritdoc}
74 | */
75 | #[\Override]
76 | public function getType(): string
77 | {
78 | return $this->type;
79 | }
80 |
81 | /**
82 | * {@inheritdoc}
83 | */
84 | #[\Override]
85 | public function getSubType(): string
86 | {
87 | return $this->subType;
88 | }
89 |
90 | /**
91 | * {@inheritdoc}
92 | */
93 | #[\Override]
94 | public function getLength(): ?int
95 | {
96 | return $this->length;
97 | }
98 |
99 | /**
100 | * {@inheritdoc}
101 | */
102 | #[\Override]
103 | public function isNullable(): bool
104 | {
105 | return $this->isNullable;
106 | }
107 |
108 | /**
109 | * {@inheritdoc}
110 | */
111 | #[\Override]
112 | public function isPrimary(): bool
113 | {
114 | return $this->isPrimary;
115 | }
116 |
117 | /**
118 | * {@inheritdoc}
119 | */
120 | #[\Override]
121 | public function isIndex(): bool
122 | {
123 | return $this->isIndex;
124 | }
125 |
126 | /**
127 | * {@inheritdoc}
128 | */
129 | #[\Override]
130 | public function isPartition(): bool
131 | {
132 | return $this->isPartition;
133 | }
134 |
135 | /**
136 | * Получает указанную строку из набора опций.
137 | *
138 | * @throws \InvalidArgumentException
139 | */
140 | protected function extractStringFromOptions(array $options, string $name, bool $required = false): string
141 | {
142 | $return = '';
143 |
144 | if (!isset($options[$name]) && $required) {
145 | throw new \InvalidArgumentException(
146 | "Option with key '{$name}' is required for EntityField."
147 | );
148 | } elseif (isset($options[$name])) {
149 | $return = trim((string) $options[$name]);
150 | }
151 |
152 | return $return;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/EntityField/EntityField.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | protected array $bindings;
23 |
24 | /**
25 | * @param array $bindings
26 | *
27 | * @throws \InvalidArgumentException
28 | */
29 | public function __construct(EntityRegistry $registry, array $bindings)
30 | {
31 | $this->registry = $registry;
32 |
33 | $this->bindings = [];
34 | foreach ($bindings as $entityName => $className) {
35 | $normalizedEntityName = $this->normalizeEntityName($entityName);
36 | $normalizedClassName = $this->normalizeClassName($className);
37 | if ($normalizedClassName === '') {
38 | throw new \InvalidArgumentException(
39 | "There is no class for {$entityName} entity name."
40 | );
41 | }
42 | $this->bindings[$normalizedEntityName] = $normalizedClassName;
43 | }
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | *
49 | * @throws EntityRegistryException
50 | */
51 | #[\Override]
52 | public function getDescriptorByEntityName(string $entityName): ?EntityDescriptor
53 | {
54 | $normalizedEntityName = $this->normalizeEntityName($entityName);
55 | $return = null;
56 |
57 | if (isset($this->bindings[$normalizedEntityName]) && $this->registry->hasDescriptor($normalizedEntityName)) {
58 | $return = $this->registry->getDescriptor($normalizedEntityName);
59 | }
60 |
61 | return $return;
62 | }
63 |
64 | /**
65 | * {@inheritdoc}
66 | */
67 | #[\Override]
68 | public function getClassByDescriptor(EntityDescriptor $descriptor): ?string
69 | {
70 | $normalizedEntityName = $this->normalizeEntityName($descriptor->getName());
71 |
72 | return $this->bindings[$normalizedEntityName] ?? null;
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | *
78 | * @throws EntityRegistryException
79 | */
80 | #[\Override]
81 | public function getDescriptorByInsertFile(string $insertFileName): ?EntityDescriptor
82 | {
83 | $return = null;
84 |
85 | foreach ($this->bindings as $entityName => $className) {
86 | $descriptor = $this->getDescriptorByEntityName($entityName);
87 | if ($descriptor && $descriptor->isFileNameFitsXmlInsertFileMask($insertFileName)) {
88 | $return = $descriptor;
89 | break;
90 | }
91 | }
92 |
93 | return $return;
94 | }
95 |
96 | /**
97 | * {@inheritdoc}
98 | *
99 | * @throws EntityRegistryException
100 | */
101 | #[\Override]
102 | public function getDescriptorByDeleteFile(string $insertFileName): ?EntityDescriptor
103 | {
104 | $return = null;
105 |
106 | foreach ($this->bindings as $entityName => $className) {
107 | $descriptor = $this->getDescriptorByEntityName($entityName);
108 | if ($descriptor && $descriptor->isFileNameFitsXmlDeleteFileMask($insertFileName)) {
109 | $return = $descriptor;
110 | break;
111 | }
112 | }
113 |
114 | return $return;
115 | }
116 |
117 | /**
118 | * {@inheritdoc}
119 | *
120 | * @throws EntityRegistryException
121 | */
122 | #[\Override]
123 | public function getDescriptorByClass(string $className): ?EntityDescriptor
124 | {
125 | $normalizedClassName = $this->normalizeClassName($className);
126 | $entityName = null;
127 |
128 | foreach ($this->bindings as $bindEntity => $bindClass) {
129 | if ($normalizedClassName === $bindClass) {
130 | $entityName = $bindEntity;
131 | break;
132 | }
133 | }
134 |
135 | return $entityName !== null && $entityName !== ''
136 | ? $this->getDescriptorByEntityName($entityName)
137 | : null;
138 | }
139 |
140 | /**
141 | * {@inheritdoc}
142 | *
143 | * @throws EntityRegistryException
144 | */
145 | #[\Override]
146 | public function getDescriptorByObject(mixed $object): ?EntityDescriptor
147 | {
148 | $return = null;
149 |
150 | if (\is_object($object)) {
151 | $return = $this->getDescriptorByClass(\get_class($object));
152 | }
153 |
154 | return $return;
155 | }
156 |
157 | /**
158 | * {@inheritdoc}
159 | */
160 | #[\Override]
161 | public function getBindedClasses(): array
162 | {
163 | return array_unique(array_values($this->bindings));
164 | }
165 |
166 | /**
167 | * Приводит имя сущности к единообразному виду.
168 | */
169 | protected function normalizeEntityName(string $entityName): string
170 | {
171 | return strtolower(trim($entityName));
172 | }
173 |
174 | /**
175 | * Приводит имя класса к единообразному виду.
176 | */
177 | protected function normalizeClassName(string $className): string
178 | {
179 | return trim($className, '\\ ');
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/EntityManager/EntityManager.php:
--------------------------------------------------------------------------------
1 | normalizeEntityName($entityName);
35 |
36 | foreach ($this->getDescriptors() as $descriptor) {
37 | $normalizedDescriptorName = $this->normalizeEntityName($descriptor->getName());
38 | if ($normalizedName === $normalizedDescriptorName) {
39 | $return = true;
40 | break;
41 | }
42 | }
43 |
44 | return $return;
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | #[\Override]
51 | public function getDescriptor(string $entityName): EntityDescriptor
52 | {
53 | $return = null;
54 | $normalizedName = $this->normalizeEntityName($entityName);
55 |
56 | foreach ($this->getDescriptors() as $descriptor) {
57 | $normalizedDescriptorName = $this->normalizeEntityName($descriptor->getName());
58 | if ($normalizedName === $normalizedDescriptorName) {
59 | $return = $descriptor;
60 | break;
61 | }
62 | }
63 |
64 | if (!$return) {
65 | throw new \InvalidArgumentException(
66 | "Can't fin entity with name '{$entityName}'."
67 | );
68 | }
69 |
70 | return $return;
71 | }
72 |
73 | /**
74 | * {@inheritdoc}
75 | */
76 | #[\Override]
77 | public function getDescriptors(): array
78 | {
79 | if ($this->registry === null) {
80 | try {
81 | $this->registry = $this->createRegistry();
82 | } catch (\Throwable $e) {
83 | throw new EntityRegistryException($e->getMessage(), 0, $e);
84 | }
85 | }
86 |
87 | return $this->registry;
88 | }
89 |
90 | /**
91 | * Приводит имена сущностей к единообразному виду.
92 | */
93 | public function normalizeEntityName(string $name): string
94 | {
95 | return trim(strtolower($name));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/EntityRegistry/ArrayEntityRegistry.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | protected array $arrayRegistry;
18 |
19 | /**
20 | * @throws \InvalidArgumentException
21 | */
22 | public function __construct(array $registry)
23 | {
24 | $this->arrayRegistry = [];
25 |
26 | foreach ($registry as $key => $descriptor) {
27 | if (!($descriptor instanceof EntityDescriptor)) {
28 | throw new \InvalidArgumentException(
29 | "Item with key {$key} must be an " . EntityDescriptor::class . ' instance.'
30 | );
31 | }
32 | $this->arrayRegistry[] = $descriptor;
33 | }
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | #[\Override]
40 | protected function createRegistry(): array
41 | {
42 | return $this->arrayRegistry;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/EntityRegistry/EntityRegistry.php:
--------------------------------------------------------------------------------
1 | pathToSource = $pathToSource ?? PathHelper::resource('fias_entities.php');
26 | }
27 |
28 | /**
29 | * {@inheritDoc}
30 | *
31 | * @psalm-suppress UnresolvableInclude
32 | */
33 | #[\Override]
34 | protected function createRegistry(): array
35 | {
36 | $registry = [];
37 |
38 | $fileData = include $this->checkAndReturnPath();
39 | $fileData = \is_array($fileData) ? $fileData : [];
40 |
41 | foreach ($fileData as $key => $entity) {
42 | if (!\is_array($entity)) {
43 | continue;
44 | }
45 | $entity['name'] = $key;
46 | $registry[] = $this->createEntityDescriptor($entity);
47 | }
48 |
49 | return $registry;
50 | }
51 |
52 | /**
53 | * Создает сущность из массива, который был записан в файле.
54 | *
55 | * @param mixed[] $entity
56 | *
57 | * @throws \InvalidArgumentException
58 | */
59 | private function createEntityDescriptor(array $entity): EntityDescriptor
60 | {
61 | if (!empty($entity['fields']) && \is_array($entity['fields'])) {
62 | $fields = [];
63 | foreach ($entity['fields'] as $key => $field) {
64 | if (!\is_array($field)) {
65 | continue;
66 | }
67 | $field['name'] = $key;
68 | $fields[] = $this->createEntityField($field);
69 | }
70 | $entity['fields'] = $fields;
71 | }
72 |
73 | return new BaseEntityDescriptor($entity);
74 | }
75 |
76 | /**
77 | * Создает поле из массива, который был записан в файле.
78 | *
79 | * @throws \InvalidArgumentException
80 | */
81 | private function createEntityField(array $field): EntityField
82 | {
83 | return new BaseEntityField($field);
84 | }
85 |
86 | /**
87 | * Проверяет, что путь до файла с описанием сущностей существует и возвращает его.
88 | */
89 | private function checkAndReturnPath(): string
90 | {
91 | $path = trim($this->pathToSource);
92 |
93 | if (!file_exists($path) || !is_readable($path)) {
94 | $message = \sprintf(
95 | "File '%s' for php entity registry must exists and be readable.",
96 | $this->pathToSource
97 | );
98 | throw new \InvalidArgumentException($message);
99 | }
100 |
101 | $extension = pathinfo($path, \PATHINFO_EXTENSION);
102 | if ($extension !== 'php') {
103 | $message = \sprintf(
104 | "File '%s' must has 'php' extension, got '%s'.",
105 | $this->pathToSource,
106 | $extension
107 | );
108 | throw new \InvalidArgumentException($message);
109 | }
110 |
111 | return $path;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Exception/DownloaderException.php:
--------------------------------------------------------------------------------
1 | trim((string) $param), $params);
27 |
28 | array_unshift($params, $message);
29 |
30 | /** @var string */
31 | $compiledMessage = \call_user_func_array('sprintf', $params);
32 |
33 | return new static($compiledMessage);
34 | }
35 |
36 | /**
37 | * Фабричный метод, который оборачивает готовое исключение другим.
38 | *
39 | * @psalm-suppress PossiblyInvalidArgument
40 | */
41 | public static function wrap(\Throwable $e): static
42 | {
43 | return $e instanceof static ? $e : new static($e->getMessage(), $e->getCode(), $e);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Exception/FiasInformerException.php:
--------------------------------------------------------------------------------
1 | getPathname(), $file->getSize());
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/FiasFile/FiasFileImpl.php:
--------------------------------------------------------------------------------
1 | size;
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | #[\Override]
33 | public function getName(): string
34 | {
35 | return $this->name;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function __toString(): string
42 | {
43 | return $this->name;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/FiasFileSelector/FiasFileSelector.php:
--------------------------------------------------------------------------------
1 | unpacker->isArchive($source);
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | #[\Override]
38 | public function selectFiles(\SplFileInfo $source): array
39 | {
40 | $selectedFiles = [];
41 | foreach ($this->unpacker->getListOfFiles($source) as $file) {
42 | if ($this->isFileAllowedForSelect($file)) {
43 | $selectedFiles[] = $file;
44 | }
45 | }
46 |
47 | return $selectedFiles;
48 | }
49 |
50 | /**
51 | * Проверяет, что файл подходит для обработки.
52 | */
53 | private function isFileAllowedForSelect(UnpackerFile $file): bool
54 | {
55 | $fileName = pathinfo($file->getName(), \PATHINFO_BASENAME);
56 |
57 | return $file->getSize() > 0
58 | && $this->filter?->test($file) !== false
59 | && (
60 | $this->isFileAllowedToInsert($fileName)
61 | || $this->isFileAllowedToDelete($fileName)
62 | );
63 | }
64 |
65 | /**
66 | * Проверяет нужно ли файл обрабатывать для создания и обновления в рамках данного процесса.
67 | */
68 | private function isFileAllowedToInsert(string $file): bool
69 | {
70 | $descriptor = $this->entityManager->getDescriptorByInsertFile($file);
71 |
72 | return $descriptor !== null && $this->entityManager->getClassByDescriptor($descriptor) !== null;
73 | }
74 |
75 | /**
76 | * Проверяет нужно ли файл обрабатывать для удаления в рамках данного процесса.
77 | */
78 | private function isFileAllowedToDelete(string $file): bool
79 | {
80 | $descriptor = $this->entityManager->getDescriptorByDeleteFile($file);
81 |
82 | return $descriptor !== null && $this->entityManager->getClassByDescriptor($descriptor) !== null;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/FiasFileSelector/FiasFileSelectorComposite.php:
--------------------------------------------------------------------------------
1 | $filesSelectors
15 | */
16 | public function __construct(private readonly iterable $filesSelectors)
17 | {
18 | }
19 |
20 | /**
21 | * {@inheritdoc}
22 | */
23 | #[\Override]
24 | public function supportSource(\SplFileInfo $source): bool
25 | {
26 | foreach ($this->filesSelectors as $selector) {
27 | if ($selector->supportSource($source)) {
28 | return true;
29 | }
30 | }
31 |
32 | return false;
33 | }
34 |
35 | /**
36 | * {@inheritdoc}
37 | */
38 | #[\Override]
39 | public function selectFiles(\SplFileInfo $source): array
40 | {
41 | foreach ($this->filesSelectors as $selector) {
42 | if ($selector->supportSource($source)) {
43 | return $selector->selectFiles($source);
44 | }
45 | }
46 |
47 | return [];
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/FiasFileSelector/FiasFileSelectorDir.php:
--------------------------------------------------------------------------------
1 | isDir();
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | #[\Override]
38 | public function selectFiles(\SplFileInfo $source): array
39 | {
40 | $selectedFiles = [];
41 | $iterator = $this->fs->createDirectoryIterator($source);
42 | foreach ($iterator as $object) {
43 | if ($object->isFile() && $this->isFileAllowedForSelect($object)) {
44 | $selectedFiles[] = FiasFileFactory::createFromSplFileInfo($object);
45 | }
46 | }
47 |
48 | return $selectedFiles;
49 | }
50 |
51 | /**
52 | * Проверяет, что файл подходит для обработки.
53 | */
54 | private function isFileAllowedForSelect(\SplFileInfo $file): bool
55 | {
56 | return $file->getSize() > 0
57 | && $this->filter?->test($file) !== false
58 | && (
59 | $this->isFileAllowedToInsert($file->getBasename())
60 | || $this->isFileAllowedToDelete($file->getBasename())
61 | );
62 | }
63 |
64 | /**
65 | * Проверяет нужно ли файл обрабатывать для создания и обновления в рамках данного процесса.
66 | */
67 | private function isFileAllowedToInsert(string $file): bool
68 | {
69 | $descriptor = $this->entityManager->getDescriptorByInsertFile($file);
70 |
71 | return $descriptor !== null && $this->entityManager->getClassByDescriptor($descriptor) !== null;
72 | }
73 |
74 | /**
75 | * Проверяет нужно ли файл обрабатывать для удаления в рамках данного процесса.
76 | */
77 | private function isFileAllowedToDelete(string $file): bool
78 | {
79 | $descriptor = $this->entityManager->getDescriptorByDeleteFile($file);
80 |
81 | return $descriptor !== null && $this->entityManager->getClassByDescriptor($descriptor) !== null;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/FiasInformer/FiasInformer.php:
--------------------------------------------------------------------------------
1 | transport = $transport;
29 | $this->endpointAll = $endpointAll instanceof FiasLink ? $endpointAll->value : $endpointAll;
30 | $this->endpointLast = $endpointLast instanceof FiasLink ? $endpointLast->value : $endpointLast;
31 | }
32 |
33 | /**
34 | * {@inheritDoc}
35 | */
36 | #[\Override]
37 | public function getLatestVersion(): FiasInformerResponse
38 | {
39 | return FiasInformerResponseFactory::createFromJson(
40 | $this->query($this->endpointLast)
41 | );
42 | }
43 |
44 | /**
45 | * {@inheritDoc}
46 | */
47 | #[\Override]
48 | public function getNextVersion(int|FiasInformerResponse $currentVersion): ?FiasInformerResponse
49 | {
50 | $currentVersionId = $currentVersion instanceof FiasInformerResponse ? $currentVersion->getVersion() : $currentVersion;
51 | if ($currentVersionId <= 0) {
52 | throw FiasInformerException::create('Version number must be more that 0');
53 | }
54 |
55 | $deltas = $this->getAllVersions();
56 | foreach ($deltas as $delta) {
57 | if ($delta->getVersion() > $currentVersionId) {
58 | return $delta;
59 | }
60 | }
61 |
62 | return null;
63 | }
64 |
65 | /**
66 | * {@inheritDoc}
67 | */
68 | #[\Override]
69 | public function getAllVersions(): array
70 | {
71 | $data = $this->query($this->endpointAll);
72 |
73 | $list = [];
74 | foreach ($data as $item) {
75 | if (\is_array($item)) {
76 | $list[] = FiasInformerResponseFactory::createFromJson($item);
77 | }
78 | }
79 |
80 | usort(
81 | $list,
82 | fn (FiasInformerResponse $a, FiasInformerResponse $b): int => $a->getVersion() - $b->getVersion()
83 | );
84 |
85 | return $list;
86 | }
87 |
88 | /**
89 | * Отправляет запрос и проверяет ответ.
90 | */
91 | private function query(string $url): array
92 | {
93 | try {
94 | $response = $this->transport->get($url);
95 | } catch (\Throwable $e) {
96 | throw FiasInformerException::wrap($e);
97 | }
98 |
99 | if (!$response->isOk()) {
100 | throw FiasInformerException::create("Informer '%s' responsed with bad status: %s", $url, $response->getStatusCode());
101 | }
102 |
103 | try {
104 | $data = $response->getJsonPayload();
105 | } catch (\Throwable $e) {
106 | throw FiasInformerException::wrap($e);
107 | }
108 |
109 | if (!\is_array($data)) {
110 | throw FiasInformerException::create('Response from informer is malformed');
111 | }
112 |
113 | return $data;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/FiasInformer/FiasInformerResponse.php:
--------------------------------------------------------------------------------
1 | checkUrl($fullUrl);
27 | }
28 | if ($deltaUrl !== '') {
29 | $this->checkUrl($deltaUrl);
30 | }
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | #[\Override]
37 | public function getVersion(): int
38 | {
39 | return $this->version;
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | #[\Override]
46 | public function getFullUrl(): string
47 | {
48 | return $this->fullUrl;
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | #[\Override]
55 | public function getDeltaUrl(): string
56 | {
57 | return $this->deltaUrl;
58 | }
59 |
60 | /**
61 | * Выбрасывает исключение, если ссылка задана в неверном формате.
62 | */
63 | private function checkUrl(string $url): void
64 | {
65 | if (!preg_match('#https?://.+#', $url)) {
66 | throw FiasInformerException::create("String '%s' is not an url", $url);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/FiasStatusChecker/FiasStatusChecker.php:
--------------------------------------------------------------------------------
1 | getFiasInformerStatus(),
30 | $this->getFileServerStatus(),
31 | ];
32 |
33 | foreach ($statusesPerServices as $status) {
34 | if ($status->getStatus() !== FiasStatusCheckerStatus::AVAILABLE) {
35 | return new FiasStatusCheckerResultImpl(FiasStatusCheckerStatus::NOT_AVAILABLE, $statusesPerServices);
36 | }
37 | }
38 |
39 | return new FiasStatusCheckerResultImpl(FiasStatusCheckerStatus::AVAILABLE, $statusesPerServices);
40 | }
41 |
42 | /**
43 | * Возвращает состояние сервиса информирования.
44 | */
45 | private function getFiasInformerStatus(): FiasStatusCheckerResultForService
46 | {
47 | $status = FiasStatusCheckerStatus::AVAILABLE;
48 | $service = FiasStatusCheckerService::INFORMER;
49 | $reason = '';
50 |
51 | try {
52 | $this->informer->getLatestVersion();
53 | } catch (\Throwable $e) {
54 | $status = FiasStatusCheckerStatus::NOT_AVAILABLE;
55 | $reason = $e->getMessage();
56 | }
57 |
58 | return new FiasStatusCheckerResultForServiceImpl($status, $service, $reason);
59 | }
60 |
61 | /**
62 | * Возвращает состояние файлового сервера.
63 | */
64 | private function getFileServerStatus(): FiasStatusCheckerResultForService
65 | {
66 | $service = FiasStatusCheckerService::FILE_SERVER;
67 |
68 | try {
69 | $url = $this->informer->getLatestVersion()->getFullUrl();
70 | } catch (\Throwable $e) {
71 | return new FiasStatusCheckerResultForServiceImpl(
72 | FiasStatusCheckerStatus::UNKNOWN,
73 | $service,
74 | 'Informer is unavailable'
75 | );
76 | }
77 |
78 | try {
79 | $response = $this->transport->head($url);
80 | if (!$response->isOk()) {
81 | throw HttpTransportException::create(
82 | "Can't reach file '%s', bad status '%s'",
83 | $url,
84 | $response->getStatusCode()
85 | );
86 | }
87 | } catch (\Throwable $e) {
88 | return new FiasStatusCheckerResultForServiceImpl(
89 | FiasStatusCheckerStatus::NOT_AVAILABLE,
90 | $service,
91 | $e->getMessage()
92 | );
93 | }
94 |
95 | return new FiasStatusCheckerResultForServiceImpl(FiasStatusCheckerStatus::AVAILABLE, $service);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/FiasStatusChecker/FiasStatusCheckerResult.php:
--------------------------------------------------------------------------------
1 | status;
26 | }
27 |
28 | /**
29 | * {@inheritDoc}
30 | */
31 | #[\Override]
32 | public function getService(): FiasStatusCheckerService
33 | {
34 | return $this->service;
35 | }
36 |
37 | /**
38 | * {@inheritDoc}
39 | */
40 | #[\Override]
41 | public function getReason(): string
42 | {
43 | return $this->reason;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/FiasStatusChecker/FiasStatusCheckerResultImpl.php:
--------------------------------------------------------------------------------
1 | resultStatus;
28 | }
29 |
30 | /**
31 | * {@inheritdoc}
32 | */
33 | #[\Override]
34 | public function getPerServiceStatuses(): array
35 | {
36 | return $this->perServiceStatuses;
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | #[\Override]
43 | public function canProceed(): bool
44 | {
45 | return $this->resultStatus === FiasStatusCheckerStatus::AVAILABLE;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/FiasStatusChecker/FiasStatusCheckerService.php:
--------------------------------------------------------------------------------
1 | sortBySizeDesc($files);
26 |
27 | $threadsFiles = [];
28 | $filesSizeInThreads = array_fill(0, $processesCount, 0);
29 | $relatedItems = [];
30 |
31 | foreach ($files as $file) {
32 | $entity = $this->getEntityNameToInsert($file);
33 | if ($entity !== null) {
34 | $region = $this->getRegionNumberForFile($file);
35 | $thread = $relatedItems["{$region}_{$entity}"] ?? $this->getThreadIdWithMinSize($filesSizeInThreads);
36 | $relatedItems["{$region}_{$entity}"] = $thread;
37 | $filesSizeInThreads[$thread] += $file->getSize();
38 | $threadsFiles[$thread][] = $file;
39 | }
40 | }
41 |
42 | foreach ($files as $file) {
43 | $entity = $this->getEntityNameToDelete($file);
44 | if ($entity !== null) {
45 | $region = $this->getRegionNumberForFile($file);
46 | $thread = $relatedItems["{$region}_{$entity}"] ?? $this->getThreadIdWithMinSize($filesSizeInThreads);
47 | $relatedItems["{$region}_{$entity}"] = $thread;
48 | $filesSizeInThreads[$thread] += $file->getSize();
49 | $threadsFiles[$thread][] = $file;
50 | }
51 | }
52 |
53 | return $threadsFiles;
54 | }
55 |
56 | /**
57 | * Сортирует файлы по размеру по убыванию, чтобы было легче балансировать количество данных в потоках.
58 | *
59 | * @param FiasFile[] $files
60 | *
61 | * @return FiasFile[]
62 | */
63 | private function sortBySizeDesc(array $files): array
64 | {
65 | usort(
66 | $files,
67 | fn (FiasFile $a, FiasFile $b): int => $b->getSize() <=> $a->getSize()
68 | );
69 |
70 | return $files;
71 | }
72 |
73 | /**
74 | * Возвращает имя сущности, к которой привязан указанный файл, если такая сущность указана.
75 | */
76 | private function getEntityNameToInsert(FiasFile $file): ?string
77 | {
78 | $fileName = pathinfo($file->getName(), \PATHINFO_BASENAME);
79 |
80 | return $this->entityManager->getDescriptorByInsertFile($fileName)?->getName();
81 | }
82 |
83 | /**
84 | * Возвращает имя сущности, к которой привязан указанный файл, если такая сущность указана.
85 | */
86 | private function getEntityNameToDelete(FiasFile $file): ?string
87 | {
88 | $fileName = pathinfo($file->getName(), \PATHINFO_BASENAME);
89 |
90 | return $this->entityManager->getDescriptorByDeleteFile($fileName)?->getName();
91 | }
92 |
93 | /**
94 | * Возвращает номер региона для указанного имени файла.
95 | */
96 | private function getRegionNumberForFile(FiasFile $file): ?int
97 | {
98 | if (preg_match("#^/?(\d+)/.*#", $file->getName(), $matches)) {
99 | return (int) $matches[1];
100 | }
101 |
102 | return null;
103 | }
104 |
105 | /**
106 | * Возвращает идентификатор трэда с наименьшим размером файлов.
107 | *
108 | * @param array $filesSizeInThreads
109 | */
110 | private function getThreadIdWithMinSize(array $filesSizeInThreads): int
111 | {
112 | $minId = 0;
113 | $minSize = null;
114 | foreach ($filesSizeInThreads as $id => $size) {
115 | if ($size === 0) {
116 | return $id;
117 | }
118 | if ($minSize === null || $minSize > $size) {
119 | $minId = $id;
120 | $minSize = $size;
121 | }
122 | }
123 |
124 | return $minId;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Filter/Filter.php:
--------------------------------------------------------------------------------
1 | __toString();
30 | } else {
31 | $message = 'This filter supports only strings or objects that can be coverted to strings.';
32 | throw new \InvalidArgumentException($message);
33 | }
34 |
35 | if (empty($this->regexps)) {
36 | return true;
37 | }
38 |
39 | $isTested = false;
40 | foreach ($this->regexps as $regexp) {
41 | if ($regexp !== '' && preg_match($regexp, $testData)) {
42 | $isTested = true;
43 | break;
44 | }
45 | }
46 |
47 | return $isTested;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Helper/ArrayHelper.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | public function getHeaders(): array;
23 |
24 | /**
25 | * Возвращает правду, если ответ был успешным.
26 | */
27 | public function isOk(): bool;
28 |
29 | /**
30 | * Возвращает длину тела ответ.
31 | */
32 | public function getContentLength(): int;
33 |
34 | /**
35 | * Возвращает правду, если сервер поддерживает докачку файла.
36 | */
37 | public function isRangeSupported(): bool;
38 |
39 | /**
40 | * Возвращает тело ответа.
41 | */
42 | public function getPayload(): string;
43 |
44 | /**
45 | * Возвращает декодированное из json тело ответа.
46 | */
47 | public function getJsonPayload(): mixed;
48 | }
49 |
--------------------------------------------------------------------------------
/src/HttpTransport/HttpTransportResponseFactory.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | private readonly array $headers;
18 |
19 | public function __construct(
20 | private readonly int $statusCode,
21 | array $headers = [],
22 | private readonly string $payload = '',
23 | private readonly mixed $payloadJson = null,
24 | ) {
25 | $this->headers = $this->prepareHeaders($headers);
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | #[\Override]
32 | public function getStatusCode(): int
33 | {
34 | return $this->statusCode;
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | #[\Override]
41 | public function getHeaders(): array
42 | {
43 | return $this->headers;
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | #[\Override]
50 | public function isOk(): bool
51 | {
52 | return $this->statusCode >= 200 && $this->statusCode < 300;
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | #[\Override]
59 | public function getContentLength(): int
60 | {
61 | return (int) ($this->headers['content-length'] ?? 0);
62 | }
63 |
64 | /**
65 | * {@inheritdoc}
66 | */
67 | #[\Override]
68 | public function isRangeSupported(): bool
69 | {
70 | return $this->getContentLength() > 0 && ($this->headers['accept-ranges'] ?? '') === 'bytes';
71 | }
72 |
73 | /**
74 | * {@inheritdoc}
75 | */
76 | #[\Override]
77 | public function getPayload(): string
78 | {
79 | return $this->payload;
80 | }
81 |
82 | /**
83 | * {@inheritdoc}
84 | */
85 | #[\Override]
86 | public function getJsonPayload(): mixed
87 | {
88 | return $this->payloadJson;
89 | }
90 |
91 | /**
92 | * Подготавливает заголовки для использования.
93 | *
94 | * @return array
95 | */
96 | private function prepareHeaders(array $headers): array
97 | {
98 | $preparedHeaders = [];
99 | foreach ($headers as $name => $value) {
100 | $name = str_replace('_', '-', strtolower(trim((string) $name)));
101 | $value = strtolower(trim((string) $value));
102 | $preparedHeaders[$name] = $value;
103 | }
104 |
105 | return $preparedHeaders;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Pipeline/Pipe/Pipe.php:
--------------------------------------------------------------------------------
1 | */
14 | private readonly array $parameters = [],
15 | private readonly bool $isCompleted = false,
16 | ) {
17 | foreach ($this->parameters as $name => $value) {
18 | if (!StateParameter::tryFrom($name)) {
19 | throw new \InvalidArgumentException("'{$name}' isn't found in " . StateParameter::class);
20 | }
21 | }
22 | }
23 |
24 | /**
25 | * {@inheritdoc}
26 | */
27 | #[\Override]
28 | public function setParameter(StateParameter $parameter, mixed $parameterValue): self
29 | {
30 | $parameters = $this->parameters;
31 | $parameters[$parameter->value] = $parameterValue;
32 |
33 | return new self($parameters, $this->isCompleted);
34 | }
35 |
36 | /**
37 | * {@inheritdoc}
38 | */
39 | #[\Override]
40 | public function complete(): self
41 | {
42 | return new self($this->parameters, true);
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | #[\Override]
49 | public function getParameter(StateParameter $parameter, mixed $default = null): mixed
50 | {
51 | return $this->parameters[$parameter->value] ?? $default;
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | #[\Override]
58 | public function getParameterInt(StateParameter $parameter, int $default = 0): int
59 | {
60 | return (int) $this->getParameter($parameter, $default);
61 | }
62 |
63 | /**
64 | * {@inheritdoc}
65 | */
66 | #[\Override]
67 | public function getParameterString(StateParameter $parameter, string $default = ''): string
68 | {
69 | return (string) $this->getParameter($parameter, $default);
70 | }
71 |
72 | /**
73 | * {@inheritdoc}
74 | */
75 | #[\Override]
76 | public function isCompleted(): bool
77 | {
78 | return $this->isCompleted;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Pipeline/State/State.php:
--------------------------------------------------------------------------------
1 | getParameter(StateParameter::FILES_TO_PROCEED);
30 | if (!\is_array($files)) {
31 | throw TaskException::create("'%s' param must be an array", StateParameter::FILES_TO_PROCEED->value);
32 | }
33 |
34 | foreach ($files as $file) {
35 | $fileState = $state->setParameter(
36 | StateParameter::FILES_TO_PROCEED,
37 | $this->createNewFilesArrayFromFile($file)
38 | );
39 | $this->pipe->run($fileState);
40 | }
41 |
42 | return $state;
43 | }
44 |
45 | /**
46 | * Превращает файл в новый массив для StateParameter::FILES_TO_PROCEED.
47 | */
48 | private function createNewFilesArrayFromFile(mixed $file): array
49 | {
50 | if (($file instanceof FiasFile) && !($file instanceof UnpackerFile)) {
51 | return [
52 | $file->getName(),
53 | ];
54 | }
55 |
56 | return [
57 | $file,
58 | ];
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/CheckStatusTask.php:
--------------------------------------------------------------------------------
1 | checker->check();
30 |
31 | if (!$status->canProceed()) {
32 | $message = 'There are some troubles on the FIAS side. Please try again later';
33 | $this->log(
34 | LogLevel::ERROR,
35 | $message,
36 | [
37 | 'services_statuses' => $status->getPerServiceStatuses(),
38 | ]
39 | );
40 | throw new StatusCheckerException($message);
41 | }
42 |
43 | return $state;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/CleanupFilesUnpacked.php:
--------------------------------------------------------------------------------
1 | getParameter(StateParameter::FILES_UNPACKED);
30 | if (!\is_array($files)) {
31 | return $state;
32 | }
33 |
34 | foreach ($files as $file) {
35 | $this->fs->removeIfExists((string) $file);
36 | $this->log(
37 | LogLevel::INFO,
38 | 'Item is cleaned up',
39 | [
40 | 'path' => $file,
41 | ]
42 | );
43 | }
44 |
45 | return $state;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/CleanupTask.php:
--------------------------------------------------------------------------------
1 | getParameterString(StateParameter::PATH_TO_DOWNLOAD_FILE),
31 | $state->getParameterString(StateParameter::PATH_TO_EXTRACT_FOLDER),
32 | ];
33 |
34 | foreach ($toRemove as $path) {
35 | if ($path !== '') {
36 | $this->fs->removeIfExists($path);
37 | $this->log(
38 | LogLevel::INFO,
39 | 'Item is cleaned up',
40 | [
41 | 'path' => $path,
42 | ]
43 | );
44 | }
45 | }
46 |
47 | return $state;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/DataAbstractTask.php:
--------------------------------------------------------------------------------
1 | getParameter(StateParameter::FILES_TO_PROCEED);
56 | $allFiles = \is_array($allFiles) ? $allFiles : [];
57 |
58 | foreach ($allFiles as $file) {
59 | $fileInfo = new \SplFileInfo((string) $file);
60 | if ($descriptor = $this->getFileDescriptor($fileInfo)) {
61 | $this->processFile($fileInfo, $descriptor);
62 | }
63 | }
64 |
65 | return $state;
66 | }
67 |
68 | /**
69 | * Обрабатывает указанный файл.
70 | *
71 | * @throws TaskException
72 | * @throws StorageException
73 | * @throws XmlException
74 | */
75 | protected function processFile(\SplFileInfo $fileInfo, EntityDescriptor $descriptor): void
76 | {
77 | $entityClass = $this->entityManager->getClassByDescriptor($descriptor);
78 | if ($entityClass !== null && $entityClass !== '') {
79 | $this->processDataFromFile($fileInfo, $descriptor->getXmlPath(), $entityClass);
80 | gc_collect_cycles();
81 | }
82 | }
83 |
84 | /**
85 | * Обрабатывает данные из файла и передает в хранилище.
86 | *
87 | * @throws TaskException
88 | * @throws StorageException
89 | * @throws XmlException
90 | */
91 | protected function processDataFromFile(\SplFileInfo $fileInfo, string $xpath, string $entityClass): void
92 | {
93 | $this->log(
94 | LogLevel::INFO,
95 | "Start processing '{$fileInfo->getRealPath()}' file for '{$entityClass}' entity",
96 | [
97 | 'entity' => $entityClass,
98 | 'path' => $fileInfo->getRealPath(),
99 | ]
100 | );
101 |
102 | $total = 0;
103 | $this->xmlReader->open($fileInfo, $xpath);
104 | $this->storage->start();
105 | try {
106 | foreach ($this->xmlReader as $xml) {
107 | $item = $this->deserializeXmlStringToObject($xml, $entityClass);
108 | if (!$this->storage->supports($item)) {
109 | continue;
110 | }
111 | $this->processItem($item);
112 | unset($item, $xml);
113 | ++$total;
114 | }
115 | } finally {
116 | $this->storage->stop();
117 | $this->xmlReader->close();
118 | }
119 |
120 | $this->log(
121 | LogLevel::INFO,
122 | "Completed processing '{$fileInfo->getRealPath()}' file for '{$entityClass}' entity. {$total} items processed",
123 | [
124 | 'entity' => $entityClass,
125 | 'path' => $fileInfo->getRealPath(),
126 | ]
127 | );
128 | }
129 |
130 | /**
131 | * Преобразует xml строку в объект указанного класса.
132 | *
133 | * @throws TaskException
134 | */
135 | protected function deserializeXmlStringToObject(?string $xml, string $entityClass): object
136 | {
137 | try {
138 | $entity = $this->serializer->deserialize(
139 | $xml,
140 | $entityClass,
141 | FiasSerializerFormat::XML->value,
142 | [
143 | FiasSerializerContextParam::FIAS_FLAG->value => true,
144 | FiasSerializerContextParam::FIAS_ENTITY->value => $entityClass,
145 | XmlEncoder::TYPE_CAST_ATTRIBUTES => false,
146 | ]
147 | );
148 | } catch (\Throwable $e) {
149 | throw new TaskException(
150 | message: "Deserialization error while deserialization of '{$xml}' string to object with '{$entityClass}' class",
151 | previous: $e
152 | );
153 | }
154 |
155 | if (!\is_object($entity)) {
156 | throw new TaskException('Serializer must returns an object instance');
157 | }
158 |
159 | return $entity;
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/DataDeleteTask.php:
--------------------------------------------------------------------------------
1 | entityManager->getDescriptorByDeleteFile($file->getBasename());
22 | }
23 |
24 | /**
25 | * {@inheritDoc}
26 | *
27 | * @throws StorageException
28 | */
29 | #[\Override]
30 | protected function processItem(object $item): void
31 | {
32 | $this->storage->delete($item);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/DataInsertTask.php:
--------------------------------------------------------------------------------
1 | entityManager->getDescriptorByInsertFile($file->getBasename());
22 | }
23 |
24 | /**
25 | * {@inheritDoc}
26 | *
27 | * @throws StorageException
28 | */
29 | #[\Override]
30 | protected function processItem(object $item): void
31 | {
32 | $this->storage->insert($item);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/DataUpsertTask.php:
--------------------------------------------------------------------------------
1 | entityManager->getDescriptorByInsertFile($file->getBasename());
23 | }
24 |
25 | /**
26 | * {@inheritDoc}
27 | *
28 | * @throws StorageException
29 | */
30 | #[\Override]
31 | protected function processItem(object $item): void
32 | {
33 | $this->storage->upsert($item);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/DownloadTask.php:
--------------------------------------------------------------------------------
1 | getParameterString(StateParameter::FIAS_VERSION_ARCHIVE_URL);
32 | if ($url === '') {
33 | throw TaskException::create("Source url isn't set");
34 | }
35 |
36 | $filePath = $state->getParameterString(StateParameter::PATH_TO_DOWNLOAD_FILE);
37 | if ($filePath === '') {
38 | throw TaskException::create("Destination path isn't set");
39 | }
40 |
41 | $this->log(
42 | LogLevel::INFO,
43 | 'Downloading file',
44 | [
45 | 'url' => $url,
46 | 'destination' => $filePath,
47 | ]
48 | );
49 |
50 | $this->downloader->download($url, new \SplFileInfo($filePath));
51 |
52 | $this->log(
53 | LogLevel::INFO,
54 | 'File downloaded',
55 | [
56 | 'url' => $url,
57 | 'destination' => $filePath,
58 | ]
59 | );
60 |
61 | return $state->setParameter(StateParameter::PATH_TO_SOURCE, $filePath);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/InformDeltaTask.php:
--------------------------------------------------------------------------------
1 | getParameterInt(StateParameter::FIAS_VERSION_NUMBER);
32 | if ($version <= 0) {
33 | throw new TaskException('Version parameter must exist and be greater than zero');
34 | }
35 |
36 | $info = $this->informer->getNextVersion($version);
37 | if ($info === null) {
38 | $state = $state->complete();
39 | $this->log(
40 | LogLevel::INFO,
41 | "Current version '{$version}' is up to date",
42 | [
43 | 'current_version' => $version,
44 | ]
45 | );
46 | } else {
47 | $this->log(
48 | LogLevel::INFO,
49 | "Current version of FIAS is '{$version}', next version is '{$info->getVersion()}' and can be downloaded from '{$info->getDeltaUrl()}'",
50 | [
51 | 'current_version' => $version,
52 | 'next_version' => $info->getVersion(),
53 | 'url' => $info->getDeltaUrl(),
54 | ]
55 | );
56 | $state = $state->setParameter(StateParameter::FIAS_NEXT_VERSION_NUMBER, $info->getVersion())
57 | ->setParameter(StateParameter::FIAS_NEXT_VERSION_FULL_URL, $info->getFullUrl())
58 | ->setParameter(StateParameter::FIAS_NEXT_VERSION_DELTA_URL, $info->getDeltaUrl())
59 | ->setParameter(StateParameter::FIAS_VERSION_ARCHIVE_URL, $info->getDeltaUrl());
60 | }
61 |
62 | return $state;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/InformFullTask.php:
--------------------------------------------------------------------------------
1 | informer->getLatestVersion();
30 |
31 | $this->log(
32 | LogLevel::INFO,
33 | "Full version of FIAS is '{$info->getVersion()}' and can be downloaded from '{$info->getFullUrl()}'",
34 | [
35 | 'next_version' => $info->getVersion(),
36 | 'url' => $info->getFullUrl(),
37 | ]
38 | );
39 |
40 | return $state->setParameter(StateParameter::FIAS_NEXT_VERSION_NUMBER, $info->getVersion())
41 | ->setParameter(StateParameter::FIAS_NEXT_VERSION_FULL_URL, $info->getFullUrl())
42 | ->setParameter(StateParameter::FIAS_NEXT_VERSION_DELTA_URL, $info->getDeltaUrl())
43 | ->setParameter(StateParameter::FIAS_VERSION_ARCHIVE_URL, $info->getFullUrl());
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/LoggableTask.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
24 | $this->defaultContext = $defaultContext;
25 | }
26 |
27 | /**
28 | * Записывает сообщение в лог.
29 | */
30 | public function log(string $logLevel, string $message, array $context = []): void
31 | {
32 | if ($this->logger) {
33 | $context = array_merge($this->defaultContext, $context);
34 | $this->logger->log($logLevel, $message, $context);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/PrepareFolderTask.php:
--------------------------------------------------------------------------------
1 | folder = new \SplFileInfo($trimmedFolder);
28 | }
29 |
30 | /**
31 | * {@inheritDoc}
32 | */
33 | #[\Override]
34 | public function run(State $state): State
35 | {
36 | $this->log(LogLevel::INFO, "Emptying '{$this->folder->getPathname()}' folder");
37 | $this->fs->mkdirIfNotExist($this->folder);
38 | $this->fs->emptyDir($this->folder);
39 |
40 | $downloadToFile = new \SplFileInfo($this->folder->getRealPath() . '/archive');
41 | $extractToFolder = new \SplFileInfo($this->folder->getRealPath() . '/extracted');
42 |
43 | $this->log(LogLevel::INFO, "Creating '{$this->folder->getRealPath()}/extracted' folder");
44 | $this->fs->mkdir($extractToFolder);
45 |
46 | return $state->setParameter(StateParameter::PATH_TO_DOWNLOAD_FILE, $downloadToFile->getPathname())
47 | ->setParameter(StateParameter::PATH_TO_EXTRACT_FOLDER, $extractToFolder->getPathname());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/SaveFiasFilesTask.php:
--------------------------------------------------------------------------------
1 | moveArchiveTo !== null) {
35 | $movePaths[StateParameter::PATH_TO_DOWNLOAD_FILE->value] = $this->moveArchiveTo;
36 | }
37 | if ($this->moveExtractedTo !== null) {
38 | $movePaths[StateParameter::PATH_TO_EXTRACT_FOLDER->value] = $this->moveExtractedTo;
39 | }
40 |
41 | foreach ($movePaths as $paramName => $moveTo) {
42 | $moveFrom = $state->getParameterString(StateParameter::from($paramName));
43 | $this->log(LogLevel::INFO, "Moving '{$moveFrom}' to '{$moveTo}'");
44 | $this->fs->rename($moveFrom, $moveTo);
45 | }
46 |
47 | return $state;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/SelectFilesToProceedTask.php:
--------------------------------------------------------------------------------
1 | getParameterString(StateParameter::PATH_TO_SOURCE);
32 | if ($pathToSource === '') {
33 | throw TaskException::create("'%s' is not a valid source", $pathToSource);
34 | }
35 |
36 | $source = new \SplFileInfo($pathToSource);
37 |
38 | $this->log(
39 | LogLevel::INFO,
40 | 'Selecting files from source',
41 | [
42 | 'path' => $pathToSource,
43 | ]
44 | );
45 |
46 | if ($this->fiasFileSelector->supportSource($source)) {
47 | $files = $this->fiasFileSelector->selectFiles($source);
48 | } else {
49 | $files = [];
50 | }
51 |
52 | if (\count($files) === 0) {
53 | $this->log(
54 | LogLevel::INFO,
55 | 'No files selected from source',
56 | [
57 | 'source' => $pathToSource,
58 | 'files' => 0,
59 | ]
60 | );
61 |
62 | return $state->complete();
63 | }
64 |
65 | $this->log(
66 | LogLevel::INFO,
67 | 'Files selected from source',
68 | [
69 | 'source' => $pathToSource,
70 | 'files' => \count($files),
71 | ]
72 | );
73 |
74 | return $state->setParameter(StateParameter::FILES_TO_PROCEED, $files);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/Task.php:
--------------------------------------------------------------------------------
1 | storage->start();
33 | foreach ($this->entityManager->getBindedClasses() as $className) {
34 | if (!$this->storage->supportsClass($className)) {
35 | continue;
36 | }
37 | $this->log(
38 | LogLevel::INFO, "Truncating '{$className}' entity",
39 | [
40 | 'entity' => $className,
41 | ]
42 | );
43 | $this->storage->truncate($className);
44 | }
45 | $this->storage->stop();
46 |
47 | return $state;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/UnpackTask.php:
--------------------------------------------------------------------------------
1 | getParameter(StateParameter::FILES_TO_PROCEED);
32 | if (!\is_array($rawFiles)) {
33 | throw TaskException::create("'%s' param must be an array", StateParameter::FILES_TO_PROCEED->value);
34 | }
35 |
36 | $files = [];
37 | $filesUnpacked = [];
38 | foreach ($rawFiles as $rawFile) {
39 | if ($rawFile instanceof UnpackerFile) {
40 | $files[] = $filesUnpacked[] = $this->unpackFile($rawFile, $state);
41 | } else {
42 | $files[] = $rawFile;
43 | }
44 | }
45 |
46 | return $state->setParameter(StateParameter::FILES_TO_PROCEED, $files)
47 | ->setParameter(StateParameter::FILES_UNPACKED, $filesUnpacked);
48 | }
49 |
50 | /**
51 | * Распаковывает файл и возвращает путь к нему.
52 | */
53 | private function unpackFile(UnpackerFile $file, State $state): string
54 | {
55 | $destination = $state->getParameterString(StateParameter::PATH_TO_EXTRACT_FOLDER);
56 | if ($destination === '') {
57 | throw new TaskException('Destination path must be a non empty string');
58 | } else {
59 | $destination = new \SplFileInfo($destination);
60 | }
61 |
62 | $res = $this->unpacker->unpackFile(
63 | $file->getArchiveFile(),
64 | $file->getName(),
65 | $destination
66 | );
67 |
68 | $this->log(
69 | LogLevel::INFO,
70 | 'File is unpacked',
71 | [
72 | 'name' => $file->getName(),
73 | 'archive' => $file->getArchiveFile()->getPathname(),
74 | 'destination' => $destination->getPathname(),
75 | 'path' => $res->getRealPath(),
76 | ]
77 | );
78 |
79 | return $res->getRealPath();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/VersionGetTask.php:
--------------------------------------------------------------------------------
1 | versionManager->getCurrentVersion();
28 |
29 | if ($version === null) {
30 | throw TaskException::create('There is no version of FIAS installed');
31 | }
32 |
33 | return $state->setParameter(StateParameter::FIAS_VERSION_NUMBER, $version->getVersion());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Pipeline/Task/VersionSetTask.php:
--------------------------------------------------------------------------------
1 | getParameterInt(StateParameter::FIAS_NEXT_VERSION_NUMBER);
28 |
29 | if ($version > 0) {
30 | $version = FiasInformerResponseFactory::create(
31 | $version,
32 | $state->getParameterString(StateParameter::FIAS_NEXT_VERSION_FULL_URL),
33 | $state->getParameterString(StateParameter::FIAS_NEXT_VERSION_DELTA_URL)
34 | );
35 | $this->versionManager->setCurrentVersion($version);
36 | }
37 |
38 | return $state;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Serializer/FiasFileDenormalizer.php:
--------------------------------------------------------------------------------
1 | true,
53 | ];
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Serializer/FiasFileNormalizer.php:
--------------------------------------------------------------------------------
1 | $data->getName(),
29 | 'size' => $data->getSize(),
30 | ];
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | #[\Override]
37 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
38 | {
39 | return $data instanceof FiasFile;
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | public function getSupportedTypes(?string $format): array
46 | {
47 | return [
48 | FiasFileImpl::class => true,
49 | ];
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Serializer/FiasFilterEmptyStringsDenormalizer.php:
--------------------------------------------------------------------------------
1 | denormalizer = $denormalizer;
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | #[\Override]
30 | public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
31 | {
32 | if (FiasSerializerFormat::XML->isEqual($format) && \is_array($data)) {
33 | $filteredData = [];
34 | foreach ($data as $key => $value) {
35 | if ($value !== '') {
36 | $filteredData[$key] = $value;
37 | }
38 | }
39 | } else {
40 | $filteredData = $data;
41 | }
42 |
43 | if ($this->denormalizer !== null) {
44 | return $this->denormalizer->denormalize($filteredData, $type, $format, $context);
45 | } else {
46 | return $filteredData;
47 | }
48 | }
49 |
50 | /**
51 | * {@inheritdoc}
52 | */
53 | #[\Override]
54 | public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
55 | {
56 | if (FiasSerializerFormat::XML->isEqual($format) && \is_array($data)) {
57 | foreach ($data as $value) {
58 | if ($value === '') {
59 | return true;
60 | }
61 | }
62 | }
63 |
64 | return false;
65 | }
66 |
67 | /**
68 | * {@inheritdoc}
69 | */
70 | public function getSupportedTypes(?string $format): array
71 | {
72 | if (FiasSerializerFormat::XML->isEqual($format)) {
73 | return [
74 | '*' => false,
75 | ];
76 | }
77 |
78 | return [];
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Serializer/FiasNameConverter.php:
--------------------------------------------------------------------------------
1 | isEqual($format)) {
23 | return $propertyName;
24 | }
25 |
26 | $propertyName = trim($propertyName);
27 | if (strpos($propertyName, '@') !== 0) {
28 | return '@' . $propertyName;
29 | }
30 |
31 | return $propertyName;
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | #[\Override]
38 | public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string
39 | {
40 | if (!FiasSerializerFormat::XML->isEqual($format)) {
41 | return $propertyName;
42 | }
43 |
44 | $propertyName = trim($propertyName);
45 | if (strpos($propertyName, '@') === 0) {
46 | return substr($propertyName, 1);
47 | }
48 |
49 | return $propertyName;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Serializer/FiasPipelineStateDenormalizer.php:
--------------------------------------------------------------------------------
1 | denormalizer = $denormalizer;
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | #[\Override]
33 | public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
34 | {
35 | /** @var array */
36 | $parameters = \is_array($data) && isset($data['parameters']) && \is_array($data['parameters'])
37 | ? $data['parameters']
38 | : [];
39 |
40 | /** @var bool */
41 | $isCompleted = \is_array($data) && isset($data['isCompleted'])
42 | ? (bool) $data['isCompleted']
43 | : false;
44 |
45 | $preparedParameters = [];
46 | foreach (StateParameter::cases() as $case) {
47 | $parameterValue = $parameters[$case->value] ?? null;
48 | $preparedValue = $this->prepareStateParameterValue($parameterValue, $format, $context);
49 | if ($preparedValue !== null) {
50 | $preparedParameters[$case->value] = $preparedValue;
51 | }
52 | }
53 |
54 | return new ArrayState($preparedParameters, $isCompleted);
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | #[\Override]
61 | public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
62 | {
63 | return State::class === $type || is_a($type, State::class, true);
64 | }
65 |
66 | /**
67 | * {@inheritdoc}
68 | */
69 | public function getSupportedTypes(?string $format): array
70 | {
71 | return [
72 | State::class => true,
73 | ];
74 | }
75 |
76 | /**
77 | * Приводит значение параметра к состоянию пригодному для использования в объекте.
78 | */
79 | private function prepareStateParameterValue(mixed $value, ?string $format, array $context): mixed
80 | {
81 | if (\is_array($value) && $this->denormalizer !== null && isset($value['class'], $value['data'])) {
82 | return $this->denormalizer->denormalize(
83 | $value['data'],
84 | (string) $value['class'],
85 | $format,
86 | $context
87 | );
88 | } elseif (\is_array($value)) {
89 | return array_map(
90 | fn (mixed $item): mixed => $this->prepareStateParameterValue($item, $format, $context),
91 | $value
92 | );
93 | }
94 |
95 | return $value;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Serializer/FiasPipelineStateNormalizer.php:
--------------------------------------------------------------------------------
1 | normalizer = $normalizer;
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | #[\Override]
33 | public function normalize(mixed $data, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
34 | {
35 | if (!($data instanceof State)) {
36 | throw new InvalidArgumentException("Instance of '" . State::class . "' is expected");
37 | }
38 |
39 | $parameters = [];
40 | foreach (StateParameter::cases() as $case) {
41 | $stateValue = $data->getParameter($case, null);
42 | $value = $this->prepareStateParameterValue($stateValue, $format, $context);
43 | if ($value !== null) {
44 | $parameters[$case->value] = $value;
45 | }
46 | }
47 |
48 | return [
49 | 'parameters' => $parameters,
50 | 'isCompleted' => $data->isCompleted(),
51 | ];
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | #[\Override]
58 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
59 | {
60 | return $data instanceof State;
61 | }
62 |
63 | /**
64 | * {@inheritdoc}
65 | */
66 | public function getSupportedTypes(?string $format): array
67 | {
68 | return [
69 | State::class => true,
70 | ];
71 | }
72 |
73 | /**
74 | * Приводит значение параметра к состоянию пригодному для отправки в json.
75 | */
76 | private function prepareStateParameterValue(mixed $value, ?string $format, array $context): mixed
77 | {
78 | if (\is_array($value)) {
79 | return array_map(
80 | fn (mixed $item): mixed => $this->prepareStateParameterValue($item, $format, $context),
81 | $value
82 | );
83 | } elseif (\is_object($value) && $this->normalizer !== null) {
84 | return [
85 | 'class' => \get_class($value),
86 | 'data' => $this->normalizer->normalize($value, $format, $context),
87 | ];
88 | }
89 |
90 | return $value;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Serializer/FiasSerializer.php:
--------------------------------------------------------------------------------
1 | |null $normalizers
28 | * @param array|null $encoders
29 | */
30 | public function __construct(?array $normalizers = null, ?array $encoders = null)
31 | {
32 | if ($normalizers === null) {
33 | $normalizers = [
34 | new DateTimeNormalizer(),
35 | new FiasPipelineStateNormalizer(),
36 | new FiasPipelineStateDenormalizer(),
37 | new FiasUnpackerFileNormalizer(),
38 | new FiasUnpackerFileDenormalizer(),
39 | new FiasFileNormalizer(),
40 | new FiasFileDenormalizer(),
41 | new ObjectNormalizer(
42 | nameConverter: new FiasNameConverter(),
43 | propertyTypeExtractor: new ReflectionExtractor(),
44 | defaultContext: [
45 | ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true,
46 | ]
47 | ),
48 | ];
49 | }
50 |
51 | array_unshift($normalizers, new FiasFilterEmptyStringsDenormalizer());
52 |
53 | if ($encoders === null) {
54 | $encoders = [
55 | new XmlEncoder(
56 | [
57 | XmlEncoder::TYPE_CAST_ATTRIBUTES => false,
58 | ]
59 | ),
60 | new JsonEncoder(),
61 | ];
62 | }
63 |
64 | $this->nestedSerializer = new Serializer($normalizers, $encoders);
65 | }
66 |
67 | /**
68 | * {@inheritdoc}
69 | */
70 | #[\Override]
71 | public function serialize(mixed $data, string $format, array $context = []): string
72 | {
73 | return $this->nestedSerializer->serialize($data, $format, $context);
74 | }
75 |
76 | /**
77 | * {@inheritdoc}
78 | *
79 | * @psalm-suppress MixedReturnStatement
80 | */
81 | #[\Override]
82 | public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed
83 | {
84 | return $this->nestedSerializer->deserialize($data, $type, $format, $context);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Serializer/FiasSerializerContextParam.php:
--------------------------------------------------------------------------------
1 | value;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Serializer/FiasUnpackerFileDenormalizer.php:
--------------------------------------------------------------------------------
1 | true,
64 | ];
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Serializer/FiasUnpackerFileNormalizer.php:
--------------------------------------------------------------------------------
1 | $data->getArchiveFile()->getPathname(),
29 | 'name' => $data->getName(),
30 | 'index' => $data->getIndex(),
31 | 'size' => $data->getSize(),
32 | ];
33 | }
34 |
35 | /**
36 | * {@inheritdoc}
37 | */
38 | #[\Override]
39 | public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
40 | {
41 | return $data instanceof UnpackerFile;
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | public function getSupportedTypes(?string $format): array
48 | {
49 | return [
50 | UnpackerFileImpl::class => true,
51 | ];
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Storage/CompositeStorage.php:
--------------------------------------------------------------------------------
1 | internalStorages as $storage) {
26 | $storage->start();
27 | }
28 | }
29 |
30 | /**
31 | * {@inheritDoc}
32 | */
33 | #[\Override]
34 | public function stop(): void
35 | {
36 | foreach ($this->internalStorages as $storage) {
37 | $storage->stop();
38 | }
39 | }
40 |
41 | /**
42 | * {@inheritDoc}
43 | */
44 | #[\Override]
45 | public function supports(object $entity): bool
46 | {
47 | $isSupport = false;
48 | foreach ($this->internalStorages as $storage) {
49 | if ($storage->supports($entity)) {
50 | $isSupport = true;
51 | break;
52 | }
53 | }
54 |
55 | return $isSupport;
56 | }
57 |
58 | /**
59 | * {@inheritDoc}
60 | */
61 | #[\Override]
62 | public function supportsClass(string $class): bool
63 | {
64 | $isSupport = false;
65 | foreach ($this->internalStorages as $storage) {
66 | if ($storage->supportsClass($class)) {
67 | $isSupport = true;
68 | break;
69 | }
70 | }
71 |
72 | return $isSupport;
73 | }
74 |
75 | /**
76 | * {@inheritDoc}
77 | */
78 | #[\Override]
79 | public function insert(object $entity): void
80 | {
81 | foreach ($this->internalStorages as $storage) {
82 | if (!$storage->supports($entity)) {
83 | continue;
84 | }
85 | $storage->insert($entity);
86 | }
87 | }
88 |
89 | /**
90 | * {@inheritDoc}
91 | */
92 | #[\Override]
93 | public function delete(object $entity): void
94 | {
95 | foreach ($this->internalStorages as $storage) {
96 | if (!$storage->supports($entity)) {
97 | continue;
98 | }
99 | $storage->delete($entity);
100 | }
101 | }
102 |
103 | /**
104 | * {@inheritDoc}
105 | */
106 | #[\Override]
107 | public function upsert(object $entity): void
108 | {
109 | foreach ($this->internalStorages as $storage) {
110 | if (!$storage->supports($entity)) {
111 | continue;
112 | }
113 | $storage->upsert($entity);
114 | }
115 | }
116 |
117 | /**
118 | * {@inheritDoc}
119 | */
120 | #[\Override]
121 | public function truncate(string $entityClassName): void
122 | {
123 | foreach ($this->internalStorages as $storage) {
124 | if (!$storage->supportsClass($entityClassName)) {
125 | continue;
126 | }
127 | $storage->truncate($entityClassName);
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/Storage/Storage.php:
--------------------------------------------------------------------------------
1 |
35 | *
36 | * @throws UnpackerException
37 | */
38 | public function getListOfFiles(\SplFileInfo $archive): iterable;
39 |
40 | /**
41 | * Возвращает правду, если файл является валидным архивом.
42 | */
43 | public function isArchive(\SplFileInfo $archive): bool;
44 | }
45 |
--------------------------------------------------------------------------------
/src/Unpacker/UnpackerFile.php:
--------------------------------------------------------------------------------
1 | archiveFile;
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | #[\Override]
35 | public function getIndex(): int
36 | {
37 | return $this->index;
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | #[\Override]
44 | public function getSize(): int
45 | {
46 | return $this->size;
47 | }
48 |
49 | /**
50 | * {@inheritdoc}
51 | */
52 | #[\Override]
53 | public function getName(): string
54 | {
55 | return $this->name;
56 | }
57 |
58 | /**
59 | * {@inheritdoc}
60 | */
61 | public function __toString(): string
62 | {
63 | return $this->name;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Unpacker/UnpackerZip.php:
--------------------------------------------------------------------------------
1 | extractTo($destination->getPathName());
23 |
24 | if ($res !== true) {
25 | throw UnpackerException::create(
26 | "Can't unpack archive '%s' to '%s'",
27 | $archive->getPathname(),
28 | $destination->getPathname()
29 | );
30 | }
31 |
32 | return new \SplFileInfo($destination->getRealPath());
33 | };
34 |
35 | return $this->runInZipContext($archive, $callback);
36 | }
37 |
38 | /**
39 | * {@inheritDoc}
40 | */
41 | #[\Override]
42 | public function unpackFile(\SplFileInfo $archive, string $fileName, \SplFileInfo $destination): \SplFileInfo
43 | {
44 | $callback = function (\ZipArchive $archiveHandler) use ($archive, $fileName, $destination): \SplFileInfo {
45 | $res = $archiveHandler->extractTo($destination->getPathname(), $fileName);
46 |
47 | if ($res !== true) {
48 | throw UnpackerException::create(
49 | "Can't extract entity '%s' form archive '%s'",
50 | $fileName,
51 | $archive->getPathname()
52 | );
53 | }
54 |
55 | return new \SplFileInfo($destination->getPathname() . '/' . $fileName);
56 | };
57 |
58 | return $this->runInZipContext($archive, $callback);
59 | }
60 |
61 | /**
62 | * {@inheritDoc}
63 | */
64 | #[\Override]
65 | public function getListOfFiles(\SplFileInfo $archive): iterable
66 | {
67 | $archiveHandler = $this->openArchive($archive);
68 |
69 | for ($i = 0; $i < $archiveHandler->numFiles; ++$i) {
70 | $stats = $archiveHandler->statIndex($i);
71 | if (\is_array($stats) && ArrayHelper::extractIntFromArrayByName('crc', $stats) !== 0) {
72 | yield UnpackerFileFactory::createFromZipStats($archive, $stats);
73 | }
74 | }
75 |
76 | $archiveHandler->close();
77 | }
78 |
79 | /**
80 | * {@inheritdoc}
81 | */
82 | #[\Override]
83 | public function isArchive(\SplFileInfo $archive): bool
84 | {
85 | try {
86 | return $this->runInZipContext($archive, fn (): bool => true);
87 | } catch (\Throwable $e) {
88 | return false;
89 | }
90 | }
91 |
92 | /**
93 | * Запускает коллбэк в контексте открытого архива.
94 | *
95 | * @template T
96 | *
97 | * @psalm-param callable(\ZipArchive): T $callback
98 | *
99 | * @psalm-return T
100 | */
101 | private function runInZipContext(\SplFileInfo $path, callable $callback): mixed
102 | {
103 | $archive = $this->openArchive($path);
104 |
105 | try {
106 | $res = $callback($archive);
107 | } catch (\Throwable $e) {
108 | throw UnpackerException::wrap($e);
109 | } finally {
110 | $archive->close();
111 | }
112 |
113 | return $res;
114 | }
115 |
116 | /**
117 | * Создает обработчик архива и открывает его для чтения.
118 | */
119 | private function openArchive(\SplFileInfo $path): \ZipArchive
120 | {
121 | $archive = new \ZipArchive();
122 |
123 | $res = $archive->open($path->getPathName(), \ZipArchive::RDONLY);
124 | if ($res !== true) {
125 | throw UnpackerException::create(
126 | "Can't open '%s' archive, error: %s",
127 | $path->getPathName(),
128 | $res
129 | );
130 | }
131 |
132 | return $archive;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/VersionManager/VersionManager.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | interface XmlReader extends \Iterator
16 | {
17 | /**
18 | * Открывает файл на чтение, пытается найти указанный путь, если
19 | * путь найден, то открывает файл и возвращает правду, если не найден, то
20 | * возвращает ложь.
21 | *
22 | * @throws XmlException
23 | */
24 | public function open(\SplFileInfo $file, string $xpath): bool;
25 |
26 | /**
27 | * Закрывает открытый файл, если такой был.
28 | */
29 | public function close(): void;
30 | }
31 |
--------------------------------------------------------------------------------