liquetsoft/fias-symfony
Symfony bundle for the Russian FIAS address database. Import and query official address data through Doctrine, build hierarchical address objects, and add reliable address autocomplete, lookup, and validation to your Symfony applications.
Install the bundle and its underlying library:
composer require liquetsoft/fias-symfony
Register the bundle (Symfony ≤5.3 uses config/bundles.php, newer apps use Flex auto-registration):
// config/bundles.php
return [
// ...
Liquetsoft\Fias\Symfony\FiasBundle::class => ['all' => true],
];
Publish a config at config/packages/fias.yaml. The bundle needs a database connection and a set of target tables. The typical shape is roughly:
fias:
# PDO/DBAL connection the importer writes into
pdo:
dsn: '%env(DATABASE_URL)%'
tables:
prefix: fias_ # tables like fias_address_object, fias_house...
# batch/memory knobs for large imports
import:
batch_size: 1000
Then look at the console command as your primary entry point:
php bin/console fias:import --help
Your first real use case should be a small, local import, not a full national import. Grab a single regional archive, point fias:import at it via --path, and verify the fias_* tables get populated. That confirms connection, table setup, and the parser pipeline before you commit hours of CPU to a full load.
Read the bundle's README for the exact option names of fias:import — they're stable but the underlying liquetsoft/fias command contract evolves with each FIAS/GAR format change.
Full vs. incremental import. The standard workflow is:
Scheduling is usually done via a Symfony scheduler or a cron entry:
# nightly delta
0 3 * * * php /var/www/app/bin/console fias:import --path=/data/fias/delta --version=<version-id> >> /var/log/fias.log 2>&1
Wiring into your own entities. The bundle owns its fias_* tables. Your application tables should store the FIAS identifier (GUID) as a foreign key and resolve display names through a repository or a denormalized "address" view you build yourself. Don't try to query fias_* directly from your domain code — wrap it:
class AddressResolver
{
public function __construct(private Connection $fiasConnection) {}
public function byGuid(string $guid): ?array
{
return $this->fiasConnection
->fetchAssociative('SELECT * FROM fias_address_object WHERE guid = ?', [$guid]);
}
}
Use a separate Doctrine entity manager / DBAL connection for fias_* if you can. Keeps Doctrine from trying to auto-map thousands of import tables and keeps migrations clean.
Custom storage. The library is built around a storage abstraction. If you need something other than the default DBAL storage (Elasticsearch index, an existing schema, a read replica), implement the storage interface and register it as a service the bundle picks up. This is the main extension point — reach for it before forking.
Integrating with commands. For anything non-trivial (filtering by region, post-processing, cache warming after import), write your own command that calls the importer service rather than shelling out to fias:import. It makes testing and error handling sane:
class ImportRegionCommand extends Command
{
public function __construct(private Importer $importer) { parent::__construct(); }
// ...
protected function execute(InputInterface $in, OutputInterface $out): int
{
$this->importer->setRegionFilter(...);
$this->importer->import();
return Command::SUCCESS;
}
}
FIAS is not GAR. Since 2021 ФНС migrated from the FIAS schema to ГАР (Государственный адресный реестр) with a different XML/DBF structure. Confirm which format the current liquetsoft/fias release expects before you build a pipeline on it — pointing it at a ГАР archive when it wants FIAS XML is the #1 source of silent no-op imports. Check the version you install, not the README on main.
Memory and timeouts dominate. Even a single regional archive can be hundreds of MB of XML. Set memory_limit in the console runtime (-d memory_limit=2G) or in the command options, and be aware that PHP's default max_execution_time is 0 in CLI but your web/fpm worker isn't. Never trigger an import from a controller.
Batch size matters more than it looks. Larger batch_size = fewer transactions but more memory; smaller = more round-trips. Tune it against your DB — MySQL/InnoDB with a large batch on a slow disk will look like a hang. Watch the import with -vvv and measure rows/sec on a small archive before scaling.
Indexes. The default schema ships with the indexes the library needs. If you add your own joins on fias_address_object, add a covering index on (region_code, ...) rather than querying guid alone — GUID lookups are fine but geographic filters over millions of rows need support.
Delta vs. full reset. Passing the wrong --version (or omitting it) can corrupt state. Keep a backup, or import into a fresh schema and swap. The fias_* tables are effectively a materialized cache of an external dataset — treat them as disposable.
Table prefix collisions. If your app or another bundle already uses a fias_* prefix, either change the prefix or give the importer its own database. Renaming afterwards means dropping and re-importing; you don't want to discover this after a 4-hour load.
Debugging. The import writes structured progress to the console output; log that. When an import "finishes" but row counts are off, diff the archive's Ver file against what's in the version table — mismatches usually mean a partially-extracted .rar/.zip, not a parser bug.
Ecosystem size. This package has ~27 stars and near-zero dependents. Plan for the possibility that a library update breaks the bundle; pin liquetsoft/fias and liquetsoft/fias-symfony to compatible versions in composer.json and keep a small integration test that runs an import against a fixture archive in CI. Consider whether wrapping the importer behind your own service (rather than calling fias:import directly) is worth it — it gives you a stable seam if you ever swap to direct ФНС API calls or a hosted address service.
Extension point for custom parsing. If you need to enrich rows at import time (e.g., normalizing house numbers, adding geocodes), the library's storage/processor interfaces are the right hook — subclass the processor and register yours in services.yaml with the bundle's tag. This is more stable across upgrades than subclassing the command.
How can I help you explore Laravel packages today?