├── .gitignore
├── LICENSE.md
├── README.md
├── cli
├── getversion.php
├── init-db.php
├── init.php
└── update.php
├── composer.json
├── composer.lock
├── config.ini.dist
├── database
├── 01_tables.sql
├── 02_system_data.sql
├── 03_indexes.sql
├── 04_constraints.sql
├── 05_clean_up.sql
└── 06_fakes.sql
├── docs
├── Описание службы получения обновлений.doc
└── Сведения о составе информации ФИАС.doc
├── huyak-package.xml
├── import.php.dist
├── phpunit.xml.dist
├── src
├── AddressObjectsImporter.php
├── AddressObjectsUpdater.php
├── AddressStorage.php
├── ApiAction
│ ├── AddressCompletion.php
│ ├── AddressPostalCode.php
│ ├── ApiActionInterface.php
│ ├── PlaceCompletion.php
│ ├── PostalCodeLocation.php
│ └── Validation.php
├── BadRequestException.php
├── Container.php
├── Controller
│ └── ApiController.php
├── DataSource
│ ├── DataSource.php
│ ├── IntervalGenerator.php
│ └── XmlReader.php
├── DatabaseDumpManager.php
├── DbHelper.php
├── FileSystem
│ ├── Dearchiver.php
│ ├── Directory.php
│ ├── FileException.php
│ └── FileHelper.php
├── HousesImporter.php
├── HousesUpdater.php
├── Importer.php
├── ImporterException.php
├── Loader
│ ├── Base.php
│ ├── InitLoader.php
│ ├── SoapResultWrapper.php
│ └── UpdateLoader.php
├── PlaceStorage.php
├── RawDataHelper.php
├── Remover.php
└── UpdateLogHelper.php
├── tests
├── AddressObjectsUpdaterTest.php
├── AddressStorageTest.php
├── ApiActionAddressCompletionTest.php
├── ApiActionAddressPostalCodeTest.php
├── ApiActionPlaceCompletionTest.php
├── ApiActionPostalCodeLocationTest.php
├── ApiActionValidationTest.php
├── ApiControllerTest.php
├── DbHelperTest.php
├── DearchiverTest.php
├── DirectoryTest.php
├── HousesUpdaterTest.php
├── ImporterTest.php
├── IntervalGeneratorTest.php
├── RawDataHelperTest.php
├── RemoverTest.php
├── TestAbstract.php
├── UpdateLoaderTest.php
├── UpdateLogHelperTest.php
├── XmlTest.php
└── resources
│ ├── correctScript.sql
│ ├── directoryTest
│ ├── AS_ADDROBJ_20131221_5316e71a-a8d8-49df-b17c-66d3a981906a.XML
│ ├── AS_DEL_ADDROBJ_20131221_8a0076a7-1f52-4423-8fc6-58dec367832b.XML
│ ├── AS_DEL_HOUSE_20131221_ea93b12d-129d-46a0-9cfb-429b64a28873.XML
│ └── AS_HOUSE_20131221_bccfd0d0-af7a-49db-8401-df23dc3d2efa.XML
│ ├── inCorrectScript.sql
│ ├── load.csv
│ ├── load.jmx
│ └── readerTest.xml
└── web
└── index.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /logs
3 | /vendor
4 | /config.ini
5 | /tests/file_directory/*
6 | /cache
7 | /phpunit.xml
8 | /import.php
9 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Alex Polev, Mikhail Natarov
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 | FIAS
2 | ====
3 |
4 | Автодополнение физических адресов по базе ФИАС.
5 |
6 | ## Инициалиация базы данных
7 |
8 | Для инициализации необходимо запустить `init.php`. Поддерживаются 3 режима работы:
9 |
10 | 1. `php cli/init.php` — скачает с сайта ФИАСа последнюю версию базы, распакует и импортирует;
11 | 2. `php cli/init.php /path/to/archive.rar` — распакует и импортирует архив;
12 | 3. `php cli/init.php /path/to/fias_directory` — импортирует уже распакованный архив.
13 |
14 | ## API
15 |
16 | ### `/api/complete` — дополнение адреса
17 |
18 | Пример запроса:
19 |
20 | http://fias.loc/api/complete?pattern=Невск&limit=20
21 |
22 | Ответ:
23 | {
24 | "items": [
25 | {"title": "г Москва, пр Невский", "is_complete": false, "tags": ["address"]},
26 | {"title": "г Москва, Невское урочище", "is_complete": false, "tags": ["address"]},
27 | {"title": "Невский вокзал", "is_complete": true, "tags": ["place", "railway"]}
28 | ]
29 | }
30 |
31 | GET-параметры:
32 |
33 | * `pattern` — дополняемый адрес;
34 | * `limit` — максимальное количество вариантов дополнения в ответе (не более 50, см. `config/config.ini`);
35 | * `regions` — массив номеров регионов для ограничения поиска адресов (см. `database/02_system_data.sql`);
36 | * `max_address_level` — максимальная детализация адреса.
37 |
38 | Максимальная детализация влияет на состав дополняемых вариантов (см. ниже).
39 |
40 | Поля ответа:
41 |
42 | * `items` — массив вариантов дополнения адреса;
43 | * `title` — текст варианта дополнения;
44 | * `is_complete` — `true` для адресов, которым не нужно дальнейшее дополнение (набран точный адрес, либо достигнута максимальная детализация адреса);
45 | * `tags` — присущие варианту ответа свойства (см. раздел теги).
46 |
47 | Параметр `is_complete` помогает отличить точные адреса от промежуточных вариантов дополнения.
48 | Например, если на Невском проспекте есть дом 11, то
49 | при дополнении строки "Санкт-Петербург" для варианта "Санкт-Петербург, Невский проспект" `is_complete=false`,
50 | а дополнении строки "Санкт-Петербург, Невский проспект" для варианта "Санкт-Петербург, Невский проспект 11" `is_complete=true`.
51 | Параметр `is_complete` не учитывает параметры детализации.
52 |
53 | Примеры запросов с ограничением детализации:
54 |
55 | http://fias.loc/api/complete?pattern=Москва, Невский пр.&limit=20
56 |
57 | В ответе будут все варианты вплоть до номеров домов:
58 | {
59 | "items": [
60 | {"title": "г Москва, пр Невский, 10", "is_complete": true, "tags": ["address"]},
61 | {"title": "г Москва, пр Невский, 11", "is_complete": true, "tags": ["address"]}
62 | ]
63 | }
64 |
65 |
66 | http://fias.loc/api/complete?pattern=Мос&limit=20&max_address_level=region
67 |
68 | В ответе будут только регионы без дальнейшей детализации:
69 | {
70 | "items": [
71 | {"title": "г Москва", "is_complete": true, "tags": ["address"]},
72 | {"title": "обл Московская", "is_complete": true, "tags": ["address"]}
73 | ]
74 | }
75 |
76 |
77 | ### `/api/validate` — валидация элемента
78 |
79 | Пример запроса:
80 |
81 | http://fias.loc/api/validate?pattern=Москва, Невский пр.
82 |
83 | Ответ:
84 | {
85 | "items": [
86 | {
87 | "is_complete": false,
88 | "tags": ["address"]
89 | },
90 | {
91 | "is_complete": true,
92 | "tags": ["place", "railway"]
93 | }
94 | ]
95 | }
96 |
97 | GET-параметры:
98 |
99 | * `pattern` — проверяемый адрес.
100 |
101 | Поля ответа:
102 |
103 | * `items` — массив вариантов корректных объектов;
104 | * `is_complete` — `true` для точного адреса (вместе с домом, корпусом и т.п.);
105 | * `tags` — присущие варианту ответа свойства (см. раздел теги):
106 |
107 |
108 | ### `/api/postal_code_location` — получение адреса по почтовому индексу
109 |
110 | Пример запроса:
111 |
112 | http://fias.loc/api/postal_code_location?postal_code=198504
113 |
114 | Ответ:
115 | {
116 | "address_parts": [
117 | {"title": "г Санкт-Петербург", "address_level": "region"},
118 | {"title": "р-н Петродворцовый", "address_level": "city_district"}
119 | ]
120 | }
121 |
122 | GET-параметры:
123 |
124 | * `postal_code` — почтовый индекс.
125 |
126 | Поля ответа:
127 |
128 | * `address_parts` — массив частей адреса по уровням детализации;
129 | * `title` — название;
130 | * `address_level` — уровень детализации (район, город и т.п.).
131 |
132 | Если соединить по порядку `title` всех частей адреса в строку,
133 | получится общий префикс для всех адресов по указанному почтовому индексу.
134 |
135 | ### `/api/address_postal_code` — получение почтового индекса по адресу
136 |
137 | Пример запроса:
138 |
139 | http://fias.loc/api/address_postal_code?address=обл Псковская, р-н Новосокольнический, д Мошино
140 |
141 | Ответ:
142 | {
143 | "postal_code": 182200
144 | }
145 |
146 | GET-параметры:
147 |
148 | * `address` — адрес.
149 |
150 | Поля ответа:
151 |
152 | * `postal_code` — почтовый индекс или `null`, если индекс не найден.
153 |
154 |
155 | ### Уровни детализации частей адреса
156 |
157 | 1. `region` — регион: Санкт-Петербург, Московская область, Хабаровский край;
158 | 2. `area` — округ: пока данные отсутствуют, заложено для дальнейшей совместимости с ФИАС, когда ФИАС перенесет часть элементов из region;
159 | 3. `area_district` — район округа/региона: Волжский район, Ломоносовский район, Гатчинский район;
160 | 4. `city` — город: Петергоф, Сосновый бор, Пушкин;
161 | 5. `city_district` — район города: микрорайон № 13, Кировский район, Центральный район;
162 | 6. `settlement` — населенный пункт: поселок Парголово, станция Разлив, поселок Металлострой;
163 | 7. `street` — улица: проспект Косыгина, улица Ярославская, проспект Художников;
164 | 8. `territory` — дополнительная территория: Рябинушка снт (садовое некоммерческое товарищество), Победа гск (гаражно-строительный кооператив);
165 | 9. `sub_territory` — часть дополнительной территории: Садовая улица, 7-я линия;
166 | 10. `building` — конкретный дом (максимальная детализация).
167 |
168 | ### Теги
169 |
170 | * `"address"` — текст найден в ФИАС;
171 | * `"place"` — текст найден в списке places (аэропорты, вокзалы, порты и т.д.);
172 | * `"airport"` — аэропорт;
173 | * `"railway_station"` — вокзал;
174 | * `"bus_terminal"` — автовокзал;
175 | * `"port"` — порт;
176 | * `"airport_terminal"` — терминал аэропорта;
177 | * `"riverside_station"` — речной вокзал.
178 |
179 |
180 | ### Выбор формата
181 |
182 | Для указания формата необходимо добавить его к названию ресурса:
183 |
184 | * `.json` для JSON (по умолчанию)
185 | * `.jsonp` для JSONP. Для JSONP требуется дополнительный GET параметр callback.
186 |
187 | Пример запроса:
188 |
189 | http://fias.loc/api/complete.jsonp?pattern=Невск&limit=20&callback=someFunction
190 |
191 | Ответ:
192 | someFunction(
193 | {
194 | "items": [
195 | {"title": "г Москва, пр Невский", "is_complete": false, "tags": ["address"]},
196 | {"title": "г Москва, Невское урочище", "is_complete": false, "tags": ["address"]},
197 | {"title": "Невский вокзал", "is_complete": true, "tags": ["place", "railway"]}
198 | ]
199 | }
200 | )
201 |
--------------------------------------------------------------------------------
/cli/getversion.php:
--------------------------------------------------------------------------------
1 | getUpdateLoader();
7 |
8 | echo "Последняя версия: ", $loader->getLastFileInfo()->getVersionId(), "\n";
9 |
--------------------------------------------------------------------------------
/cli/init-db.php:
--------------------------------------------------------------------------------
1 | getDb();
7 | $dbPath = $container->getDatabaseSourcesDirectory();
8 |
9 | $db->execute(file_get_contents($dbPath . '/01_tables.sql'));
10 | $db->execute(file_get_contents($dbPath . '/02_system_data.sql'));
11 | $db->execute(file_get_contents($dbPath . '/03_indexes.sql'));
12 | $db->execute(file_get_contents($dbPath . '/04_constraints.sql'));
13 | $db->execute(file_get_contents($dbPath . '/05_clean_up.sql'));
14 | $db->execute(file_get_contents($dbPath . '/06_fakes.sql'));
15 |
--------------------------------------------------------------------------------
/cli/init.php:
--------------------------------------------------------------------------------
1 | getDb();
12 | $dataBaseName = $container->getDatabaseName();
13 | $logger = $container->getErrorLogger();
14 | $dbPath = $container->getDatabaseSourcesDirectory();
15 |
16 | FailureHandler::setup(function ($error) use ($logger) {
17 | $logger->error($error['message'], $error);
18 | fwrite(STDERR, "В процессе инициализации произошла ошибка:\n{$error['message']}\n");
19 | exit(1);
20 | });
21 |
22 | set_time_limit(0);
23 |
24 | if ($_SERVER['argc'] == 2) {
25 | $path = $_SERVER['argv']['1'];
26 | if (!is_dir($path)) {
27 | $path = Dearchiver::extract($container->getFileDirectory(), $path);
28 | }
29 |
30 | $directory = new Directory($path);
31 | } else {
32 | $loader = $container->getInitLoader();
33 | $directory = $loader->load();
34 | }
35 | // Получаем VersionId поскольку если его не окажется, то сообщение об этом мы получим только в самом конце 15-ти минутного процесса, что не очень приятно.
36 | $versionId = $directory->getVersionId();
37 |
38 | DbHelper::runFile($dataBaseName, $dbPath . '/01_tables.sql');
39 | DbHelper::runFile($dataBaseName, $dbPath . '/02_system_data.sql');
40 |
41 | $addressObjectsConfig = $container->getAddressObjectsImportConfig();
42 | $addressObjects = new AddressObjectsImporter($db, $addressObjectsConfig['table_name'], $addressObjectsConfig['fields']);
43 | $reader = new XmlReader(
44 | $directory->getAddressObjectFile(),
45 | $addressObjectsConfig['node_name'],
46 | array_keys($addressObjectsConfig['fields']),
47 | $addressObjectsConfig['filters']
48 | );
49 |
50 | $addressObjects->import($reader);
51 |
52 | $housesConfig = $container->getHousesImportConfig();
53 |
54 | if ($housesConfig) {
55 | $houses = new HousesImporter($db, $housesConfig['table_name'], $housesConfig['fields']);
56 |
57 | $reader = new XmlReader(
58 | $directory->getHouseFile(),
59 | $housesConfig['node_name'],
60 | array_keys($housesConfig['fields'])
61 | );
62 |
63 | $houses->import($reader);
64 | }
65 |
66 | DbHelper::runFile($dataBaseName, $dbPath . '/03_indexes.sql');
67 |
68 | $addressObjects->modifyDataAfterImport();
69 |
70 | if ($housesConfig) {
71 | $houses->modifyDataAfterImport();
72 | }
73 |
74 | DbHelper::runFile($dataBaseName, $dbPath . '/04_constraints.sql');
75 | DbHelper::runFile($dataBaseName, $dbPath . '/05_clean_up.sql');
76 |
77 | UpdateLogHelper::addVersionIdToLog($db, $versionId);
78 |
--------------------------------------------------------------------------------
/cli/update.php:
--------------------------------------------------------------------------------
1 | getDb();
12 | $logger = $container->getErrorLogger();
13 |
14 | FailureHandler::setup(function ($error) use ($logger) {
15 | $logger->error($error['message'], $error);
16 | fwrite(STDERR, "В процессе инициализации произошла ошибка:\n{$error['message']}\n");
17 | exit(1);
18 | });
19 |
20 | $db->start();
21 |
22 | if ($_SERVER['argc'] == 2) {
23 | $path = $_SERVER['argv']['1'];
24 | if (!is_dir($path)) {
25 | $path = Dearchiver::extract($container->getFileDirectory(), $path);
26 | }
27 |
28 | $directory = new Directory($path);
29 | } else {
30 | $loader = $container->getUpdateLoader();
31 | $directory = $loader->load();
32 | }
33 |
34 | $oldVersionId = UpdateLogHelper::getLastVersionId($db);
35 | $newVersionId = $directory->getVersionId();
36 |
37 | if ($newVersionId != ($oldVersionId + 1)) {
38 | throw new \LogicException("Попытка обновления с версии {$oldVersionId} на версию {$newVersionId}.");
39 | }
40 |
41 | $db->execute('SET CONSTRAINTS "address_objects_parent_id_fkey", "houses_parent_id_fkey" DEFERRED');
42 |
43 | $housesConfig = $container->getHousesImportConfig();
44 | $addressObjectsConfig = $container->getAddressObjectsImportConfig();
45 |
46 | $deletedHouseFile = $directory->getDeletedHouseFile();
47 | if ($deletedHouseFile && $housesConfig) {
48 | $houseRemover = new Remover(
49 | $db,
50 | $housesConfig['table_name'],
51 | $housesConfig['xml_key'],
52 | $housesConfig['database_key']
53 | );
54 | $houseRemover->remove(
55 | new XmlReader(
56 | $deletedHouseFile,
57 | $housesConfig['node_name'],
58 | [$housesConfig['primary_key']],
59 | []
60 | )
61 | );
62 | }
63 |
64 | $deletedAddressObjectsFile = $directory->getDeletedAddressObjectFile();
65 | if ($deletedAddressObjectsFile) {
66 | $addressObjectsRemover = new Remover(
67 | $db,
68 | $addressObjectsConfig['table_name'],
69 | $addressObjectsConfig['xml_key'],
70 | $addressObjectsConfig['database_key']
71 | );
72 | $addressObjectsRemover->remove(
73 | new XmlReader(
74 | $deletedAddressObjectsFile,
75 | $addressObjectsConfig['node_name'],
76 | [$addressObjectsConfig['xml_key']],
77 | []
78 | )
79 | );
80 | }
81 |
82 | $addressObjectFields = $addressObjectsConfig['fields'];
83 | $addressObjectFields['PREVID'] = ['name' => 'previous_id', 'type' => 'uuid'];
84 | $addressObjectsUpdater = new AddressObjectsUpdater($db, $addressObjectsConfig['table_name'], $addressObjectFields);
85 | $addressObjectsUpdater->update(
86 | new XmlReader(
87 | $directory->getAddressObjectFile(),
88 | $addressObjectsConfig['node_name'],
89 | array_keys($addressObjectFields),
90 | $addressObjectsConfig['filters']
91 | )
92 | );
93 |
94 | if ($housesConfig) {
95 | $houseFields = $housesConfig['fields'];
96 | $houseFields['PREVID'] = ['name' => 'previous_id', 'type' => 'uuid'];
97 | $housesUpdater = new HousesUpdater($db, $housesConfig['table_name'], $houseFields);
98 | $housesUpdater->update(new XmlReader(
99 | $directory->getHouseFile(),
100 | $housesConfig['node_name'],
101 | array_keys($houseFields),
102 | []
103 | ));
104 | }
105 |
106 | UpdateLogHelper::addVersionIdToLog($db, $directory->getVersionId());
107 |
108 | $db->commit();
109 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stiffbeards/fias",
3 | "minimum-stability": "dev",
4 | "require": {
5 | "php": ">=5.5.0",
6 | "miknatr/grace-dbal": "dev-master",
7 | "miknatr/bravicility": "1.0.0",
8 | "stiffbeards/kola": "dev-master",
9 | "stiffbeards/browser": "dev-master"
10 | },
11 | "require-dev": {
12 | "phpunit/phpunit": "5.2.*"
13 | },
14 | "autoload": {
15 | "psr-0": {
16 | "": ["src", "tests"]
17 | }
18 | },
19 | "repositories": [
20 | {
21 | "type": "vcs",
22 | "url": "https://github.com/miknatr/bravicility.git"
23 | },
24 | {
25 | "type": "vcs",
26 | "url": "https://bitbucket.org/miknatr/tos-kola.git"
27 | },
28 | {
29 | "type": "vcs",
30 | "url": "https://github.com/miknatr/browser.git"
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/config.ini.dist:
--------------------------------------------------------------------------------
1 |
2 | app.host = fias.loc
3 | app.file_directory = /var/www/upload
4 | app.wsdl_url = http://fias.nalog.ru/WebServices/Public/DownloadService.asmx?WSDL
5 | app.max_completion_limit = 50
6 |
7 | db.uri = pgsql://postgres:1@localhost:5432/fias
8 |
9 | router.skip_cache_invalidation = off
10 |
11 | logging.error_log = logs/error.log
12 |
--------------------------------------------------------------------------------
/database/01_tables.sql:
--------------------------------------------------------------------------------
1 | DROP TABLE IF EXISTS houses;
2 | CREATE TABLE houses (
3 | id UUID PRIMARY KEY NOT NULL,
4 | house_id UUID NOT NULL,
5 | address_id UUID DEFAULT NULL,
6 | number VARCHAR,
7 | full_number VARCHAR,
8 | building VARCHAR,
9 | structure VARCHAR,
10 | postal_code INTEGER
11 | );
12 | COMMENT ON TABLE houses IS 'данные по домам';
13 | COMMENT ON COLUMN houses.id IS 'идентификационный код записи';
14 | COMMENT ON COLUMN houses.house_id IS 'идентификационный код дома';
15 | COMMENT ON COLUMN houses.address_id IS 'идентификационный код адресного объекта';
16 | COMMENT ON COLUMN houses.number IS 'номер дома';
17 | COMMENT ON COLUMN houses.building IS 'корпус';
18 | COMMENT ON COLUMN houses.structure IS 'строение';
19 | COMMENT ON COLUMN houses.postal_code IS 'индекс';
20 |
21 | DROP TABLE IF EXISTS address_objects;
22 | CREATE TABLE address_objects (
23 | id UUID PRIMARY KEY NOT NULL,
24 | address_id UUID NOT NULL,
25 | parent_id UUID DEFAULT NULL,
26 | level INTEGER,
27 | address_level INTEGER,
28 | house_count INTEGER,
29 | next_address_level INTEGER,
30 | title VARCHAR,
31 | full_title VARCHAR,
32 | postal_code INTEGER,
33 | region VARCHAR,
34 | prefix VARCHAR
35 | );
36 | COMMENT ON TABLE address_objects IS 'данные по адресным объектам(округам, улицам, городам)';
37 | COMMENT ON COLUMN address_objects.id IS 'идентификационный код записи';
38 | COMMENT ON COLUMN address_objects.address_id IS 'идентификационный код адресного объекта';
39 | COMMENT ON COLUMN address_objects.parent_id IS 'идентификационный код родительского адресного объекта';
40 | COMMENT ON COLUMN address_objects.level IS 'уровень объекта по parent_id (0 для региона и далее по возрастающей';
41 | COMMENT ON COLUMN address_objects.address_level IS 'уровень объекта по ФИАС';
42 | COMMENT ON COLUMN address_objects.parent_id IS 'идентификационный код родительского адресного объекта';
43 | COMMENT ON COLUMN address_objects.title IS 'наименование объекта';
44 | COMMENT ON COLUMN address_objects.full_title IS 'полное наименование объекта';
45 | COMMENT ON COLUMN address_objects.postal_code IS 'индекс';
46 | COMMENT ON COLUMN address_objects.region IS 'регион';
47 | COMMENT ON COLUMN address_objects.prefix IS 'ул., пр. и так далее';
48 | COMMENT ON COLUMN address_objects.house_count IS 'количество домов';
49 | COMMENT ON COLUMN address_objects.next_address_level IS 'уровень следующего дочернего объекта по ФИАС';
50 |
51 | DROP TABLE IF EXISTS address_object_levels;
52 | CREATE TABLE address_object_levels (
53 | id INTEGER PRIMARY KEY,
54 | title VARCHAR,
55 | code VARCHAR
56 | );
57 | COMMENT ON TABLE address_object_levels IS 'перечень уровня адресных объектов по ФИАС';
58 | COMMENT ON COLUMN address_object_levels.id IS 'идентификационный код записи';
59 | COMMENT ON COLUMN address_object_levels.title IS 'описание уровня';
60 | COMMENT ON COLUMN address_object_levels.title IS 'код уровня';
61 |
62 | DROP TABLE IF EXISTS update_log;
63 | CREATE TABLE update_log (
64 | id SERIAL PRIMARY KEY,
65 | version_id INTEGER NOT NULL,
66 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP(0)
67 | );
68 | COMMENT ON TABLE update_log IS 'лог обновлений';
69 | COMMENT ON COLUMN update_log.version_id IS 'id версии, полученной от базы ФИАС';
70 | COMMENT ON COLUMN update_log.created_at IS 'дата установки обновления/инициализации';
71 |
72 | DROP TABLE IF EXISTS places;
73 | CREATE TABLE places (
74 | id SERIAL PRIMARY KEY,
75 | title VARCHAR,
76 | full_title VARCHAR,
77 | parent_id INTEGER,
78 | type_id INTEGER NOT NULL,
79 | have_children BOOLEAN DEFAULT FALSE
80 | );
81 | COMMENT ON TABLE places IS 'справочник мест';
82 | COMMENT ON COLUMN places.title IS 'название места';
83 | COMMENT ON COLUMN places.full_title IS 'название места с типом';
84 | COMMENT ON COLUMN places.parent_id IS 'идентификатор родительского места';
85 | COMMENT ON COLUMN places.type_id IS 'идентификатор типа места';
86 | COMMENT ON COLUMN places.have_children IS 'есть ли дочерние сущности';
87 |
88 | DROP TABLE IF EXISTS place_types;
89 | CREATE TABLE place_types(
90 | id SERIAL PRIMARY KEY,
91 | parent_id INTEGER,
92 | title VARCHAR NOT NULL UNIQUE,
93 | system_name VARCHAR UNIQUE
94 | );
95 | COMMENT ON TABLE place_types IS 'справочник типов мест';
96 | COMMENT ON COLUMN place_types.parent_id IS 'идентификатор типа родителя';
97 | COMMENT ON COLUMN place_types.title IS 'название типа для пользователя';
98 | COMMENT ON COLUMN place_types.system_name IS 'системное имя типа, для использования в программном коде';
99 |
100 | DROP TABLE IF EXISTS regions;
101 | CREATE TABLE regions (
102 | number VARCHAR PRIMARY KEY,
103 | title VARCHAR
104 | );
105 | COMMENT ON TABLE regions IS 'список регионов';
106 | COMMENT ON COLUMN regions.number IS 'номер региона';
107 | COMMENT ON COLUMN regions.number IS 'название региона';
108 |
--------------------------------------------------------------------------------
/database/02_system_data.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO place_types(id, title)
2 | VALUES
3 | (1, 'транспортный объект')
4 | ;
5 |
6 | INSERT INTO place_types(id, title, parent_id, system_name)
7 | VALUES
8 | (2, 'аэропорт', 1, 'airport'),
9 | (3, 'вокзал', 1, 'railway_station'),
10 | (4, 'автовокзал', 1, 'bus_terminal'),
11 | (5, 'порт', 1, 'port'),
12 | (6, 'терминал', 2, 'airport_terminal'),
13 | (7, 'речной вокзал', 1, 'riverside_station')
14 | ;
15 |
16 | SELECT setval('place_types_id_seq', (SELECT MAX(id) FROM place_types LIMIT 1));
17 |
18 | INSERT INTO places(id, parent_id, title, type_id)
19 | VALUES
20 | (1, NULL, 'Внуково', 2),
21 | (2, 1, 'A', 6),
22 | (3, 1, 'B', 6),
23 | (4, 1, 'D', 6),
24 | (5, NULL, 'Домодедово', 2),
25 | (6, NULL, 'Шереметьево', 2),
26 | (7, 6, 'B', 6),
27 | (8, 6, 'C', 6),
28 | (9, 6, 'D', 6),
29 | (10, 6, 'E', 6),
30 | (11, 6, 'F', 6),
31 | (12, NULL, 'Остафьево', 2),
32 | (13, NULL, 'Раменское', 2),
33 | (14, NULL, 'Чкаловский', 2),
34 | (15, NULL, 'Пулково', 2),
35 | (16, 15, '1', 6),
36 | (17, 15, '2', 6),
37 | (18, 15, 'новый', 6)
38 | ;
39 |
40 | SELECT setval('places_id_seq', (SELECT MAX(id) FROM places LIMIT 1));
41 |
42 | INSERT INTO places(title, type_id)
43 | VALUES
44 | ('Балтийский', 3),
45 | ('Витебский', 3),
46 | ('Ладожский', 3),
47 | ('Московский', 3),
48 | ('Финляндский', 3),
49 | ('Белорусский', 3),
50 | ('Казанский', 3),
51 | ('Киевский', 3),
52 | ('Курский', 3),
53 | ('Ленинградский', 3),
54 | ('Павелецкий', 3),
55 | ('Рижский', 3),
56 | ('Савеловский', 3),
57 | ('Ярославский', 3),
58 | ('Щелковский', 4),
59 | ('Павелецкий', 4),
60 | ('Морской', 5),
61 | ('Пролетарский', 7),
62 | ('Уткина заводь', 7)
63 | ;
64 |
65 | UPDATE places p
66 | SET full_title = p.title || ' ' || pt.title
67 | FROM place_types pt
68 | WHERE pt.id = p.type_id;
69 |
70 | -- Формируем полный заголовок
71 | UPDATE places p
72 | SET full_title = tmp.title
73 | FROM (
74 | WITH RECURSIVE required_places(id, title) AS (
75 | SELECT DISTINCT pw.id, pw.title || ' ' || pwt.title
76 | FROM places pw
77 | INNER JOIN place_types pwt
78 | ON pwt.id = pw.type_id
79 | WHERE pw.parent_id IS NULL
80 | UNION ALL
81 | SELECT pwr.id, pw.title || ', ' || pwr.title || ' ' || pwt.title
82 | FROM places pwr
83 | INNER JOIN required_places pw
84 | ON pw.id = pwr.parent_id
85 | INNER JOIN place_types pwt
86 | ON pwt.id = pwr.type_id
87 | )
88 | SELECT * FROM required_places
89 | ) tmp
90 | WHERE tmp.id = p.id;
91 |
92 | -- Определяем наличие дочерних записей
93 | UPDATE places
94 | SET have_children = TRUE
95 | WHERE id IN (SELECT DISTINCT parent_id FROM places);
96 |
97 | INSERT INTO address_object_levels (id, title, code)
98 | VALUES
99 | (1, 'Регион', 'region'),
100 | (2, 'Округ', 'area'),
101 | (3, 'Район округа', 'area_district'),
102 | (4, 'Город', 'city'),
103 | (5, 'Район города', 'city_district'),
104 | (6, 'Населенный пункт', 'settlement'),
105 | (7, 'Улица', 'street'),
106 | (90, 'Дополнительная территория', 'territory'),
107 | (91, 'Часть дополнительной территории', 'sub_territory'),
108 | (0, 'Здание', 'building')
109 | ;
110 |
111 | INSERT INTO regions (number, title)
112 | VALUES
113 | ('01', 'Адыгея Респ'),
114 | ('02', 'Башкортостан Респ'),
115 | ('03', 'Бурятия Респ'),
116 | ('04', 'Алтай Респ'),
117 | ('05', 'Дагестан Респ'),
118 | ('06', 'Ингушетия Респ'),
119 | ('07', 'Кабардино-Балкарская Респ'),
120 | ('08', 'Калмыкия Респ'),
121 | ('09', 'Карачаево-Черкесская Респ'),
122 | ('10', 'Карелия Респ'),
123 | ('11', 'Коми Респ'),
124 | ('12', 'Марий Эл Респ'),
125 | ('13', 'Мордовия Респ'),
126 | ('14', 'Саха /Якутия/ Респ'),
127 | ('15', 'Северная Осетия - Алания Респ'),
128 | ('16', 'Татарстан Респ'),
129 | ('17', 'Тыва Респ'),
130 | ('18', 'Удмуртская Респ'),
131 | ('19', 'Хакасия Респ'),
132 | ('20', 'Чеченская Респ'),
133 | ('21', 'Чувашская Республика - Чувашия'),
134 | ('22', 'Алтайский край'),
135 | ('23', 'Краснодарский край'),
136 | ('24', 'Красноярский край'),
137 | ('25', 'Приморский край'),
138 | ('26', 'Ставропольский край'),
139 | ('27', 'Хабаровский край'),
140 | ('28', 'Амурская обл'),
141 | ('29', 'Архангельская обл'),
142 | ('30', 'Астраханская обл'),
143 | ('31', 'Белгородская обл'),
144 | ('32', 'Брянская обл'),
145 | ('33', 'Владимирская обл'),
146 | ('34', 'Волгоградская обл'),
147 | ('35', 'Вологодская обл'),
148 | ('36', 'Воронежская обл'),
149 | ('37', 'Ивановская обл'),
150 | ('38', 'Иркутская обл'),
151 | ('39', 'Калининградская обл'),
152 | ('40', 'Калужская обл'),
153 | ('41', 'Камчатский край'),
154 | ('42', 'Кемеровская обл'),
155 | ('43', 'Кировская обл'),
156 | ('44', 'Костромская обл'),
157 | ('45', 'Курганская обл'),
158 | ('46', 'Курская обл'),
159 | ('47', 'Ленинградская обл'),
160 | ('48', 'Липецкая обл'),
161 | ('49', 'Магаданская обл'),
162 | ('50', 'Московская обл'),
163 | ('51', 'Мурманская обл'),
164 | ('52', 'Нижегородская обл'),
165 | ('53', 'Новгородская обл'),
166 | ('54', 'Новосибирская обл'),
167 | ('55', 'Омская обл'),
168 | ('56', 'Оренбургская обл'),
169 | ('57', 'Орловская обл'),
170 | ('58', 'Пензенская обл'),
171 | ('59', 'Пермский край'),
172 | ('60', 'Псковская обл'),
173 | ('61', 'Ростовская обл'),
174 | ('62', 'Рязанская обл'),
175 | ('63', 'Самарская обл'),
176 | ('64', 'Саратовская обл'),
177 | ('65', 'Сахалинская обл'),
178 | ('66', 'Свердловская обл'),
179 | ('67', 'Смоленская обл'),
180 | ('68', 'Тамбовская обл'),
181 | ('69', 'Тверская обл'),
182 | ('70', 'Томская обл'),
183 | ('71', 'Тульская обл'),
184 | ('72', 'Тюменская обл'),
185 | ('73', 'Ульяновская обл'),
186 | ('74', 'Челябинская обл'),
187 | ('75', 'Забайкальский край'),
188 | ('76', 'Ярославская обл'),
189 | ('77', 'Москва г'),
190 | ('78', 'Санкт-Петербург г'),
191 | ('79', 'Еврейская Аобл'),
192 | ('80', 'Забайкальский край Агинский Бурятский округ'),
193 | ('81', 'Коми-Пермяцкий АО'),
194 | ('82', 'Корякский АО'),
195 | ('83', 'Ненецкий АО'),
196 | ('84', 'Таймырский (Долгано-Ненецкий) АО'),
197 | ('85', 'Иркутская обл Усть-Ордынский Бурятский округ'),
198 | ('86', 'Ханты-Мансийский Автономный округ - Югра АО'),
199 | ('87', 'Чукотский АО'),
200 | ('88', 'Эвенкийский АО'),
201 | ('89', 'Ямало-Ненецкий АО'),
202 | ('91', 'Крым Респ'),
203 | ('92', 'Севастополь г'),
204 | ('99', 'Байконур г')
205 | ;
206 |
--------------------------------------------------------------------------------
/database/03_indexes.sql:
--------------------------------------------------------------------------------
1 | CREATE UNIQUE INDEX address_objects_address_id_uq_idx
2 | ON address_objects
3 | USING BTREE (address_id)
4 | ;
5 |
6 | CREATE INDEX address_objects_parent_id_fkey_idx
7 | ON address_objects
8 | USING BTREE (parent_id)
9 | ;
10 |
11 | CREATE INDEX address_objects_level_full_title_lower_idx
12 | ON address_objects
13 | USING BTREE (level, lower(full_title))
14 | ;
15 |
16 | CREATE INDEX address_objects_title_lower_idx
17 | ON address_objects
18 | USING BTREE (lower(title))
19 | ;
20 |
21 | CREATE INDEX houses_address_id_fkey_idx
22 | ON houses
23 | USING BTREE (address_id)
24 | ;
25 |
26 | CREATE INDEX houses_full_number_idx
27 | ON houses
28 | USING BTREE (full_number)
29 | ;
30 |
31 | CREATE INDEX tmp_houses_number_id_fkey_idx
32 | ON houses
33 | USING BTREE (number)
34 | ;
35 |
36 | CREATE INDEX tmp_houses_building_id_fkey_idx
37 | ON houses
38 | USING BTREE (building)
39 | ;
40 |
41 | CREATE INDEX tmp_houses_structure_fkey_idx
42 | ON houses
43 | USING BTREE (structure)
44 | ;
45 |
46 | CREATE UNIQUE INDEX update_log_version_id_idx
47 | ON update_log
48 | USING BTREE (version_id)
49 | ;
50 |
51 | CREATE INDEX place_types_parent_id_fkey_idx
52 | ON place_types
53 | USING BTREE (parent_id)
54 | ;
55 |
56 | CREATE INDEX places_type_id_fkey_idx
57 | ON places
58 | USING BTREE (type_id)
59 | ;
60 |
61 | CREATE UNIQUE INDEX places_title_type_id_parent_id_uq_idx
62 | ON places
63 | USING BTREE (title, type_id, parent_id)
64 | ;
65 |
66 | CREATE INDEX places_title_idx
67 | ON places
68 | USING BTREE (title)
69 | ;
70 |
71 | -- Что бы если заглючит оптимизатор,он планы составил исходя из индексов все равно в момент массовой правки
72 | ANALYZE;
73 |
--------------------------------------------------------------------------------
/database/04_constraints.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE address_objects
2 | ADD CONSTRAINT address_objects_parent_id_fkey
3 | FOREIGN KEY(parent_id) REFERENCES address_objects(address_id)
4 | ON UPDATE CASCADE ON DELETE CASCADE
5 | DEFERRABLE INITIALLY IMMEDIATE
6 | ;
7 |
8 | ALTER TABLE address_objects
9 | ADD CONSTRAINT address_objects_address_level_fkey
10 | FOREIGN KEY(address_level) REFERENCES address_object_levels(id)
11 | ON UPDATE CASCADE ON DELETE CASCADE
12 | DEFERRABLE INITIALLY IMMEDIATE
13 | ;
14 |
15 | ALTER TABLE houses
16 | ADD CONSTRAINT houses_parent_id_fkey
17 | FOREIGN KEY(address_id) REFERENCES address_objects(address_id)
18 | ON UPDATE CASCADE ON DELETE CASCADE
19 | DEFERRABLE INITIALLY IMMEDIATE
20 | ;
21 |
22 | ALTER TABLE place_types
23 | ADD CONSTRAINT place_types_parent_id_fkey
24 | FOREIGN KEY(parent_id) REFERENCES place_types(id)
25 | ON UPDATE CASCADE ON DELETE CASCADE
26 | ;
27 |
28 | ALTER TABLE places
29 | ADD CONSTRAINT places_type_id_fkey
30 | FOREIGN KEY (type_id) REFERENCES place_types(id)
31 | ON UPDATE CASCADE ON DELETE RESTRICT
32 | ;
33 |
--------------------------------------------------------------------------------
/database/05_clean_up.sql:
--------------------------------------------------------------------------------
1 | DROP INDEX tmp_houses_number_id_fkey_idx;
2 | DROP INDEX tmp_houses_building_id_fkey_idx;
3 | DROP INDEX tmp_houses_structure_fkey_idx;
4 |
5 | CLUSTER address_objects USING address_objects_level_full_title_lower_idx;
6 | CLUSTER houses USING houses_full_number_idx;
7 | ANALYZE;
8 |
--------------------------------------------------------------------------------
/database/06_fakes.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO address_objects (id, address_id, parent_id, level, full_title, title, prefix, house_count, postal_code, address_level, region, next_address_level)
2 | VALUES
3 | ('29251dcf-00a1-4e34-98d4-5c47484a36d4', '29251dcf-00a1-4e34-98d4-5c47484a36d4', NULL, '0', 'г Москва', 'Москва', 'г', '0', NULL, 1, 77, 7),
4 | ('77303f7c-452b-4e73-b2b0-cbc59fe636c2', '77303f7c-452b-4e73-b2b0-cbc59fe636c2', '29251dcf-00a1-4e34-98d4-5c47484a36d4', '1', 'г Москва, ул Стахановская', 'Стахановская', 'ул', '4', 123456, 7, 77, NULL),
5 | ('77303f7c-452b-4e73-b2b0-cbc59fe636c4', '77303f7c-452b-4e73-b2b0-cbc59fe636c4', '29251dcf-00a1-4e34-98d4-5c47484a36d4', '1', 'г Москва, пр Ставропольский', 'Ставропольский', 'пр', '0', NULL, 7, 77, NULL),
6 | ('77303f7c-452b-4e73-b2b0-cbc59fe636c5', '77303f7c-452b-4e73-b2b0-cbc59fe636c5', '29251dcf-00a1-4e34-98d4-5c47484a36d4', '1', 'г Москва, пл Сталина', 'Сталина', 'пл', '0', NULL, 7, 77, NULL),
7 | ('77303f7c-452b-4e73-b2b0-cbc59fe636c6', '77303f7c-452b-4e73-b2b0-cbc59fe636c6', '29251dcf-00a1-4e34-98d4-5c47484a36d4', '1', 'г Москва, ал Старших Бобров', 'Старших Бобров', 'ал', '0', 123456, 7, 77, NULL),
8 | ('77303f7c-452b-4e73-b2b0-cbc59fe636c7', '77303f7c-452b-4e73-b2b0-cbc59fe636c7', '29251dcf-00a1-4e34-98d4-5c47484a36d4', '1', 'г Москва, ул Машинная', 'Машинная', 'ул', '0', NULL, 7, 77, NULL),
9 | ('77303f7c-452b-4e73-b2b0-cbc59fe636c8', '77303f7c-452b-4e73-b2b0-cbc59fe636c8', '77303f7c-452b-4e73-b2b0-cbc59fe636d0', '2', 'г Москва, р-н Мытищинский, ул Стахановская', 'Стахановская', 'ул', '0', 1234567, 7, 77, NULL),
10 | ('77303f7c-452b-4e73-b2b0-cbc59fe636c9', '77303f7c-452b-4e73-b2b0-cbc59fe636c9', '77303f7c-452b-4e73-b2b0-cbc59fe636d0', '2', 'г Москва, р-н Мытищинский, ул Раздолбайская', 'Раздолбайская', 'ул', '0', 1234567, 7, 77, NULL),
11 | ('77303f7c-452b-4e73-b2b0-cbc59fe636d0', '77303f7c-452b-4e73-b2b0-cbc59fe636d0', '29251dcf-00a1-4e34-98d4-5c47484a36d4', '1', 'г Москва, р-н Мытищинский', 'Мытищинский', 'р-н', '0', NULL, 7, 77, NULL)
12 | ;
13 |
14 | INSERT INTO houses(full_number, id, house_id, address_id, postal_code)
15 | VALUES
16 | ('16с17', '841254dc-0074-41fe-99ba-0c8501526c04', '841254dc-0074-41fe-99ba-0c8501526c04', '77303f7c-452b-4e73-b2b0-cbc59fe636c2', 654321),
17 | ('16с18', '841254dc-0074-41fe-99ba-0c8501526c05', '841254dc-0074-41fe-99ba-0c8501526c05', '77303f7c-452b-4e73-b2b0-cbc59fe636c2', NULL),
18 | ('23', '841254dc-0074-41fe-99ba-0c8501526c06', '841254dc-0074-41fe-99ba-0c8501526c06', '77303f7c-452b-4e73-b2b0-cbc59fe636c2', NULL),
19 | ('1к1', '841254dc-0074-41fe-99ba-0c8501526c07', '841254dc-0074-41fe-99ba-0c8501526c07', '77303f7c-452b-4e73-b2b0-cbc59fe636c2', NULL)
20 | ;
21 |
--------------------------------------------------------------------------------
/docs/Описание службы получения обновлений.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miknatr/fias/dde1717f78136d1af179c281a86b99237d105c32/docs/Описание службы получения обновлений.doc
--------------------------------------------------------------------------------
/docs/Сведения о составе информации ФИАС.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miknatr/fias/dde1717f78136d1af179c281a86b99237d105c32/docs/Сведения о составе информации ФИАС.doc
--------------------------------------------------------------------------------
/huyak-package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | mkdir -p logs
24 | mkdir -p cache
25 | mkdir -p tests/file_directory
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | composer install
37 |
38 |
39 |
40 | rm -rf cache/*
41 |
42 |
43 |
44 | php cli/init-db.php
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/import.php.dist:
--------------------------------------------------------------------------------
1 | [
5 | 'table_name' => 'address_objects',
6 | 'node_name' => 'Object',
7 | 'xml_key' => 'AOID',
8 | 'database_key' => 'id',
9 | 'fields' => [
10 | 'AOID' => ['name' => 'id', 'type' => 'uuid'],
11 | 'AOGUID' => ['name' => 'address_id', 'type' => 'uuid'],
12 | 'AOLEVEL' => ['name' => 'address_level', 'type' => 'integer'],
13 | 'PARENTGUID' => ['name' => 'parent_id', 'type' => 'uuid'],
14 | 'FORMALNAME' => ['name' => 'title'],
15 | 'POSTALCODE' => ['name' => 'postal_code', 'type' => 'integer'],
16 | 'SHORTNAME' => ['name' => 'prefix'],
17 | 'REGIONCODE' => ['name' => 'region', 'type' => 'integer'],
18 | ],
19 | 'filters' => [
20 | ['field' => 'ACTSTATUS', 'type' => 'eq', 'value' => 1],
21 | ],
22 | ],
23 | // При простановке false вместо массива загрузка домов не будет производиться.
24 | 'houses' => [
25 | 'table_name' => 'houses',
26 | 'node_name' => 'House',
27 | 'xml_key' => 'HOUSEID',
28 | 'database_key' => 'id',
29 | 'fields' => [
30 | 'HOUSEID' => ['name' => 'id', 'type' => 'uuid'],
31 | 'HOUSEGUID' => ['name' => 'house_id', 'type' => 'uuid'],
32 | 'AOGUID' => ['name' => 'address_id', 'type' => 'uuid'],
33 | 'HOUSENUM' => ['name' => 'number'],
34 | 'BUILDNUM' => ['name' => 'building'],
35 | 'STRUCNUM' => ['name' => 'structure'],
36 | ],
37 | ],
38 | ];
39 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | tests
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/AddressObjectsImporter.php:
--------------------------------------------------------------------------------
1 | db);
16 | }
17 |
18 | protected $rowsPerInsert = 10000;
19 | }
20 |
--------------------------------------------------------------------------------
/src/AddressObjectsUpdater.php:
--------------------------------------------------------------------------------
1 | import($reader);
16 | $this->modifyDataAfterImport();
17 | }
18 |
19 | public function modifyDataAfterImport()
20 | {
21 | $this->updateOldRecords();
22 | $this->addNewRecords();
23 |
24 | RawDataHelper::cleanAddressObjects($this->db);
25 | }
26 |
27 | private function updateOldRecords()
28 | {
29 | $this->db->execute(
30 | "UPDATE address_objects ao_old
31 | SET title = ao_new.title,
32 | postal_code = ao_new.postal_code,
33 | prefix = ao_new.prefix,
34 | parent_id = ao_new.parent_id
35 | FROM ?f ao_new
36 | WHERE (ao_old.address_id = ao_new.address_id OR ao_old.id = ao_new.previous_id)
37 | AND (
38 | COALESCE(ao_old.title, '') != COALESCE(ao_new.title, '')
39 | OR COALESCE(ao_old.postal_code, 0) != COALESCE(ao_new.postal_code, 0)
40 | OR COALESCE(ao_old.prefix, '') != COALESCE(ao_new.prefix, '')
41 | OR COALESCE(ao_old.parent_id, '00000000-0000-0000-0000-000000000000') != COALESCE(ao_new.parent_id, '00000000-0000-0000-0000-000000000000')
42 | )",
43 | [$this->table]
44 | );
45 | }
46 |
47 | private function addNewRecords()
48 | {
49 | $this->db->execute(
50 | 'INSERT INTO address_objects(id, address_id, parent_id, title, postal_code, prefix)
51 | SELECT ao_new.id, ao_new.address_id, ao_new.parent_id, ao_new.title, ao_new.postal_code, ao_new.prefix
52 | FROM ?f ao_new
53 | LEFT JOIN address_objects ao_old
54 | ON (ao_old.address_id = ao_new.address_id OR ao_old.id = ao_new.previous_id)
55 | WHERE ao_old.id IS NULL
56 | ',
57 | [$this->table]
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/AddressStorage.php:
--------------------------------------------------------------------------------
1 | db = $db;
13 | }
14 |
15 | public function findAddress($address)
16 | {
17 | $level = count(explode(',', $address)) - 1;
18 | $sql = '
19 | SELECT *
20 | FROM address_objects
21 | WHERE level = ?q
22 | AND lower(full_title) = lower(?q)
23 | LIMIT 1'
24 | ;
25 |
26 | return $this->db->execute($sql, [$level, $address])->fetchOneOrFalse();
27 | }
28 |
29 | public function findHouse($address)
30 | {
31 | $tmp = explode(',', $address);
32 | $house = trim(array_pop($tmp));
33 |
34 | $address = $this->findAddress(implode(',', $tmp));
35 | if ($address) {
36 | $addressId = $address['address_id'];
37 | $sql = '
38 | SELECT *
39 | FROM houses
40 | WHERE address_id = ?q
41 | AND full_number = lower(?q)
42 | LIMIT 1'
43 | ;
44 |
45 | return $this->db->execute($sql, [$addressId, $house])->fetchOneOrFalse();
46 | }
47 |
48 | return false;
49 | }
50 |
51 | public function findAddressById($id)
52 | {
53 | $sql = '
54 | SELECT *
55 | FROM address_objects
56 | WHERE address_id = ?q
57 | ORDER BY level DESC
58 | LIMIT 1'
59 | ;
60 |
61 | return $this->db->execute($sql, [$id])->fetchOneOrFalse();
62 | }
63 |
64 | public function findHousesByPostalCode($postalCode)
65 | {
66 | $sql = '
67 | SELECT *
68 | FROM houses
69 | WHERE postal_code = ?q'
70 | ;
71 |
72 | return $this->db->execute($sql, [$postalCode])->fetchResult();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/ApiAction/AddressCompletion.php:
--------------------------------------------------------------------------------
1 | db = $db;
24 | $this->limit = $limit;
25 | $this->address = $address;
26 | $this->regions = $regions;
27 |
28 | if ($maxAddressLevel) {
29 | $this->maxAddressLevel = $this->getAddressLevelId($maxAddressLevel);
30 | }
31 | }
32 |
33 | public function run()
34 | {
35 | $storage = new AddressStorage($this->db);
36 | $addressParts = static::splitAddress($this->address);
37 |
38 | $address = $storage->findAddress($addressParts['address']);
39 | $this->parentId = $address ? $address['address_id'] : null;
40 | $houseCount = $address ? $address['house_count'] : null;
41 |
42 | if ($houseCount && $this->maxAddressLevel) {
43 | return [];
44 | }
45 |
46 | if ($this->getHouseCount()) {
47 | $rows = $this->findHouses($addressParts['pattern']);
48 | $rows = $this->setIsCompleteFlag($rows);
49 | } else {
50 | $rows = $this->findAddresses($addressParts['pattern']);
51 | $rows = $this->setIsCompleteFlag($rows);
52 | }
53 |
54 | foreach ($rows as $key => $devNull) {
55 | $rows[$key]['tags'] = ['address'];
56 | }
57 |
58 | return $rows;
59 | }
60 |
61 | private function getHouseCount()
62 | {
63 | if (!$this->parentId) {
64 | return null;
65 | }
66 |
67 | $sql = 'SELECT house_count FROM address_objects WHERE address_id = ?q';
68 |
69 | return $this->db->execute($sql, [$this->parentId])->fetchResult();
70 | }
71 |
72 | private function findAddressesWithParentId($pattern)
73 | {
74 | $where = $this->generateGeneralWherePart($pattern);
75 | $sql = "
76 | SELECT full_title title, address_level, next_address_level
77 | FROM address_objects ao
78 | WHERE ?p
79 | AND (parent_id = ?q)
80 | ORDER BY ao.title
81 | LIMIT ?e"
82 | ;
83 |
84 | return $this->db->execute(
85 | $sql,
86 | [$where, $this->parentId, $this->limit]
87 | )->fetchAll();
88 | }
89 |
90 | private function findAddressesWithoutParentId($pattern)
91 | {
92 | $sql = "
93 | (
94 | SELECT full_title title, address_level, next_address_level
95 | FROM address_objects ao
96 | WHERE ?p:where:
97 | AND (parent_id IS NULL)
98 | LIMIT ?e:limit:
99 | )
100 | UNION
101 | (
102 | SELECT ao.full_title title, ao.address_level, ao.next_address_level
103 | FROM address_objects ao
104 | INNER JOIN address_objects AS aop
105 | ON aop.parent_id IS NULL
106 | AND aop.address_id = ao.parent_id
107 | WHERE ?p:where:
108 | LIMIT ?e:limit:
109 | )
110 | ORDER BY title
111 | LIMIT ?e:limit:
112 | "
113 | ;
114 |
115 | $where = $this->generateGeneralWherePart($pattern);
116 | $values = [
117 | 'where' => $where,
118 | 'limit' => $this->limit
119 | ];
120 |
121 | return $this->db->execute($sql, $values)->fetchAll();
122 | }
123 |
124 | private function findAddresses($pattern)
125 | {
126 | if ($this->parentId) {
127 | return $this->findAddressesWithParentId($pattern);
128 | }
129 |
130 | return $this->findAddressesWithoutParentId($pattern);
131 | }
132 |
133 | private function generateGeneralWherePart($pattern)
134 | {
135 | $whereParts = [$this->db->replacePlaceholders("ao.title ilike '?e%'", [$pattern])];
136 |
137 | if ($this->maxAddressLevel) {
138 | $whereParts[] = $this->db->replacePlaceholders('ao.address_level <= ?q', [$this->maxAddressLevel]);
139 | }
140 |
141 | if ($this->regions) {
142 | $whereParts[] = $this->db->replacePlaceholders('ao.region IN (?l)', [$this->regions]);
143 | }
144 |
145 | return '(' . implode(') AND (', $whereParts) . ')';
146 | }
147 |
148 | private function findHouses($pattern)
149 | {
150 | $sql = "
151 | SELECT full_title||', '||full_number title, ?q address_level, NULL next_address_level
152 | FROM houses h
153 | INNER JOIN address_objects ao
154 | ON ao.address_id = h.address_id
155 | WHERE h.address_id = ?q
156 | AND full_number ilike '?e%'
157 | ORDER BY (regexp_matches(full_number, '^[0-9]+', 'g'))[1]
158 | LIMIT ?e"
159 | ;
160 | $values = [static::BUILDING_ADDRESS_LEVEL, $this->parentId, $pattern, $this->limit];
161 |
162 | return $this->db->execute($sql, $values)->fetchAll();
163 | }
164 |
165 | private function setIsCompleteFlag(array $values)
166 | {
167 | foreach ($values as $key => $value) {
168 | $isMaxLevelReached = $value['address_level'] == $this->maxAddressLevel;
169 | $doChildrenSuitNextLevel = ($value['next_address_level'] <= $this->maxAddressLevel)
170 | || (!$this->maxAddressLevel && !empty($value['house_count']))
171 | ;
172 |
173 | $values[$key]['is_complete'] = $isMaxLevelReached || !$doChildrenSuitNextLevel;
174 |
175 | unset($values[$key]['address_level']);
176 | }
177 |
178 | return $values;
179 | }
180 |
181 | private static function splitAddress($address)
182 | {
183 | $tmp = explode(',', $address);
184 |
185 | return [
186 | 'pattern' => static::cleanAddressPart(array_pop($tmp)),
187 | 'address' => implode(',', $tmp),
188 | ];
189 | }
190 |
191 | private static function cleanAddressPart($rawAddress)
192 | {
193 | // избавляемся от популярных префиксов/постфиксов (Вопросы по поводу регулярки к johnnywoo, сам я ее слабо понимаю).
194 | $cleanAddress = preg_replace('
195 | {
196 | (?<= ^ | [^а-яА-ЯЁё] )
197 |
198 | (?:ул|улица|снт|деревня|тер|пер|переулок|ал|аллея|линия|проезд|гск|ш|шоссе|г|город|обл|область|пр|проспект)
199 |
200 | (?= [^а-яА-ЯЁё] | $ )
201 |
202 | [.,-]*
203 | }x',
204 | '',
205 | $rawAddress
206 | );
207 |
208 | return trim($cleanAddress);
209 | }
210 |
211 | public function getAddressLevelId($code)
212 | {
213 | $result = $this->db->execute(
214 | 'SELECT id FROM address_object_levels WHERE code = ?q',
215 | [$code]
216 | )->fetchResult();
217 |
218 | if ($result === null) {
219 | throw new BadRequestException('Некорректное значение уровня адреса: ' . $code);
220 | }
221 |
222 | return $result;
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/src/ApiAction/AddressPostalCode.php:
--------------------------------------------------------------------------------
1 | db = $db;
17 | $this->address = $address;
18 | }
19 |
20 | public function run()
21 | {
22 | $storage = new AddressStorage($this->db);
23 | $address = $storage->findAddress($this->address);
24 | if ($address) {
25 | return $address['postal_code'];
26 | }
27 |
28 | $house = $storage->findHouse($this->address);
29 | if ($house) {
30 | if ($house['postal_code']) {
31 | return $house['postal_code'];
32 | }
33 |
34 | $address = $storage->findAddressById($house['address_id']);
35 | return $address['postal_code'];
36 | }
37 |
38 | // ничего не найдено
39 | return null;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/ApiAction/ApiActionInterface.php:
--------------------------------------------------------------------------------
1 | db = $db;
19 | $this->limit = $limit;
20 |
21 | $this->textForCompletion = $textForCompletion;
22 | }
23 |
24 | public function run()
25 | {
26 | $placeParts = $this->splitPlaceTitle($this->textForCompletion);
27 | $parentId = null;
28 |
29 | if (!empty($placeParts['parent_place'])) {
30 | $storage = new PlaceStorage($this->db);
31 | $parentId = $storage->findPlace($placeParts['parent_place'])['id'];
32 | if (!$parentId) {
33 | return [];
34 | }
35 | }
36 |
37 | $placeWords = $this->splitPatternToWords($placeParts['pattern']);
38 | $type = $this->extractType($placeWords);
39 |
40 | if ($type) {
41 | unset($placeWords[$type['title']]);
42 | }
43 |
44 | $rows = $this->findPlaces($placeWords, $type, $parentId);
45 |
46 | return $rows;
47 | }
48 |
49 | private function splitPlaceTitle($title)
50 | {
51 | $tmp = explode(',', $title);
52 |
53 | return [
54 | 'pattern' => strtolower(trim(array_pop($tmp))),
55 | 'parent_place' => implode(',', $tmp),
56 | ];
57 | }
58 |
59 | private function extractType(array $words)
60 | {
61 | $type = $this->db->execute(
62 | 'SELECT id, title
63 | FROM place_types
64 | WHERE title IN (?l)
65 | ',
66 | [$words]
67 | )->fetchOneOrFalse();
68 |
69 | return $type ?: [];
70 | }
71 |
72 | private function findPlaces(array $placeWords, array $type, $parentId)
73 | {
74 | $pattern = implode(' ', $placeWords);
75 | $sql = "
76 | SELECT full_title title, (CASE WHEN have_children THEN 0 ELSE 1 END) is_complete, pt.system_name type_system_name
77 | FROM places p
78 | INNER JOIN place_types pt
79 | ON p.type_id = pt.id
80 | WHERE ?p
81 | ORDER BY p.title
82 | LIMIT ?e"
83 | ;
84 |
85 | $whereParts = [$this->db->replacePlaceholders("p.title ilike '?e%'", [$pattern])];
86 |
87 | if ($type) {
88 | $whereParts[] = $this->db->replacePlaceholders('type_id = ?q', [$type['id']]);
89 | }
90 |
91 | if ($parentId) {
92 | $whereParts[] = $this->db->replacePlaceholders('p.parent_id = ?q', [$parentId]);
93 | }
94 |
95 | $values = ['(' . implode($whereParts, ') AND (') . ')', $this->limit];
96 |
97 | $items = $this->db->execute($sql, $values)->fetchAll();
98 | if ($items) {
99 | foreach ($items as $key => $item) {
100 | $items[$key]['is_complete'] = $item['is_complete'] ? true : false;
101 | $items[$key]['tags'] = ['place', $item['type_system_name']];
102 | }
103 | }
104 |
105 | return $items;
106 | }
107 |
108 | private function splitPatternToWords($pattern)
109 | {
110 | $tmp = explode(' ', $pattern);
111 | $result = [];
112 |
113 | foreach ($tmp as $word) {
114 | $trimmedWord = trim($word);
115 | $result[$trimmedWord] = $trimmedWord;
116 | }
117 |
118 | return $result;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/ApiAction/PostalCodeLocation.php:
--------------------------------------------------------------------------------
1 | db = $db;
16 | $this->postalCode = $postalCode;
17 | }
18 |
19 | public function run()
20 | {
21 | $count = $this->getAddressCount();
22 | if (!$count) {
23 | return [];
24 | }
25 |
26 | $stack = $this->getBiggestSuitableStack();
27 |
28 | return $this->filterAddressStack($stack, $count);
29 | }
30 |
31 | private function getAddressCount($pattern = null)
32 | {
33 | $values = [$this->postalCode];
34 | $sql = 'SELECT COUNT(*) FROM address_objects WHERE postal_code = ?q';
35 |
36 | if ($pattern) {
37 | $values[] = $pattern;
38 | $sql .= " AND full_title LIKE '?e%'";
39 | }
40 |
41 | return $this->db->execute($sql, $values)->fetchResult();
42 | }
43 |
44 | private function getBiggestSuitableStack()
45 | {
46 | // Берем наименьший адрес и через родительские связки строим стек.
47 | return $this->db->execute(
48 | "WITH RECURSIVE
49 | address_stack(parent_id, title, full_title, level, address_level) AS (
50 | SELECT parent_id, prefix || ' ' || title, full_title, level, address_level
51 | FROM address_objects ao
52 | WHERE address_id IN (
53 | SELECT address_id
54 | FROM address_objects
55 | WHERE postal_code = ?q
56 | ORDER BY level
57 | LIMIT 1
58 | )
59 | UNION ALL
60 | SELECT r.parent_id, r.prefix || ' ' || r.title, r.full_title, r.level, r.address_level
61 | FROM address_objects r
62 | INNER JOIN address_stack ads
63 | ON ads.parent_id = r.address_id
64 |
65 | )
66 | SELECT parent_id, a.title, full_title, level, al.code address_level
67 | FROM address_stack a
68 | INNER JOIN address_object_levels al
69 | ON al.id = address_level
70 | ORDER BY level
71 | ",
72 | [$this->postalCode]
73 | )->fetchAll();
74 | }
75 |
76 | private function filterAddressStack(array $partsForChecking, $totalCount, array $currentStack = [])
77 | {
78 | $part = array_shift($partsForChecking);
79 | $levelCount = $this->getAddressCount($part['full_title']);
80 |
81 | if ($levelCount == $totalCount) {
82 | $currentStack[] = ['title' => $part['title'], 'address_level' => $part['address_level']];
83 | }
84 |
85 | if (!$partsForChecking || ($levelCount != $totalCount)) {
86 | return $currentStack;
87 | }
88 |
89 | return $this->filterAddressStack($partsForChecking, $totalCount, $currentStack);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/ApiAction/Validation.php:
--------------------------------------------------------------------------------
1 | address = $address;
18 | $this->db = $db;
19 | }
20 |
21 | public function run()
22 | {
23 | $result = [];
24 |
25 | $addressData = $this->lookUpInFias();
26 | if ($addressData) {
27 | $addressData['tags'] = ['address'];
28 | $result[] = $addressData;
29 | }
30 |
31 | $placeData = $this->lookUpInPlaces();
32 | if ($placeData) {
33 | $result[] = [
34 | 'is_valid' => $placeData['is_valid'],
35 | 'is_complete' => $placeData['is_complete'],
36 | 'tags' => ['place', $placeData['type_system_name']]
37 | ];
38 | }
39 |
40 | return $result;
41 | }
42 |
43 | private function lookUpInFias()
44 | {
45 | $storage = new AddressStorage($this->db);
46 |
47 | $completeAddress = $storage->findHouse($this->address);
48 | if ($completeAddress) {
49 | return ['is_complete' => true, 'is_valid' => true];
50 | }
51 |
52 | $incompleteAddress = $storage->findAddress($this->address);
53 | if ($incompleteAddress) {
54 | return ['is_complete' => false, 'is_valid' => true];
55 | }
56 |
57 | // ничего не нашлось
58 | return null;
59 | }
60 |
61 | private function lookUpInPlaces()
62 | {
63 | $storage = new PlaceStorage($this->db);
64 | $place = $storage->findPlace($this->address);
65 |
66 | if ($place) {
67 | return [
68 | 'type_system_name' => $place['type_system_name'],
69 | 'is_valid' => true,
70 | 'is_complete' => true
71 | ];
72 | }
73 |
74 | return null;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/BadRequestException.php:
--------------------------------------------------------------------------------
1 | config = parse_ini_file(__DIR__ . '/../config.ini', true);
21 | $this->ensureParameters(
22 | $this->config,
23 | ['app.host', 'app.file_directory', 'app.wsdl_url', 'app.max_completion_limit', 'db.uri']
24 | );
25 |
26 | $this->importConfig = require(__DIR__ . '/../import.php');
27 | $this->loadDbConfig($this->config);
28 | $this->loadRouterConfig($this->config, $this->getRootDirectory());
29 | $this->loadLoggingConfig($this->config, $this->getRootDirectory());
30 | }
31 |
32 | protected function ensureParameters(array $config, array $parameterNames)
33 | {
34 | $undefinedMessages = [];
35 | foreach ($parameterNames as $name) {
36 | if (!isset($config[$name])) {
37 | $undefinedMessages[] = "Config parameter {$name} is not defined";
38 | }
39 | }
40 |
41 | if (count($undefinedMessages) > 0) {
42 | throw new \LogicException(implode("\n", $undefinedMessages));
43 | }
44 | }
45 |
46 | public function getDbUri()
47 | {
48 | return $this->config['db.uri'];
49 | }
50 |
51 | public function getRootDirectory()
52 | {
53 | return __DIR__ . '/..';
54 | }
55 |
56 | public function getHost()
57 | {
58 | return $this->config['app.host'];
59 | }
60 |
61 | public function getWsdlUrl()
62 | {
63 | return $this->config['app.wsdl_url'];
64 | }
65 |
66 | public function getFileDirectory()
67 | {
68 | return $this->config['app.file_directory'];
69 | }
70 |
71 | public function getMaxCompletionLimit()
72 | {
73 | return $this->config['app.max_completion_limit'];
74 | }
75 |
76 | public function getDatabaseName()
77 | {
78 | $parts = explode('/', $this->config['db.uri']);
79 |
80 | return array_pop($parts);
81 | }
82 |
83 | public function getDatabaseSourcesDirectory()
84 | {
85 | return __DIR__ . '/../database';
86 | }
87 |
88 | public function getHousesImportConfig()
89 | {
90 | return $this->importConfig['houses'];
91 | }
92 |
93 | public function getAddressObjectsImportConfig()
94 | {
95 | return $this->importConfig['address_objects'];
96 | }
97 |
98 | public function getUpdateLoader()
99 | {
100 | return new UpdateLoader($this->getWsdlUrl(), $this->getFileDirectory());
101 | }
102 |
103 | public function getInitLoader(){
104 | return new InitLoader($this->getWsdlUrl(), $this->getFileDirectory());
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Controller/ApiController.php:
--------------------------------------------------------------------------------
1 | container = $container;
25 | }
26 |
27 | /**
28 | * @route GET /api/complete
29 | * @route GET /api/complete.json
30 | * @route GET /api/complete.jsonp format=jsonp
31 | * @param Request $request
32 | * @return JsonpResponse|JsonResponse|Response
33 | * @throws RouteNotFoundException
34 | */
35 | public function complete(Request $request)
36 | {
37 | $maxLimit = $this->container->getMaxCompletionLimit();
38 |
39 | $maxAddressLevel = $request->get('max_address_level');
40 | $regions = $request->get('regions', []);
41 | $pattern = $request->get('pattern', '');
42 | $limit = $request->get('limit', $maxLimit);
43 |
44 | if ($limit > $maxLimit) {
45 | return $this->makeErrorResponse($request, 'Превышен допустимый лимит на количество записей. Лимит должен быть не более: ' . $maxLimit);
46 | }
47 |
48 | $places = (new PlaceCompletion($this->container->getDb(), $pattern, $limit))->run();
49 |
50 | $addresses = (new AddressCompletion(
51 | $this->container->getDb(),
52 | $pattern,
53 | $limit,
54 | $maxAddressLevel,
55 | $regions
56 | ))->run();
57 |
58 | $result = ['items' => array_merge($places, $addresses)];
59 |
60 | return $this->makeResponse($request, $result);
61 | }
62 |
63 | /**
64 | * @route GET /api/validate
65 | * @route GET /api/validate.json
66 | * @route GET /api/validate.jsonp format=jsonp
67 | * @param Request $request
68 | * @return JsonpResponse|JsonResponse|Response
69 | * @throws RouteNotFoundException
70 | */
71 | public function validate(Request $request)
72 | {
73 | $pattern = $request->get('pattern');
74 | if (!$pattern) {
75 | return $this->makeErrorResponse($request, 'Отсутствует обязательный параметр: pattern.');
76 | }
77 |
78 | $result = ['items' => (new Validation($this->container->getDb(), $pattern))->run()];
79 |
80 | return $this->makeResponse($request, $result);
81 | }
82 |
83 | /**
84 | * @route GET /api/address_postal_code
85 | * @route GET /api/address_postal_code.json
86 | * @route GET /api/address_postal_code.jsonp format=jsonp
87 | * @param Request $request
88 | * @return JsonpResponse|JsonResponse|Response
89 | * @throws RouteNotFoundException
90 | */
91 | public function postalCode(Request $request)
92 | {
93 | $address = $request->get('address', '');
94 | if (!$address) {
95 | return $this->makeErrorResponse($request, 'Отсутствует обязательный параметр: address.');
96 | }
97 |
98 | $result = (new AddressPostalCode($this->container->getDb(), $address))->run();
99 |
100 | return $this->makeResponse($request, ['postal_code' => $result]);
101 | }
102 |
103 | /**
104 | * @route GET /api/postal_code_location
105 | * @route GET /api/postal_code_location.json
106 | * @route GET /api/postal_code_location.jsonp format=jsonp
107 | * @param Request $request
108 | * @return JsonpResponse|JsonResponse|Response
109 | * @throws RouteNotFoundException
110 | */
111 | public function postalCodeLocation(Request $request)
112 | {
113 | $postalCode = $request->get('postal_code', '');
114 | if (!$postalCode) {
115 | return $this->makeErrorResponse($request, 'Отсутствует обязательный параметр: postal_code.');
116 | }
117 |
118 | $result = ['address_parts' => (new PostalCodeLocation($this->container->getDb(), $postalCode))->run()];
119 |
120 | return $this->makeResponse($request, $result);
121 | }
122 |
123 | private function makeErrorResponse(Request $request, $message)
124 | {
125 | return $this->makeResponse($request, ['error_message' => $message], 400);
126 | }
127 |
128 | private function makeResponse(Request $request, array $values, $status = 200)
129 | {
130 | $format = $request->option('format', 'json');
131 | switch ($format) {
132 | case 'json':
133 | $response = new JsonResponse($status, $values);
134 | break;
135 | case 'jsonp':
136 | $callback = $request->get('callback');
137 |
138 | if (!$callback) {
139 | return new Response(400, 'Отсутствует обязательный параметр: callback.');
140 | }
141 | $response = new JsonpResponse($values, $callback);
142 | break;
143 | default:
144 | throw new RouteNotFoundException;
145 | }
146 |
147 | return $response;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/DataSource/DataSource.php:
--------------------------------------------------------------------------------
1 | dataSource = $dataSource;
25 |
26 | $this->startAttribute = $startAttribute;
27 | $this->endAttribute = $endAttribute;
28 | $this->typeAttribute = $typeAttribute;
29 | $this->resultAttribute = $resultAttribute;
30 | }
31 |
32 | private $currentPosition;
33 | private $currentRow;
34 | private $startPosition;
35 | private $endPosition;
36 |
37 | public function getRows($maxCount = 1000)
38 | {
39 | $this->ensureMaxCountIsValid($maxCount);
40 |
41 | $count = 0;
42 | $result = [];
43 |
44 | // Первый раз $this->currentRow гарантировано равна null. Поэтому do - while.
45 | do {
46 | if (($this->currentPosition <= $this->endPosition) && $this->currentRow) {
47 | $resultPart = $this->getRowsFromInterval($maxCount - $count);
48 | $count += count($resultPart);
49 | $result = array_merge($result, $resultPart);
50 | } else {
51 | $this->setCurrentRow();
52 | }
53 | } while (($count < $maxCount) && $this->currentRow);
54 |
55 | return $result;
56 | }
57 |
58 | private function ensureMaxCountIsValid($maxCount)
59 | {
60 | if ($maxCount < 1) {
61 | throw new \LogicException('Неверное значение максимального количества строк: ' . $maxCount);
62 | }
63 | }
64 |
65 | private function getRowsFromInterval($maxCount)
66 | {
67 | $result = [];
68 | $count = 0;
69 |
70 | while (($count < $maxCount) && ($this->currentPosition <= $this->endPosition)) {
71 | switch ($this->currentRow[$this->typeAttribute]) {
72 | case static::BASE_TYPE:
73 | $value = $this->currentPosition++;
74 | break;
75 | case static::EVEN_TYPE:
76 | $value = ($this->currentPosition % 2) == 0 ? $this->currentPosition : $this->currentPosition + 1;
77 | $this->currentPosition = ($this->currentPosition % 2) == 0 ? $this->currentPosition + 2 : $this->currentPosition + 3;
78 | break;
79 | case static::ODD_TYPE:
80 | $value = ($this->currentPosition % 2) == 1 ? $this->currentPosition : $this->currentPosition + 1;
81 | $this->currentPosition = ($this->currentPosition % 2) == 1 ? $this->currentPosition + 2 : $this->currentPosition + 3;
82 | break;
83 | default:
84 | throw new \Exception('Задан неверный тип работы.');
85 | }
86 |
87 | $this->currentRow[$this->resultAttribute] = $value;
88 |
89 | $result[] = $this->currentRow;
90 | ++$count;
91 | }
92 |
93 | return $result;
94 | }
95 |
96 | private function setCurrentRow()
97 | {
98 | $tmpResult = $this->dataSource->getRows(1);
99 | if (!$tmpResult) {
100 | $this->currentRow = null;
101 | return;
102 | }
103 |
104 | $this->currentRow = array_shift($tmpResult);
105 | $this->startPosition = $this->currentRow[$this->startAttribute];
106 | $this->currentPosition = $this->currentRow[$this->startAttribute];
107 | $this->endPosition = $this->currentRow[$this->endAttribute];
108 |
109 | if ($this->startPosition > $this->endPosition) {
110 | throw new \Exception('Начало интервала не может быть больше его окончания');
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/DataSource/XmlReader.php:
--------------------------------------------------------------------------------
1 | nodeName = $nodeName;
19 | $this->attributes = $attributes;
20 | $this->filters = $filters;
21 |
22 | $this->initializeReader($pathToFile);
23 | }
24 |
25 | private function initializeReader($pathToFile)
26 | {
27 | FileHelper::ensureIsReadable($pathToFile);
28 |
29 | $this->reader = new \XMLReader();
30 |
31 | $success = $this->reader->open($pathToFile);
32 | if (!$success) {
33 | throw new ImporterException('Ошибка открытия XML файла по адресу: ' . $pathToFile);
34 | }
35 | }
36 |
37 | public function getRows($maxCount = 1000)
38 | {
39 | $this->ensureMaxCountIsValid($maxCount);
40 |
41 | $result = [];
42 | $count = 0;
43 |
44 | while (($count < $maxCount) && $this->reader->read()) {
45 | if ($this->checkIsNodeAccepted($this->reader->name)) {
46 | $result[] = $this->getRowAttributes();
47 | ++$count;
48 | }
49 | }
50 |
51 | return $result;
52 | }
53 |
54 | private function ensureMaxCountIsValid($maxCount)
55 | {
56 | if ($maxCount < 1) {
57 | throw new \LogicException('Неверное значение максимального количества строк: ' . $maxCount);
58 | }
59 | }
60 |
61 | private function checkIsNodeAccepted($node)
62 | {
63 | if ($node != $this->nodeName) {
64 | return false;
65 | }
66 |
67 | foreach ($this->filters as $filter) {
68 | $value = $this->reader->getAttribute($filter['field']);
69 |
70 | switch ($filter['type']) {
71 | case 'in':
72 | if ($filter['value'] && !in_array($value, $filter['value'])) {
73 | return false;
74 | }
75 | break;
76 | case 'nin':
77 | if ($filter['value'] && in_array($value, $filter['value'])) {
78 | return false;
79 | }
80 | break;
81 | case 'hash':
82 | if ($filter['value'] && empty($filter['value'][$value])) {
83 | return false;
84 | }
85 | break;
86 | case 'eq':
87 | // no break
88 | default :
89 | if ($filter['value'] != $value) {
90 | return false;
91 | }
92 | }
93 | }
94 |
95 | return true;
96 | }
97 |
98 | private function getRowAttributes()
99 | {
100 | $result = [];
101 | foreach ($this->attributes as $attribute) {
102 | // Если атрибут отсутствует, в $result[$attribute] окажется null, также передаем null вместо пустого значения
103 | $result[$attribute] = $this->reader->getAttribute($attribute) ?: null;
104 | }
105 |
106 | return $result;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/DatabaseDumpManager.php:
--------------------------------------------------------------------------------
1 | execute($sql, $params);
34 | }
35 |
36 | public static function runFile($db, $path)
37 | {
38 | FileHelper::ensureIsReadable($path);
39 | $path = escapeshellarg($path);
40 | $db = escapeshellarg($db);
41 |
42 | exec('psql -f ' . $path . ' ' . $db . ' 2>&1', $output, $result);
43 |
44 | if ($result !== 0) {
45 | throw new \Exception('Ошибка выполнения SQL файла: ' . implode("\n", $output));
46 | }
47 |
48 | if ($output) {
49 | foreach ($output as $line) {
50 | if (preg_match('/psql:(.*)ERROR:/', $line)) {
51 | throw new \Exception('Ошибка выполнения SQL файла: ' . implode("\n", $output));
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/FileSystem/Dearchiver.php:
--------------------------------------------------------------------------------
1 | &1', $output, $result);
42 |
43 | if ($result !== 0) {
44 | throw new \Exception('Ошибка разархивации: ' . implode("\n", $output));
45 | }
46 |
47 | return $directoryForExtract;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/FileSystem/Directory.php:
--------------------------------------------------------------------------------
1 | directoryPath = $path;
15 | }
16 |
17 | public function getVersionId()
18 | {
19 | $prefix = 'VERSION_ID_';
20 | return str_replace($prefix, '', $this->find($prefix));
21 | }
22 |
23 | public function getDeletedAddressObjectFile()
24 | {
25 | $fileName = $this->find('AS_DEL_ADDROBJ', false);
26 | return $fileName ? $this->directoryPath . '/' . $fileName : null;
27 | }
28 |
29 | public function getDeletedHouseFile()
30 | {
31 | $fileName = $this->find('AS_DEL_HOUSE_', false);
32 | return $fileName ? $this->directoryPath . '/' . $fileName : null;
33 | }
34 |
35 | public function getAddressObjectFile()
36 | {
37 | return $this->directoryPath . '/' . $this->find('AS_ADDROBJ');
38 | }
39 |
40 | public function getHouseFile()
41 | {
42 | return $this->directoryPath . '/' . $this->find('AS_HOUSE_');
43 | }
44 |
45 | public function getPath()
46 | {
47 | return $this->directoryPath;
48 | }
49 |
50 | private function find($prefix, $isIndispensable = true)
51 | {
52 | $files = scandir($this->directoryPath);
53 | foreach ($files as $file) {
54 | if (in_array($file, ['.', '..'])) {
55 | continue;
56 | }
57 |
58 | if (mb_strpos($file, $prefix) === 0) {
59 | return $file;
60 | }
61 | }
62 |
63 | if ($isIndispensable) {
64 | throw new FileException('Файл с префиксом ' . $prefix . ' не найден в директории: ' . $this->directoryPath);
65 | }
66 |
67 | return null;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/FileSystem/FileException.php:
--------------------------------------------------------------------------------
1 | db, $this->table);
16 | RawDataHelper::updateHousesCount($this->db);
17 | RawDataHelper::updateNextAddressLevelFlag($this->db);
18 | }
19 |
20 | protected $rowsPerInsert = 10000;
21 | }
22 |
--------------------------------------------------------------------------------
/src/HousesUpdater.php:
--------------------------------------------------------------------------------
1 | 'full_number'];
11 | parent::__construct($db, $table, $fields, true);
12 | }
13 |
14 | public function update(DataSource $reader)
15 | {
16 | $this->import($reader);
17 | $this->modifyDataAfterImport();
18 | }
19 |
20 | public function modifyDataAfterImport()
21 | {
22 | $this->createTemporaryIndexes();
23 |
24 | RawDataHelper::cleanHouses($this->db, $this->table);
25 |
26 | $this->removeOldRecords();
27 | $this->addNewRecords();
28 |
29 | RawDataHelper::updateHousesCount($this->db);
30 | RawDataHelper::updateNextAddressLevelFlag($this->db);
31 | }
32 |
33 | private function removeOldRecords()
34 | {
35 | $this->db->execute(
36 | 'DELETE FROM ?f:temp_table: h
37 | USING (
38 | SELECT DISTINCT h.address_id
39 | FROM ?f:temp_table: h
40 | LEFT JOIN address_objects ao
41 | ON ao.address_id = h.address_id
42 | WHERE ao.id IS NULL
43 | ) a
44 | WHERE a.address_id = h.address_id
45 | ',
46 | ['temp_table' => $this->table]
47 | );
48 |
49 | $this->db->execute(
50 | 'DELETE FROM houses h_old
51 | USING ?f h_new
52 | WHERE (h_old.house_id = h_new.house_id OR h_old.id = h_new.previous_id)
53 | ',
54 | [$this->table]
55 | );
56 | }
57 |
58 | private function addNewRecords()
59 | {
60 | $this->db->execute(
61 | 'INSERT INTO houses(id, house_id, address_id, number, building, structure, full_number)
62 | SELECT h_new.id, h_new.house_id, h_new.address_id, h_new.number, h_new.building, h_new.structure, h_new.full_number
63 | FROM ?f h_new
64 | ',
65 | [$this->table]
66 | );
67 | }
68 |
69 | private function createTemporaryIndexes()
70 | {
71 | $sql = 'CREATE INDEX tmp_' . rand() . '_idx ON ?f USING BTREE(building)';
72 | $this->db->execute($sql, [$this->table]);
73 |
74 | $sql = 'CREATE INDEX tmp_2' . rand() . '_idx ON ?f USING BTREE(structure)';
75 | $this->db->execute($sql, [$this->table]);
76 |
77 | $sql = 'CREATE INDEX tmp_3' . rand() . '_idx ON ?f USING BTREE(house_id)';
78 | $this->db->execute($sql, [$this->table]);
79 |
80 | $sql = 'CREATE INDEX tmp_4' . rand() . '_idx ON ?f USING BTREE(previous_id)';
81 | $this->db->execute($sql, [$this->table]);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Importer.php:
--------------------------------------------------------------------------------
1 | db = $db;
25 | $this->fields = $fields;
26 | $this->table = $table;
27 |
28 | if ($isTemp) {
29 | $this->table .= '_xml_importer';
30 | DbHelper::createTable($this->db, $this->table, $this->fields, $isTemp);
31 | }
32 | }
33 |
34 | protected $rowsPerInsert = 1000;
35 |
36 | public function import(DataSource $reader)
37 | {
38 | $i = 0;
39 | while ($rows = $reader->getRows($this->rowsPerInsert)) {
40 | $this->db->execute($this->getQuery($rows[0]), [$rows]);
41 | ++$i;
42 | if (($i % 100) == 0) {
43 | $this->db->getLogger()->reset();
44 | }
45 | }
46 |
47 | return $this->table;
48 | }
49 |
50 | private $sqlHeader;
51 |
52 | private function getQuery($rowExample)
53 | {
54 | if (!$this->sqlHeader) {
55 | $fields = [];
56 | foreach ($rowExample as $attribute => $devNull) {
57 | $fields[] = $this->fields[$attribute]['name'];
58 | }
59 |
60 | $headerPart = $this->db->replacePlaceholders('INSERT INTO ?f(?i) VALUES ', [$this->table, $fields]);
61 |
62 | $this->sqlHeader = $headerPart . ' ?v';
63 | }
64 |
65 | return $this->sqlHeader;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/ImporterException.php:
--------------------------------------------------------------------------------
1 | wsdlUrl = $wsdlUrl;
20 | $this->fileDirectory = $fileDirectory;
21 |
22 | FileHelper::ensureIsDirectory($fileDirectory);
23 | FileHelper::ensureIsWritable($fileDirectory);
24 | }
25 |
26 | /** @var SoapResultWrapper */
27 | private $fileInfoResult = null;
28 |
29 | public function getLastFileInfo()
30 | {
31 | if (!$this->fileInfoResult) {
32 | $this->fileInfoResult = $this->getLastFileInfoRaw();
33 | }
34 |
35 | return $this->fileInfoResult;
36 | }
37 |
38 | private function getLastFileInfoRaw()
39 | {
40 | $client = new \SoapClient($this->wsdlUrl);
41 | $rawResult = $client->__soapCall('GetLastDownloadFileInfo', []);
42 |
43 | return new SoapResultWrapper($rawResult);
44 | }
45 |
46 | protected function loadFile($fileName, $url)
47 | {
48 | $filePath = $this->fileDirectory . '/' . $fileName;
49 | if (file_exists($filePath)) {
50 | if ($this->isFileSizeCorrect($filePath, $url)) {
51 | return $filePath;
52 | }
53 |
54 | unlink($filePath);
55 | }
56 |
57 | $fp = fopen($filePath, 'w');
58 | $ch = curl_init($url);
59 |
60 | curl_setopt($ch, CURLOPT_FILE, $fp);
61 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
62 |
63 | curl_exec($ch);
64 |
65 | curl_close($ch);
66 | fclose($fp);
67 |
68 | return $filePath;
69 | }
70 |
71 | protected function wrap($path)
72 | {
73 | $pathToDirectory = Dearchiver::extract($this->fileDirectory, $path);
74 | $this->addVersionId($pathToDirectory);
75 |
76 | return new Directory($pathToDirectory);
77 | }
78 |
79 | private function addVersionId($pathToDirectory)
80 | {
81 | $versionId = $this->getLastFileInfo()->getVersionId();
82 | file_put_contents($pathToDirectory . '/VERSION_ID_' . $versionId, 'Версия: ' . $versionId);
83 | }
84 |
85 | public function isFileSizeCorrect($filePath, $url)
86 | {
87 | $ch = curl_init($url);
88 |
89 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
90 | curl_setopt($ch, CURLOPT_HEADER, true);
91 | curl_setopt($ch, CURLOPT_NOBODY, true);
92 |
93 | curl_exec($ch);
94 |
95 | $correctSize = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
96 |
97 | curl_close($ch);
98 |
99 | return (filesize($filePath) == $correctSize);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Loader/InitLoader.php:
--------------------------------------------------------------------------------
1 | getLastFileInfo();
15 |
16 | return $this->wrap(
17 | $this->loadFile($filesInfo->getInitFileName(), $filesInfo->getInitFileUrl())
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Loader/SoapResultWrapper.php:
--------------------------------------------------------------------------------
1 | GetLastDownloadFileInfoResult;
14 | $this->versionId = $rawResult->VersionId;
15 | $this->initFileUrl = $rawResult->FiasCompleteXmlUrl;
16 | $this->updateFileUrl = $rawResult->FiasDeltaXmlUrl;
17 | }
18 |
19 | public function getVersionId()
20 | {
21 | return $this->versionId;
22 | }
23 |
24 | public function getUpdateFileUrl()
25 | {
26 | return $this->updateFileUrl;
27 | }
28 |
29 | public function getInitFileUrl()
30 | {
31 | return $this->initFileUrl;
32 | }
33 |
34 | public function getUpdateFileName()
35 | {
36 | $fileName = basename($this->updateFileUrl);
37 | return $this->versionId . '_' . $fileName;
38 | }
39 |
40 | public function getInitFileName()
41 | {
42 | $fileName = basename($this->initFileUrl);
43 | return $this->versionId . '_' . $fileName;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Loader/UpdateLoader.php:
--------------------------------------------------------------------------------
1 | getLastFileInfo();
15 |
16 | return $this->wrap(
17 | $this->loadFile($filesInfo->getUpdateFileName(), $filesInfo->getUpdateFileUrl())
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/PlaceStorage.php:
--------------------------------------------------------------------------------
1 | db = $db;
13 | }
14 |
15 | public function findPlace($place)
16 | {
17 | $sql = '
18 | SELECT p.id, pt.system_name type_system_name
19 | FROM places p
20 | INNER JOIN place_types pt
21 | ON pt.id = p.type_id
22 | WHERE lower(full_title) = lower(?q)'
23 | ;
24 |
25 | return $this->db->execute($sql, [$place])->fetchOneOrFalse();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/RawDataHelper.php:
--------------------------------------------------------------------------------
1 | execute($sql);
31 | }
32 |
33 | public static function cleanHouses(ConnectionInterface $db, $table = 'houses')
34 | {
35 | $inCorrectValues = ['нет', '-', 'стр.', 'стр1'];
36 |
37 | // Если будем импортировать больше половины регионов из фиаса, перенести на сторону PHP.
38 | $db->execute(
39 | "UPDATE ?f
40 | SET number = lower(number),
41 | building = CASE WHEN building IN (?l) THEN NULL ELSE lower(building) END,
42 | structure = CASE WHEN structure IN (?l) THEN NULL ELSE lower(structure) END
43 | WHERE number ~ '[^0-9]+'
44 | OR building ~ '[^0-9]+'
45 | OR structure ~ '[^0-9]+'
46 | ",
47 | [$table, $inCorrectValues, $inCorrectValues]
48 | );
49 |
50 | // Убираем ложные данные по корпусам и строениям ("1а" и в корпусе и в номере, например)
51 | $db->execute(
52 | "UPDATE ?f
53 | SET building = NULL,
54 | structure = NULL
55 | WHERE number ~ '[^0-9]+'
56 | AND (
57 | (structure ~ '[^0-9]+' AND number = structure)
58 | OR
59 | (building ~ '[^0-9]+' AND number = building)
60 | )
61 | ",
62 | [$table]
63 | );
64 |
65 | // нормализуем адрес по яндексу
66 | $db->execute(
67 | "UPDATE ?f
68 | SET full_number = COALESCE(number, '')
69 | ||COALESCE('к'||building, '')
70 | ||COALESCE('с'||structure, '')
71 | ",
72 | [$table]
73 | );
74 | }
75 |
76 | public static function updateHousesCount(ConnectionInterface $db)
77 | {
78 | // прописываем данные по домам в address_objects
79 | $db->execute(
80 | "UPDATE address_objects ao
81 | SET house_count = tmp.count,
82 | next_address_level = 0
83 | FROM (
84 | SELECT address_id, count(*) count
85 | FROM houses GROUP BY 1
86 | ) tmp
87 | WHERE tmp.address_id = ao.address_id
88 | "
89 | );
90 | }
91 |
92 | public static function updateNextAddressLevelFlag(ConnectionInterface $db)
93 | {
94 | $db->execute(
95 | "UPDATE address_objects ao
96 | SET next_address_level = tmp.address_level
97 | FROM (
98 | SELECT DISTINCT ON (parent_id) parent_id, address_level
99 | FROM address_objects
100 | WHERE parent_id IS NOT NULL
101 | ORDER BY parent_id, address_level
102 | ) tmp
103 | WHERE tmp.parent_id = address_id
104 | "
105 | );
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Remover.php:
--------------------------------------------------------------------------------
1 | db = $db;
17 | $this->table = $table;
18 | $this->keyFieldXml = $keyFieldXml;
19 | $this->keyFieldDatabase = $keyFieldDatabase;
20 | }
21 |
22 | public function remove(DataSource $reader)
23 | {
24 | while ($rows = $reader->getRows()) {
25 | $this->removeRows($rows);
26 | }
27 | }
28 |
29 | private function removeRows(array $rows)
30 | {
31 | $ids = [];
32 |
33 | foreach ($rows as $row) {
34 | if (empty($row[$this->keyFieldXml])) {
35 | throw new \LogicException('Не найдено поле: ' . $this->keyFieldXml);
36 | }
37 |
38 | $ids[] = $row[$this->keyFieldXml];
39 | }
40 |
41 | $this->db->execute('DELETE FROM ?f WHERE ?f IN (?l)', [$this->table, $this->keyFieldDatabase, $ids]);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/UpdateLogHelper.php:
--------------------------------------------------------------------------------
1 | execute('INSERT INTO update_log(version_id) VALUES (?q)', [$versionId]);
10 | }
11 |
12 | public static function getLastVersionId(ConnectionInterface $db)
13 | {
14 | return (int) $db->execute('SELECT MAX(version_id) FROM update_log')->fetchResult();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/AddressObjectsUpdaterTest.php:
--------------------------------------------------------------------------------
1 | '5c8b06f1-518e-496e-b683-7bf917e0d70b',
17 | 'AOGUID' => '0c5b2444-70a0-4932-980c-b4dc0d3f02b5',
18 | 'PREVIOUSID' => '0c5b2444-70a0-4932-980c-b4dc0d3f02b5',
19 | 'PARENTGUID' => NULL,
20 | 'FORMALNAME' => 'Москваа',
21 | 'POSTALCODE' => NULL,
22 | 'SHORTNAME' => 'г',
23 | ],
24 | [
25 | 'AOID' => '00000000-0000-0000-0000-000000000000',
26 | 'AOGUID' => '00000000-0000-0000-0000-000000000000',
27 | 'PREVIOUSID' => '0c5b2444-70a0-4932-980c-b4dc0d3f02b5',
28 | 'PARENTGUID' => '0c5b2444-70a0-4932-980c-b4dc0d3f02b5',
29 | 'FORMALNAME' => 'Все вы, питерские, идиоты какие-то',
30 | 'POSTALCODE' => NULL,
31 | 'SHORTNAME' => 'пл',
32 | ],
33 | ];
34 |
35 | $this->reader = $this->getReaderMock($this, [$results]);
36 | }
37 |
38 | /** @group slow */
39 | public function testUpdater()
40 | {
41 | $addressObjectConfig = $this->container->getAddressObjectsImportConfig();
42 |
43 | $addressObjectConfig['fields']['PREVIOUSID'] = ['name' => 'previous_id', 'type' => 'uuid'];
44 |
45 | $updater = new AddressObjectsUpdater($this->db, $addressObjectConfig['table_name'], $addressObjectConfig['fields']);
46 | $updater->update($this->reader);
47 |
48 | $this->assertEquals(
49 | 'г Москваа',
50 | $this->db->execute(
51 | 'SELECT full_title FROM address_objects WHERE address_id = ?q',
52 | ['0c5b2444-70a0-4932-980c-b4dc0d3f02b5']
53 | )->fetchResult()
54 | );
55 |
56 | $this->assertEquals(
57 | 'г Москваа, пл Все вы, питерские, идиоты какие-то',
58 | $this->db->execute(
59 | 'SELECT full_title FROM address_objects WHERE address_id = ?q',
60 | ['00000000-0000-0000-0000-000000000000']
61 | )->fetchResult()
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/AddressStorageTest.php:
--------------------------------------------------------------------------------
1 | storage = new AddressStorage($this->db);
12 | }
13 |
14 | public function testFindAddress()
15 | {
16 | $this->assertEquals(
17 | '29251dcf-00a1-4e34-98d4-5c47484a36d4',
18 | $this->storage->findAddress('г москва')['address_id']
19 | );
20 |
21 | $this->assertEquals(
22 | '77303f7c-452b-4e73-b2b0-cbc59fe636c2',
23 | $this->storage->findAddress('г москва, ул стахановская')['address_id']
24 | );
25 |
26 | $this->assertFalse($this->storage->findAddress('Ерунда какая-то, а не адрес.'));
27 | }
28 |
29 | public function testFindHouse()
30 | {
31 | $this->assertEquals(
32 | '841254dc-0074-41fe-99ba-0c8501526c04',
33 | $this->storage->findHouse('г москва, ул стахановская, 16с17')['house_id']
34 | );
35 |
36 | $this->assertFalse($this->storage->findHouse('Ерунда какая-то, а не дом'));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/ApiActionAddressCompletionTest.php:
--------------------------------------------------------------------------------
1 | db, 'Нави, Главная б', 50);
10 | $this->assertCount(0, $complete->run());
11 | }
12 |
13 | public function testAddressCompletion()
14 | {
15 | $complete = new AddressCompletion($this->db, 'г Москва, Ста', 50);
16 | $result = $complete->run();
17 |
18 | $this->assertCount(4, $result);
19 | $this->assertEquals('г Москва, пр Ставропольский', $result[0]['title']);
20 | $this->assertEquals(0, $result[0]['is_complete']);
21 | }
22 |
23 | public function testHomeCompletion()
24 | {
25 | $complete = new AddressCompletion($this->db, 'г Москва, ул Стахановская, 1', 2);
26 | $result = $complete->run();
27 |
28 | $this->assertCount(2, $result);
29 | $this->assertEquals('г Москва, ул Стахановская, 1к1', $result[0]['title']);
30 | $this->assertEquals(1, $result[0]['is_complete']);
31 | }
32 |
33 | public function testMaxAddressLevel()
34 | {
35 | $complete = new AddressCompletion($this->db, 'г Москва, Ста', 50, 'region');
36 | $this->assertEmpty($complete->run());
37 |
38 | $complete = new AddressCompletion($this->db, 'Моск', 50, 'region');
39 | $result = $complete->run();
40 | $this->assertCount(1, $result);
41 | $this->assertTrue($result[0]['is_complete']);
42 |
43 | $complete = new AddressCompletion($this->db, 'Моск', 50, 'street');
44 | $result = $complete->run();
45 | $this->assertCount(1, $result);
46 | $this->assertFalse($result[0]['is_complete']);
47 |
48 | $complete = new AddressCompletion($this->db, 'г Москва, ул Стахановская, 1', 2, 'building');
49 | $result = $complete->run();
50 |
51 | $this->assertCount(2, $result);
52 | }
53 |
54 | public function testRegion()
55 | {
56 | $complete = new AddressCompletion($this->db, 'Моск', 50, null, [78]);
57 | $this->assertEmpty($complete->run());
58 |
59 | $complete = new AddressCompletion($this->db, 'Моск', 50, null, [77]);
60 | $this->assertCount(1, $complete->run());
61 | }
62 |
63 | public function testTags()
64 | {
65 | $completion = new AddressCompletion($this->db, 'Моск', 50);
66 | $result = $completion->run();
67 |
68 | $this->assertEquals(['address'], $result[0]['tags']);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/ApiActionAddressPostalCodeTest.php:
--------------------------------------------------------------------------------
1 | db, 'Точно отсутствующий в базе адрес');
10 | $this->assertNull($mapping->run());
11 | }
12 |
13 | public function testAddressToPostalCodeMapping()
14 | {
15 | $mapping = new AddressPostalCode($this->db, 'г Москва, ал Старших Бобров');
16 | $this->assertEquals(123456, $mapping->run());
17 | }
18 |
19 | public function testAddressToPostalCodeMappingWithHomes()
20 | {
21 | // У дома есть свой индекс
22 | $mapping = new AddressPostalCode($this->db, 'г Москва, ул Стахановская, 16с17');
23 | $this->assertEquals(654321, $mapping->run());
24 |
25 | // У дома нет своего индекса
26 | $mapping = new AddressPostalCode($this->db, 'г Москва, ул Стахановская, 16с18');
27 | $this->assertEquals(123456, $mapping->run());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/ApiActionPlaceCompletionTest.php:
--------------------------------------------------------------------------------
1 | db, 'Никому неизвестное место', 50);
10 | $this->assertEmpty($completion->run());
11 | }
12 |
13 | public function testWithType()
14 | {
15 | $completion = new PlaceCompletion($this->db, 'вокзал Паве', 50);
16 | $this->assertEquals('Павелецкий вокзал', $completion->run()[0]['title']);
17 | }
18 |
19 | public function testWithoutType()
20 | {
21 | $completion = new PlaceCompletion($this->db, 'Павел', 50);
22 | $result = $completion->run();
23 |
24 | $this->assertEquals('Павелецкий автовокзал', $result[0]['title']);
25 | $this->assertEquals('Павелецкий вокзал', $result[1]['title']);
26 | }
27 |
28 | public function testWithParent()
29 | {
30 | $completion = new PlaceCompletion($this->db, 'пулко', 50);
31 | $result = $completion->run();
32 |
33 | $this->assertEquals('Пулково аэропорт', $result[0]['title']);
34 | $this->assertEquals(0, $result[0]['is_complete']);
35 |
36 | $completion = new PlaceCompletion($this->db, 'Пулково аэропорт, терминал', 50);
37 | $result = $completion->run();
38 |
39 | $this->assertCount(3, $result);
40 | $this->assertEquals('Пулково аэропорт, 1 терминал', $result[0]['title']);
41 | $this->assertEquals(1, $result[0]['is_complete']);
42 |
43 | $completion = new PlaceCompletion($this->db, 'Пулково аэропорт, но', 50);
44 | $result = $completion->run();
45 |
46 | $this->assertEquals('Пулково аэропорт, новый терминал', $result[0]['title']);
47 | $this->assertEquals(1, $result[0]['is_complete']);
48 | }
49 |
50 | public function testTags()
51 | {
52 | $completion = new PlaceCompletion($this->db, 'пулко', 50);
53 | $result = $completion->run();
54 |
55 | $this->assertEquals(['place', 'airport'], $result[0]['tags']);
56 |
57 | $completion = new PlaceCompletion($this->db, 'Павел', 50);
58 | $result = $completion->run();
59 |
60 | $this->assertEquals(['place', 'bus_terminal'], $result[0]['tags']);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/ApiActionPostalCodeLocationTest.php:
--------------------------------------------------------------------------------
1 | db, '234325432');
10 | $this->assertEmpty($mapping->run());
11 | }
12 |
13 | public function testPostalCodeToAddressMapping()
14 | {
15 | $mapping = new PostalCodeLocation($this->db, '123456');
16 | $parts = $mapping->run();
17 |
18 | $this->assertCount(1, $parts);
19 | $this->assertEquals($parts[0]['title'], 'г Москва');
20 |
21 | $mapping = new PostalCodeLocation($this->db, '1234567');
22 | $parts = $mapping->run();
23 |
24 | $this->assertCount(2, $parts);
25 | $this->assertEquals($parts[0]['title'], 'г Москва');
26 | $this->assertEquals($parts[0]['address_level'], 'region');
27 | $this->assertEquals($parts[1]['title'], 'р-н Мытищинский');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/ApiActionValidationTest.php:
--------------------------------------------------------------------------------
1 | db, 'Непонятный адрес');
10 | $result = $validate->run();
11 |
12 | $this->assertEmpty($result);
13 | }
14 |
15 | public function testIncomplete()
16 | {
17 | $validate = new Validation($this->db, 'г москва, ул стахановская');
18 | $result = $validate->run()[0];
19 |
20 | $this->assertFalse($result['is_complete']);
21 | }
22 |
23 | public function testValid()
24 | {
25 | $validate = new Validation($this->db, 'г москва, ул стахановская, 16с17');
26 | $result = $validate->run()[0];
27 |
28 | $this->assertTrue($result['is_complete']);
29 | $this->assertTrue(in_array('address', $result['tags']));
30 | $this->assertFalse(in_array('place', $result['tags']));
31 |
32 | $validate = new Validation($this->db, 'Павелецкий автовокзал');
33 | $result = $validate->run()[0];
34 |
35 | $this->assertTrue($result['is_complete']);
36 | $this->assertTrue(in_array('place', $result['tags']));
37 | $this->assertFalse(in_array('address', $result['tags']));
38 | $this->assertTrue(in_array('bus_terminal', $result['tags']));
39 | }
40 |
41 | public function testZeroLevel()
42 | {
43 | $validate = new Validation($this->db, 'г москва');
44 | $result = $validate->run()[0];
45 |
46 | $this->assertFalse($result['is_complete']);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/ApiControllerTest.php:
--------------------------------------------------------------------------------
1 | prepareHttpClient('http://' . $this->container->getHost());
13 | }
14 |
15 | public function testFormat()
16 | {
17 | $this->describe('Проверяем выдачу ошибки при отсутствии callback параметра если выбран jsonp')
18 | ->loadPage('/api/complete.jsonp/?pattern=Москва', 400)
19 | ->ensurePageContains('Отсутствует обязательный параметр: callback.')
20 | ->describe('Проверяем нормальную работу jsonp')
21 | ->loadPage('/api/complete.jsonp/?pattern=Москва&callback=testCallback', 200)
22 | ->ensurePageContains('testCallback(')
23 | ->describe('Проверяем нормальную работу json')
24 | ->loadPage('/api/complete.json/?pattern=Москва', 200)
25 | ->ensureResponse(json_decode($this->curResponse->getContent())->items)
26 | ;
27 | }
28 |
29 |
30 | public function testComplete()
31 | {
32 | $tooBigLimit = $this->container->getMaxCompletionLimit() + 1;
33 | $this->describe('Проверяем отсечение слишком большого лимита.')
34 | ->loadPage('/api/complete/?pattern=Москва&limit=' . $tooBigLimit, 400)
35 | ->describe('Проверяем автоподстановку для адресного объекта.')
36 | ->loadPage('/api/complete/?pattern=Моск', 200)
37 | ->ensureResponse(json_decode($this->curResponse->getContent())->items[0]->title == 'Московский вокзал')
38 | ->ensureResponse(json_decode($this->curResponse->getContent())->items[1]->title == 'г Москва')
39 | ->describe('Проверяем автоподстановку с ограничением максимально допустимого уровня')
40 | ->loadPage('/api/complete/?pattern=г Москва, Ста')
41 | ->ensureResponse(json_decode($this->curResponse->getContent())->items)
42 | ->loadPage('/api/complete/?pattern=г Москва, Ста&max_address_level=region')
43 | ->ensureResponse(!json_decode($this->curResponse->getContent())->items)
44 | ->loadPage('/api/complete/?pattern=Моск&max_address_level=region')
45 | ->ensureResponse(json_decode($this->curResponse->getContent())->items)
46 | ->describe('Проверяем автоподстановку для дома.')
47 | ->loadPage('/api/complete/?pattern=г Москва, ул Стахановская, 1')
48 | ->ensureResponse(json_decode($this->curResponse->getContent())->items[0]->title == 'г Москва, ул Стахановская, 1к1')
49 | ->describe('Проверяем автоподстановку для места')
50 | ->loadPage('/api/complete/?pattern=Паве')
51 | ->ensureResponse(count(json_decode($this->curResponse->getContent())->items) == 2)
52 | ;
53 | }
54 |
55 | public function testValidate()
56 | {
57 | $this->describe('Проверяем выдачу ошибки при отсутствии необходимого параметра')
58 | ->loadPage('/api/validate/', 400)
59 | ->describe('Проверяем валидацию корректного адресного объекта')
60 | ->loadPage('/api/validate/?pattern=г Москва', 200)
61 | ->ensureResponse(!json_decode($this->curResponse->getContent())->items[0]->is_complete)
62 | ->describe('Проверяем валидацию некорректного адресного объекта')
63 | ->loadPage('/api/validate/?pattern=г Мосва', 200)
64 | ->ensureResponse(!json_decode($this->curResponse->getContent())->items)
65 | ->describe('Проверяем валидацию корректного дома')
66 | ->loadPage('/api/validate/?pattern=г Москва, ул Стахановская, 1к1', 200)
67 | ->ensureResponse(json_decode($this->curResponse->getContent())->items[0]->is_complete)
68 | ->describe('Проверяем валидацию некорректного дома')
69 | ->loadPage('/api/validate/?pattern=г Москва, ул Стахановская, 1324326с1', 200)
70 | ->ensureResponse(!json_decode($this->curResponse->getContent())->items)
71 | ;
72 | }
73 |
74 | public function testMapping()
75 | {
76 | $this->describe('Проверяем выдачу ошибки при отсутствии необходимого параметра')
77 | ->loadPage('/api/address_postal_code', 400)
78 | ->loadPage('/api/postal_code_location', 400)
79 | ->describe('Проверяем выдачу корректного почтового индекса')
80 | ->loadPage('/api/address_postal_code/?address=г Москва, ул Стахановская', 200)
81 | ->ensureResponse(json_decode($this->curResponse->getContent())->postal_code == 123456)
82 | ->describe('Проверяем выдачу адресов')
83 | ->loadPage('/api/postal_code_location/?postal_code=123456', 200)
84 | ->ensureResponse(count(json_decode($this->curResponse->getContent())->address_parts) == 1)
85 | ;
86 | }
87 |
88 | public function testNotFound()
89 | {
90 | $this->describe('Проверяем выдачу 404 ошибки при ошибочном uri')
91 | ->loadPage('/totally/wrong/destination', 404)
92 | ;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/DbHelperTest.php:
--------------------------------------------------------------------------------
1 | container->getDatabaseName(), __DIR__ . '/resources/inCorrectScript.sql');
12 | }
13 |
14 | public function testRun()
15 | {
16 | DbHelper::runFile($this->container->getDatabaseName(), __DIR__ . '/resources/correctScript.sql');
17 | $this->assertEquals(2, $this->db->execute('SELECT * FROM "correctTable"')->getNumRows());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tests/DearchiverTest.php:
--------------------------------------------------------------------------------
1 | fileDirectory = __DIR__ . '/file_directory';
16 | $this->testTxtFile = $this->fileDirectory . '/dearchiverTestFile.txt';
17 | $this->testRarFile = $this->fileDirectory . '/dearchiverTestFile.rar';
18 |
19 | file_put_contents($this->testTxtFile, $text);
20 |
21 | $cmd = 'rar a '
22 | . escapeshellarg($this->testRarFile)
23 | . ' '
24 | . escapeshellarg($this->testTxtFile)
25 | . ' 2>&1'
26 | ;
27 |
28 | exec($cmd, $output, $result);
29 |
30 | if ($result !== 0) {
31 | throw new \Exception('Ошибка архивации: ' . implode("\n", $output));
32 | }
33 | }
34 |
35 | protected function tearDown()
36 | {
37 | $this->cleanUpFileDirectory();
38 | }
39 |
40 | /** @expectedException \FileSystem\FileException */
41 | public function testBadFile()
42 | {
43 | Dearchiver::extract($this->fileDirectory, 'bad_file');
44 | }
45 |
46 | /** @expectedException \FileSystem\FileException */
47 | public function testBadDirectory()
48 | {
49 | Dearchiver::extract('bad_directory', $this->testRarFile);
50 | }
51 |
52 | private $extractedFiles;
53 |
54 | public function testNormalFile()
55 | {
56 | $this->extractedFiles = Dearchiver::extract($this->fileDirectory, $this->testRarFile);
57 | $this->assertEquals(
58 | md5_file($this->testTxtFile),
59 | md5_file($this->extractedFiles . '/' . basename($this->testTxtFile))
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/tests/DirectoryTest.php:
--------------------------------------------------------------------------------
1 | directory = new Directory(__DIR__ . '/resources/directoryTest');
13 | }
14 |
15 | public function testGet()
16 | {
17 | $this->assertEquals(
18 | $this->directory->getPath() . '/AS_ADDROBJ_20131221_5316e71a-a8d8-49df-b17c-66d3a981906a.XML',
19 | $this->directory->getAddressObjectFile()
20 | );
21 |
22 | $this->assertEquals(
23 | $this->directory->getPath() . '/AS_HOUSE_20131221_bccfd0d0-af7a-49db-8401-df23dc3d2efa.XML',
24 | $this->directory->getHouseFile()
25 | );
26 |
27 | $this->assertEquals(
28 | $this->directory->getPath() . '/AS_DEL_ADDROBJ_20131221_8a0076a7-1f52-4423-8fc6-58dec367832b.XML',
29 | $this->directory->getDeletedAddressObjectFile()
30 | );
31 |
32 | $this->assertEquals(
33 | $this->directory->getPath() . '/AS_DEL_HOUSE_20131221_ea93b12d-129d-46a0-9cfb-429b64a28873.XML',
34 | $this->directory->getDeletedHouseFile()
35 | );
36 | }
37 |
38 | /**
39 | * @expectedException \FileSystem\FileException
40 | * @expectedExceptionMessage Файл с префиксом
41 | */
42 | public function testFileNotFound()
43 | {
44 | $this->directory = new Directory(__DIR__);
45 | $this->directory->getHouseFile();
46 | }
47 |
48 | /** @expectedException \FileSystem\FileException */
49 | public function testDirectoryNotFound()
50 | {
51 | new Directory('badDir');
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/HousesUpdaterTest.php:
--------------------------------------------------------------------------------
1 | 'a64330e3-7a41-41ee-a8a2-41db8693c584',
16 | 'HOUSEGUID' => 'a64330e3-7a41-41ee-a8a2-41db8693c584',
17 | 'PREVIOUSID' => 'a64330e3-7a41-41ee-a8a2-41db8693c584',
18 | 'AOGUID' => '77303f7c-452b-4e73-b2b0-cbc59fe636c5',
19 | 'HOUSENUM' => '02',
20 | 'BUILDNUM' => '123',
21 | 'STRUCNUM' => 'нет'
22 | ],
23 | [
24 | 'HOUSEID' => '00000000-0000-0000-0000-000000000000',
25 | 'HOUSEGUID' => '00000000-0000-0000-0000-000000000000',
26 | 'PREVIOUSID' => '00000000-0000-0000-0000-000000000000',
27 | 'AOGUID' => '77303f7c-452b-4e73-b2b0-cbc59fe636c5',
28 | 'HOUSENUM' => '1',
29 | 'BUILDNUM' => '2',
30 | 'STRUCNUM' => '3'
31 | ],
32 | ];
33 |
34 | $this->reader = $this->getReaderMock($this, [$results]);
35 | }
36 |
37 | /** @group slow */
38 | public function testUpdater()
39 | {
40 | $countBeforeUpdate = (int) $this->db->execute(
41 | 'SELECT house_count FROM address_objects WHERE id = ?q',
42 | ['0c5b2444-70a0-4932-980c-b4dc0d3f02b5']
43 | )->fetchResult();
44 |
45 | $housesConfig = $this->container->getHousesImportConfig();
46 |
47 | $housesConfig['fields']['PREVIOUSID'] = ['name' => 'previous_id', 'type' => 'uuid'];
48 |
49 | $updater = new HousesUpdater($this->db, $housesConfig['table_name'], $housesConfig['fields']);
50 | $updater->update($this->reader);
51 |
52 | $this->assertEquals(
53 | '02к123',
54 | $this->db->execute(
55 | 'SELECT full_number FROM houses WHERE house_id = ?q',
56 | ['a64330e3-7a41-41ee-a8a2-41db8693c584']
57 | )->fetchResult()
58 | );
59 |
60 | $this->assertEquals(
61 | '1к2с3',
62 | $this->db->execute(
63 | 'SELECT full_number FROM houses WHERE house_id = ?q',
64 | ['00000000-0000-0000-0000-000000000000']
65 | )->fetchResult()
66 | );
67 |
68 | $this->assertEquals(
69 | $countBeforeUpdate + 2,
70 | $this->db->execute(
71 | 'SELECT house_count FROM address_objects WHERE address_id = ?q',
72 | ['77303f7c-452b-4e73-b2b0-cbc59fe636c5']
73 | )->fetchResult()
74 | );
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/tests/ImporterTest.php:
--------------------------------------------------------------------------------
1 | table = 'test_table';
11 | }
12 |
13 | /**
14 | * @expectedException \ImporterException
15 | * @expectedExceptionMessage таблица
16 | */
17 | public function testEmptyTable()
18 | {
19 | new Importer($this->db, '', ['one', 'two', 'three']);
20 | }
21 |
22 | /**
23 | * @expectedException \ImporterException
24 | * @expectedExceptionMessage поля
25 | */
26 | public function testEmptyFields()
27 | {
28 | new Importer($this->db, 'some_table_name', []);
29 | }
30 |
31 | public function testImport()
32 | {
33 | $results = [
34 | [
35 | ['id' => 1, 'madeIn' => 'China', 'title' => 'Phone'],
36 | ['id' => 2, 'madeIn' => 'USA', 'title' => 'Chicken wings'],
37 | ['id' => 3, 'madeIn' => 'Russia', 'title' => 'Topol-M'],
38 | ],
39 | [
40 | ['id' => 4, 'madeIn' => 'France', 'title' => 'Wine'],
41 | ['id' => 5, 'madeIn' => 'Germany', 'title' => 'Audi'],
42 | ['id' => 6, 'madeIn' => 'Denmark', 'title' => 'Tulip'],
43 | ],
44 | ];
45 |
46 | $reader = $this->getReaderMock($this, $results);
47 | $fields = [
48 | 'madeIn' => ['name' => 'two'],
49 | 'id' => ['name' => 'one'],
50 | 'title' => ['name' => 'three'],
51 | ];
52 |
53 | $importer = new Importer($this->db, $this->table, $fields);
54 | $tableName = $importer->import($reader);
55 |
56 | $this->assertEquals(
57 | 6,
58 | $this->db->execute('SELECT COUNT(*) count FROM ?F', [$tableName])->fetchResult()
59 | );
60 |
61 | $this->assertEquals(
62 | 'USA',
63 | $this->db->execute("SELECT two FROM ?F WHERE one = '2'", [$tableName])->fetchResult()
64 | );
65 |
66 | $this->assertEquals(
67 | 'Tulip',
68 | $this->db->execute("SELECT three FROM ?F WHERE one = '6'", [$tableName])->fetchResult()
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/IntervalGeneratorTest.php:
--------------------------------------------------------------------------------
1 | getGenerator();
14 | $reader->getRows(0);
15 | }
16 |
17 | public function testGeneratedRow()
18 | {
19 | $reader = $this->getGenerator(
20 | [
21 | [['title' => 'Title 1', 'start' => 5, 'end' => 9, 'type' => 1]],
22 | ]
23 | );
24 |
25 | $rows = $reader->getRows();
26 |
27 | $this->assertEquals(5, count($rows));
28 | $this->assertEquals('Title 1', $rows[3]['title']);
29 | $this->assertEquals(5, $rows[0]['result']);
30 | $this->assertEquals(9, $rows[4]['result']);
31 | }
32 |
33 | public function testGetRowsWithCount()
34 | {
35 | $reader = $this->getGenerator(
36 | [
37 | [['title' => 'Title 1', 'start' => 5, 'end' => 9, 'type' => 1]],
38 | [['title' => 'Title 2', 'start' => 10, 'end' => 14, 'type' => 2]],
39 | [['title' => 'Title 3', 'start' => 15, 'end' => 20, 'type' => 3]],
40 | ]
41 | );
42 |
43 | // Выборка прервана на getRowsFromInterval
44 | $result1 = $reader->getRows(3);
45 | // Выборка должна прерваться в конце второй строки
46 | $result2 = $reader->getRows(5);
47 | // Тут только по 3-й строке
48 | $result3 = $reader->getRows();
49 |
50 | // Смотрим количество
51 | $this->assertEquals(3, count($result1));
52 | $this->assertEquals(5, count($result2));
53 | $this->assertEquals(3, count($result3));
54 |
55 | // Проверяем значения на стыках
56 | $this->assertEquals(7, $result1[2]['result']);
57 | $this->assertEquals(8, $result2[0]['result']);
58 | $this->assertEquals(14, $result2[4]['result']);
59 | $this->assertEquals(15, $result3[0]['result']);
60 | }
61 |
62 | /**
63 | * @dataProvider provider
64 | */
65 | public function testGeneration($correctResult, $readerData)
66 | {
67 | $reader = $this->getGenerator([$readerData]);
68 | $this->assertEquals($correctResult, count($reader->getRows()));
69 | }
70 |
71 | public function provider()
72 | {
73 | return [
74 | [
75 | 3,
76 | [
77 | ['title' => 'Title', 'start' => 9, 'end' => 15, 'type' => 2],
78 | ],
79 | ],
80 | [
81 | 4,
82 | [
83 | ['title' => 'Title', 'start' => 10, 'end' => 16, 'type' => 2],
84 | ],
85 | ],
86 | [
87 | 3,
88 | [
89 | ['title' => 'Title', 'start' => 8, 'end' => 14, 'type' => 3],
90 | ],
91 | ],
92 | [
93 | 4,
94 | [
95 | ['title' => 'Title', 'start' => 9, 'end' => 15, 'type' => 3],
96 | ],
97 | ],
98 | ];
99 | }
100 |
101 | public function getGenerator($results = [])
102 | {
103 | return new IntervalGenerator($this->getReaderMock($this, $results), 'start', 'end', 'type', 'result');
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/tests/RawDataHelperTest.php:
--------------------------------------------------------------------------------
1 | addressObjectTable = 'address_objects';
13 | $this->housesTable = 'houses';
14 |
15 | $this->db->execute('TRUNCATE TABLE address_objects CASCADE');
16 | $this->db->execute('TRUNCATE TABLE houses CASCADE');
17 |
18 | $addressObjects = [
19 | ['5c8b06f1-518e-496e-b683-7bf917e0d70b', '0c5b2444-70a0-4932-980c-b4dc0d3f02b5', NULL, 'Москва', NULL, 'г'],
20 | ['afdda482-42ae-45d3-9af1-61ac6da41105', '0ecde158-a58f-43af-9707-aa6dd3484b56', '0c5b2444-70a0-4932-980c-b4dc0d3f02b5', 'Тверская', NULL, 'ул'],
21 | ];
22 | $this->db->execute(
23 | 'INSERT INTO ?f ("id", "address_id", "parent_id", "title", "postal_code", "prefix")
24 | VALUES ?v',
25 | [$this->addressObjectTable, $addressObjects]
26 | );
27 |
28 | $houses = [
29 | ['a64330e3-7a41-41ee-a8a2-41db8693c584', 'a64330e3-7a41-41ee-a8a2-41db8693c584', '0ecde158-a58f-43af-9707-aa6dd3484b56', '02', '1', 'нет'],
30 | ['b3ace9e8-dead-4e2c-9c56-e524aef28082', '4fd3b082-34bf-4ad9-8f27-c5c92952554c', '0ecde158-a58f-43af-9707-aa6dd3484b56', '02a', '02a', null],
31 | ];
32 | $this->db->execute(
33 | 'INSERT INTO ?f ("id", "house_id", "address_id", "number", "structure", "building")
34 | VALUES ?v',
35 | [$this->housesTable, $houses]
36 | );
37 | }
38 |
39 | public function testCleanAddressObjects()
40 | {
41 | RawDataHelper::cleanAddressObjects($this->db);
42 |
43 | $this->assertEquals(
44 | 'г Москва',
45 | $this->db->execute(
46 | "SELECT full_title FROM ?f WHERE id = '5c8b06f1-518e-496e-b683-7bf917e0d70b'",
47 | [$this->addressObjectTable]
48 | )->fetchResult()
49 | );
50 |
51 | $this->assertEquals(
52 | 'г Москва, ул Тверская',
53 | $this->db->execute(
54 | "SELECT full_title FROM ?f WHERE id = 'afdda482-42ae-45d3-9af1-61ac6da41105'",
55 | [$this->addressObjectTable]
56 | )->fetchResult()
57 | );
58 |
59 | $this->assertEquals(
60 | 0,
61 | $this->db->execute(
62 | "SELECT level FROM ?f WHERE id = '5c8b06f1-518e-496e-b683-7bf917e0d70b'",
63 | [$this->addressObjectTable]
64 | )->fetchResult()
65 | );
66 |
67 | $this->assertEquals(
68 | 1,
69 | $this->db->execute(
70 | "SELECT level FROM ?f WHERE id = 'afdda482-42ae-45d3-9af1-61ac6da41105'",
71 | [$this->addressObjectTable]
72 | )->fetchResult()
73 | );
74 | }
75 |
76 | public function testCleanHouses()
77 | {
78 | RawDataHelper::cleanHouses($this->db, $this->housesTable);
79 |
80 | $this->assertEquals(
81 | 1,
82 | $this->db->execute(
83 | 'SELECT COUNT(*) FROM ?f WHERE building IS NULL AND id = ?q',
84 | [$this->housesTable, 'a64330e3-7a41-41ee-a8a2-41db8693c584']
85 | )->fetchResult()
86 | );
87 |
88 | $this->assertEquals(
89 | 1,
90 | $this->db->execute(
91 | 'SELECT COUNT(*) FROM ?f WHERE building IS NULL AND structure IS NULL AND id = ?q',
92 | [$this->housesTable, 'b3ace9e8-dead-4e2c-9c56-e524aef28082']
93 | )->fetchResult()
94 | );
95 |
96 | $this->assertEquals(
97 | 1,
98 | $this->db->execute(
99 | 'SELECT COUNT(*) FROM ?f WHERE full_number = ?q AND id = ?q',
100 | [$this->housesTable, '02с1', 'a64330e3-7a41-41ee-a8a2-41db8693c584']
101 | )->fetchResult()
102 | );
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tests/RemoverTest.php:
--------------------------------------------------------------------------------
1 | db = $this->container->getDb();
15 | $this->table = 'test_table';
16 |
17 | $results = [];
18 | for ($i = 1; $i < 200; ++$i) {
19 | $results[] = ['xmlId' => $i];
20 | }
21 | $this->reader = $this->getReaderMock($this, [$results]);
22 |
23 | $this->db->execute('DROP TABLE IF EXISTS ?f', [$this->table]);
24 | $this->db->execute('CREATE TEMP TABLE ?f (id integer)', [$this->table]);
25 | $this->db->execute('INSERT INTO ?f SELECT generate_series(0,499)', [$this->table]);
26 | }
27 |
28 | /**
29 | * @expectedException \LogicException
30 | * @expectedExceptionMessage Не найдено
31 | */
32 | public function testRemoveWithBadParams()
33 | {
34 | $remover = new Remover($this->db, $this->table, 'fakeKey', 'keyFake');
35 | $remover->remove($this->reader);
36 | }
37 |
38 | public function testRemove()
39 | {
40 | $this->assertEquals('500', $this->db->execute('SELECT COUNT(*) FROM ?f', [$this->table])->fetchResult());
41 |
42 | $remover = new Remover($this->db, $this->table, 'xmlId', 'id');
43 | $remover->remove($this->reader);
44 |
45 | $this->assertEquals('301', $this->db->execute('SELECT COUNT(*) FROM ?f', [$this->table])->fetchResult());
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/TestAbstract.php:
--------------------------------------------------------------------------------
1 | container = new Container();
20 |
21 | if (!static::$dumpManager) {
22 | static::$dumpManager = new DatabaseDumpManager(null, $this->container->getDbUri());
23 | }
24 |
25 | static::$dumpManager->restore('init', function () {
26 | static::$dumpManager->clean(false);
27 | exec('php ' . __DIR__ . '/../cli/init-db.php');
28 | });
29 |
30 | $this->db = $this->container->getDb();
31 | }
32 |
33 | public static function cleanUpFileDirectory()
34 | {
35 | static::removeFilesInDirectory(__DIR__ . '/file_directory');
36 | }
37 |
38 | private static function removeFilesInDirectory($directoryPath)
39 | {
40 | $files = scandir($directoryPath);
41 | foreach ($files as $file) {
42 | if ($file == '.' || $file == '..') {
43 | continue;
44 | }
45 |
46 | $filePath = $directoryPath . '/' . $file;
47 |
48 | if (is_dir($filePath)) {
49 | static::removeFilesInDirectory($filePath);
50 | rmdir($filePath);
51 | } else {
52 | unlink($filePath);
53 | }
54 | }
55 | }
56 |
57 | /**
58 | * @param \PHPUnit_Framework_TestCase $testCase
59 | * @param array $results
60 | * @return XmlReader
61 | */
62 | public function getReaderMock(\PHPUnit_Framework_TestCase $testCase, array $results)
63 | {
64 | $result = new PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls(array_merge($results, []));
65 | $reader = $testCase->getMockBuilder('\DataSource\XmlReader')
66 | ->disableOriginalConstructor()
67 | ->getMock()
68 | ;
69 |
70 | $reader->expects(static::any())
71 | ->method('getRows')
72 | ->will($result)
73 | ;
74 |
75 | return $reader;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/UpdateLoaderTest.php:
--------------------------------------------------------------------------------
1 | fileDirectory = __DIR__ . '/file_directory';
13 |
14 | if (!is_dir($this->fileDirectory)) {
15 | mkdir($this->fileDirectory);
16 | }
17 |
18 | $information = $this->getInformationAboutCurrentUpdateFile();
19 | @unlink($this->fileDirectory . '/' . $information['version'] . '_fias_delta_xml.rar');
20 | }
21 |
22 | protected function tearDown()
23 | {
24 | $this->cleanUpFileDirectory();
25 | }
26 |
27 | public function testLoad()
28 | {
29 | $loader = new UpdateLoader($this->container->getWsdlUrl(), $this->fileDirectory);
30 | $filesCount = count(scandir($loader->load()->getPath()));
31 | $this->assertGreaterThan(16, $filesCount);
32 | }
33 |
34 | public function testReWritingBadFile()
35 | {
36 | $message = 'Really bad file';
37 | $filePath = $this->fileDirectory
38 | . '/'
39 | . $this->getInformationAboutCurrentUpdateFile()['version']
40 | . '_fias_delta_xml.rar'
41 | ;
42 |
43 | file_put_contents($filePath, $message);
44 |
45 | $loader = new UpdateLoader($this->container->getWsdlUrl(), $this->fileDirectory);
46 | $loader->load();
47 |
48 | $this->assertTrue(strlen($message) != filesize($filePath));
49 | }
50 |
51 | public function testNoRewritingGoodFile()
52 | {
53 | $loader = new UpdateLoader($this->container->getWsdlUrl(), $this->fileDirectory);
54 | $loader->load();
55 |
56 | $filePath = $this->fileDirectory
57 | . '/'
58 | . $this->getInformationAboutCurrentUpdateFile()['version']
59 | . '_fias_delta_xml.rar'
60 | ;
61 |
62 | $this->assertTrue($loader->isFileSizeCorrect($filePath, $this->getInformationAboutCurrentUpdateFile()['url']));
63 | }
64 |
65 | private $updateInformation = [];
66 |
67 | private function getInformationAboutCurrentUpdateFile()
68 | {
69 | if (!$this->updateInformation) {
70 | try {
71 | $client = new \SoapClient($this->container->getWsdlUrl());
72 | $filesInfo = $client->__soapCall('GetLastDownloadFileInfo', []);
73 | } catch (SoapFault $e) {
74 | $this->markTestSkipped($e->getMessage());
75 | return []; // IDE calmer, markTestSkipped throws an exception
76 | }
77 |
78 | $ch = curl_init($filesInfo->GetLastDownloadFileInfoResult->FiasDeltaXmlUrl);
79 |
80 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
81 | curl_setopt($ch, CURLOPT_HEADER, true);
82 | curl_setopt($ch, CURLOPT_NOBODY, true);
83 |
84 | curl_exec($ch);
85 |
86 | $this->updateInformation = [
87 | 'url' => $filesInfo->GetLastDownloadFileInfoResult->FiasDeltaXmlUrl,
88 | 'version' => $filesInfo->GetLastDownloadFileInfoResult->VersionId,
89 | 'file_size' => curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD),
90 | ];
91 |
92 | curl_close($ch);
93 | }
94 |
95 | return $this->updateInformation;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/tests/UpdateLogHelperTest.php:
--------------------------------------------------------------------------------
1 | db, 100000);
8 |
9 | $this->assertEquals(
10 | 100000,
11 | $this->db->execute('SELECT MAX(version_id) FROM update_log')->fetchResult()
12 | );
13 | }
14 |
15 | public function testGetLastVersionId()
16 | {
17 | $values = [
18 | [12],
19 | [18],
20 | [180],
21 | ];
22 | $this->db->execute('INSERT INTO update_log(version_id) VALUES ?v', [$values]);
23 |
24 | $this->assertEquals(
25 | 180,
26 | UpdateLogHelper::getLastVersionId($this->db)
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/XmlTest.php:
--------------------------------------------------------------------------------
1 | 'available', 'type' => 'eq', 'value' => 1],
14 | ['field' => 'madeIn', 'type' => 'in', 'value' => ['USA', 'China', 'Germany', 'Rwanda']],
15 | ['field' => 'title', 'type' => 'in', 'value' => []],
16 | ['field' => 'id', 'type' => 'nin', 'value' => [6]],
17 | [
18 | 'field' => 'id',
19 | 'type' => 'hash',
20 | 'value' => [1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 6 => true, 7 => false]
21 | ],
22 | ];
23 |
24 | $this->reader = new XmlReader(
25 | __DIR__ . '/resources/readerTest.xml',
26 | 'Computer',
27 | [
28 | 'id',
29 | 'madeIn',
30 | 'fakeAttribute'
31 | ],
32 | $filters
33 | );
34 | }
35 |
36 | public function testRead()
37 | {
38 | $rows = $this->reader->getRows();
39 |
40 | $this->assertEquals(2, count($rows));
41 | $this->assertEquals('USA', $rows[1]['madeIn']);
42 | $this->assertEquals(null, $rows[0]['fakeAttribute']);
43 | $this->assertTrue(!isset($rows[0]['title']));
44 | }
45 |
46 | public function testReadWithCount()
47 | {
48 | $this->assertEquals(1, count($this->reader->getRows(1)));
49 | $this->assertEquals(1, count($this->reader->getRows(1)));
50 | $this->assertEquals(0, count($this->reader->getRows(1)));
51 | }
52 |
53 | /**
54 | * @expectedException \LogicException
55 | * @expectedExceptionMessage количества
56 | */
57 | public function testBadCount()
58 | {
59 | $this->reader->getRows(0);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/tests/resources/correctScript.sql:
--------------------------------------------------------------------------------
1 | START TRANSACTION;
2 |
3 | DROP TABLE IF EXISTS "correctTable";
4 | CREATE TABLE "correctTable" (id SERIAL, title VARCHAR);
5 |
6 | INSERT INTO "correctTable" (id, title)
7 | VALUES (1, 'test1'), (2, 'test2');
8 |
9 | COMMIT;
10 |
--------------------------------------------------------------------------------
/tests/resources/directoryTest/AS_ADDROBJ_20131221_5316e71a-a8d8-49df-b17c-66d3a981906a.XML:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/resources/directoryTest/AS_DEL_ADDROBJ_20131221_8a0076a7-1f52-4423-8fc6-58dec367832b.XML:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/resources/directoryTest/AS_DEL_HOUSE_20131221_ea93b12d-129d-46a0-9cfb-429b64a28873.XML:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/resources/directoryTest/AS_HOUSE_20131221_bccfd0d0-af7a-49db-8401-df23dc3d2efa.XML:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/resources/inCorrectScript.sql:
--------------------------------------------------------------------------------
1 | SELECT * FROM "totallyInCorrectTableName";
2 |
--------------------------------------------------------------------------------
/tests/resources/load.csv:
--------------------------------------------------------------------------------
1 | e8562dcc-8e4e-4673-86cc-467f5705f5fb г Москва, Пар г Москва, ул Парковая 9-я, 1 г Москва, ул Парковая 9-я, 11а
2 | e3a4f8a1-9bbd-4528-905f-8d0eb5a10733 г Москва, Хор г Москва, проезд Хорошевский 2-й, 7 г Москва, проезд Хорошевский 2-й, 7с1
3 | cb2a1efe-dcee-4f0f-9d4c-5a703b9550f6 г Москва, Мож г Москва, ш Можайское, 4 г Москва, ш Можайское, 4к1с2
4 | 9c3e9392-0324-4d21-9cf5-70076f1b5e15 г Москва, Лен г Москва, пр-кт Ленинградский, 7 г Москва, пр-кт Ленинградский, 74к6
5 | bae2a8c8-1bee-44d5-800e-ffb906d8460f г Москва, Хло г Москва, ул Хлобыстова, 2 г Москва, ул Хлобыстова, 20к3
6 | 13dc3c29-418f-4789-a235-647ab5a58503 г Москва, Шип г Москва, проезд Шипиловский, 2 г Москва, проезд Шипиловский, 28с2
7 | 5d835f2e-b03d-40e3-bc8b-1df81f6f36c4 г Москва, Кав г Москва, б-р Кавказский, 1 г Москва, б-р Кавказский, 17а
8 | 2d846b8c-d2d6-4c48-aa02-acea32de48e2 г Москва, Гол г Москва, ул Гольяновская, 4 г Москва, ул Гольяновская, 4ас7
9 | 7eb9ee15-936a-4cc5-b191-e1d601e60d04 г Москва, Пер г Москва, пер Переведеновский, 1 г Москва, пер Переведеновский, 13с5
10 | 25d692b4-0b3e-406f-8936-51207019ae2f г Москва, Кля г Москва, ул Клязьминская, 2 г Москва, ул Клязьминская, 21а
11 | 7879822d-005a-4499-8924-465399eb3e40 г Москва, Сим г Москва, наб Симоновская, 1 г Москва, наб Симоновская, 1с49
12 | 1735120d-e814-4ca2-bf8d-5f53b42ea541 г Москва, Тве г Москва, ул Тверская-Ямская 3-Я, 2 г Москва, ул Тверская-Ямская 3-Я, 22с5
13 | e8a84c63-3907-41a1-b3da-f338a94be538 г Москва, Кас г Москва, ул Каскадная, 1 г Москва, ул Каскадная, 15
14 | 27f5dd0f-9040-48ea-8631-52b7f5852670 г Москва, Изу г Москва, ул Изумрудная, 3 г Москва, ул Изумрудная, 32с1
15 | 6d7e8cd6-5688-4737-8c1e-ec485ec11637 г Москва, Жук г Москва, проезд Жуков, 8 г Москва, проезд Жуков, 8с4
16 | 4d3154e3-61e1-4dfb-a299-18e290fbffd7 г Москва, Физ г Москва, проезд Физкультурный, 3 г Москва, проезд Физкультурный, 3к2
17 | fcbf0d82-176c-473f-80bb-b4048d03f9a0 г Москва, Каш г Москва, ш Каширское, 3 г Москва, ш Каширское, 33к13
18 | 2202650e-00a5-4e05-b7dc-d2138a2d2468 г Москва, Дми г Москва, ш Дмитровское, 1 г Москва, ш Дмитровское, 107с25а
19 | 7122adb7-6f1c-4d48-b2b9-436a6d9832b2 г Москва, Вод г Москва, ул Водников, 2 г Москва, ул Водников, 20
20 | 36b4d93f-38cd-4b02-9d0d-c005f14e38eb г Москва, Ряз г Москва, пр-кт Рязанский, 4 г Москва, пр-кт Рязанский, 49к1
21 |
--------------------------------------------------------------------------------
/tests/resources/load.jmx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | false
7 | false
8 |
9 |
10 |
11 | SERVER
12 | fias.loc
13 | =
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | false
23 | -1
24 |
25 | 400
26 | 5
27 | 1391087137000
28 | 1391087137000
29 | false
30 | continue
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | ${SERVER}
40 |
41 |
42 |
43 | http
44 |
45 | /api/complete/?pattern=Москв
46 | GET
47 | true
48 | false
49 | true
50 | false
51 |
52 |
53 |
54 | false
55 |
56 |
57 |
58 |
59 |
60 |
61 | Connection
62 | keep-alive
63 |
64 |
65 | Accept-Language
66 | en-US,en;q=0.5
67 |
68 |
69 | Accept
70 | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
71 |
72 |
73 | User-Agent
74 | Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0
75 |
76 |
77 | Accept-Encoding
78 | gzip, deflate
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | ${SERVER}
89 |
90 |
91 |
92 | http
93 |
94 | /api/complete/?pattern=${ADDRESS_PATTERN}
95 | GET
96 | true
97 | false
98 | true
99 | false
100 |
101 |
102 |
103 | false
104 |
105 |
106 |
107 |
108 |
109 |
110 | Connection
111 | keep-alive
112 |
113 |
114 | Accept-Language
115 | en-US,en;q=0.5
116 |
117 |
118 | Accept
119 | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
120 |
121 |
122 | User-Agent
123 | Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0
124 |
125 |
126 | Accept-Encoding
127 | gzip, deflate
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | ${SERVER}
138 |
139 |
140 |
141 | http
142 |
143 | /api/complete/?pattern=${HOUSE_PATTERN}
144 | GET
145 | true
146 | false
147 | true
148 | false
149 |
150 |
151 |
152 | false
153 |
154 |
155 |
156 |
157 |
158 |
159 | Connection
160 | keep-alive
161 |
162 |
163 | Accept-Language
164 | en-US,en;q=0.5
165 |
166 |
167 | Accept
168 | text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
169 |
170 |
171 | User-Agent
172 | Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0
173 |
174 |
175 | Accept-Encoding
176 | gzip, deflate
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 | ${SERVER}
187 |
188 |
189 |
190 |
191 |
192 | /api/validate/?pattern=${FULL_ADDRESS}
193 | GET
194 | false
195 | true
196 | true
197 | false
198 |
199 |
200 |
201 | false
202 |
203 |
204 |
205 |
206 | false
207 |
208 | saveConfig
209 |
210 |
211 | true
212 | true
213 | true
214 |
215 | true
216 | true
217 | true
218 | true
219 | false
220 | true
221 | true
222 | false
223 | false
224 | true
225 | false
226 | false
227 | false
228 | false
229 | false
230 | 0
231 | true
232 |
233 |
234 |
235 |
236 |
237 |
238 | false
239 |
240 | saveConfig
241 |
242 |
243 | true
244 | true
245 | true
246 |
247 | true
248 | true
249 | true
250 | true
251 | false
252 | true
253 | true
254 | false
255 | false
256 | true
257 | false
258 | false
259 | false
260 | false
261 | false
262 | 0
263 | true
264 |
265 |
266 |
267 |
268 |
269 |
270 | \t
271 |
272 | ./load.csv
273 | false
274 | true
275 | All threads
276 | false
277 | ADDRESS_ID,ADDRESS_PATTERN,HOUSE_PATTERN,FULL_ADDRESS
278 |
279 |
280 |
281 | 500
282 | 500.0
283 |
284 |
285 |
286 |
287 |
288 |
289 |
--------------------------------------------------------------------------------
/tests/resources/readerTest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/web/index.php:
--------------------------------------------------------------------------------
1 | getErrorLogger();
13 | FailureHandler::setup(function ($error) use ($logger) {
14 | (new TextResponse(500, 'Произошла ошибка сервера'))->send();
15 | $logger->error($error['message'], $error);
16 | exit;
17 | });
18 |
19 | try {
20 | $request = Request::createFromGlobals();
21 | $route = $container->getRouter()->route($request->getMethod(), $request->getUrlPath());
22 | $request->setOptions($route->vars);
23 |
24 | /** @var Response $response */
25 | $response = (new $route->class($container))->{$route->method}($request);
26 | } catch (RouteNotFoundException $e) {
27 | $response = new Response(404);
28 | } catch (BadRequestException $e) {
29 | $response = new Response(400, $e->getMessage());
30 | }
31 |
32 | $response->addHeader('Access-Control-Allow-Origin: *');
33 | $response->send();
34 |
--------------------------------------------------------------------------------