chore: initial import for test contour

This commit is contained in:
sova-bootstrap
2026-05-27 19:36:32 +03:00
commit 166cdb148e
282 changed files with 84872 additions and 0 deletions
BIN
View File
Binary file not shown.
+20
View File
@@ -0,0 +1,20 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
BabDev\PagerfantaBundle\BabDevPagerfantaBundle::class => ['all' => true],
Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
];
+11
View File
@@ -0,0 +1,11 @@
framework:
asset_mapper:
# The paths to make available to the asset mapper.
paths:
- assets/
missing_import_mode: strict
when@prod:
framework:
asset_mapper:
missing_import_mode: warn
+10
View File
@@ -0,0 +1,10 @@
framework:
cache:
app: cache.adapter.redis
default_redis_provider: '%env(resolve:REDIS_URL)%'
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.app
+11
View File
@@ -0,0 +1,11 @@
# Enable stateless CSRF protection for forms and logins/logouts
framework:
form:
csrf_protection:
token_id: submit
csrf_protection:
stateless_token_ids:
- submit
- authenticate
- logout
+5
View File
@@ -0,0 +1,5 @@
when@dev:
debug:
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
# See the "server:dump" command to start a new server.
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
+14
View File
@@ -0,0 +1,14 @@
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler:
collect: true
only_exceptions: false
collect_serializer_data: true
# Уберите опцию profiling, она больше не существует
http_client:
# Профилирование теперь включается автоматически в dev среде
# при наличии установленного web_profiler
+92
View File
@@ -0,0 +1,92 @@
doctrine:
dbal:
connections:
default: # PostgreSQL
schema_filter: ~^(?!cron)~
url: '%env(resolve:DATABASE_URL)%'
logging: true
profiling: true
profiling_collect_backtrace: '%kernel.debug%'
use_savepoints: true
mysql: # Bitrix MySQL
url: '%env(resolve:DATABASE_BITRIX_URL)%'
driver: pdo_mysql
logging: true
profiling: true
cabinet: # Cabinet PostgreSQL
url: '%env(resolve:DATABASE_CABINET_URL)%'
logging: true
profiling: true
orm:
dql:
string_functions:
JSONB_CONTAINS: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Postgresql\JsonbContains
JSON_CONTAINS: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Postgresql\JsonContains
JSONB_EXISTS: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Postgresql\JsonbExists
JSONB_EXISTS_ANY: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Postgresql\JsonbExistsAny
JSONB_EXISTS_ALL: Scienta\DoctrineJsonFunctions\Query\AST\Functions\Postgresql\JsonbExistsAll
auto_generate_proxy_classes: false
metadata_cache_driver:
type: pool
pool: doctrine.system_cache_pool
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
enable_lazy_ghost_objects: true
report_fields_where_declared: true
validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
identity_generation_preferences:
Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
auto_mapping: true
mappings:
App:
type: attribute
is_bundle: false
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
controller_resolver:
auto_mapping: false
when@dev:
doctrine:
orm:
# In dev, avoid Redis-backed metadata/query cache: stale ClassMetadata (e.g. removed fields) breaks warmup.
metadata_cache_driver:
type: pool
pool: cache.system
query_cache_driver:
type: pool
pool: cache.system
when@test:
doctrine:
dbal:
# "TEST_TOKEN" is typically set by ParaTest
# dbname_suffix: '_test%env(default::TEST_TOKEN)%'
when@prod:
doctrine:
orm:
auto_generate_proxy_classes: false
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system
+6
View File
@@ -0,0 +1,6 @@
doctrine_migrations:
migrations_paths:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false
+31
View File
@@ -0,0 +1,31 @@
framework:
http_method_override: false
handle_all_throwables: true
secret: '%env(APP_SECRET)%'
session:
handler_id: null
cookie_secure: auto
cookie_samesite: lax
storage_factory_id: session.storage.factory.native
php_errors:
log: true
http_client:
default_options:
max_duration: 30
#esi: false
#fragments: false
when@dev:
framework:
php_errors:
throw: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file
@@ -0,0 +1,5 @@
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 31536000
+2
View File
@@ -0,0 +1,2 @@
framework:
lock: '%env(LOCK_DSN)%'
+3
View File
@@ -0,0 +1,3 @@
framework:
mailer:
dsn: '%env(MAILER_DSN)%'
+24
View File
@@ -0,0 +1,24 @@
framework:
messenger:
enabled: true
failure_transport: failed
transports:
sync: 'sync://'
failed: 'doctrine://default?queue_name=failed'
scheduler_default:
dsn: '%env(resolve:MESSENGER_TRANSPORT_DSN)%'
options:
queue_name: scheduler_default
routing:
Symfony\Component\Scheduler\Messenger\SchedulerTransport: scheduler_default
App\Message\GetScheduleMessage: sync
App\Message\GetSpecialistPictureMessage: sync
App\Message\GetAnonymousReserveRequestMessage: sync
# when@test:
# framework:
# messenger:
# transports:
# # replace with your transport name here (e.g., my_transport: 'in-memory://')
# # For more Messenger testing tools, see https://github.com/zenstruck/messenger-test
# async: 'in-memory://'
+76
View File
@@ -0,0 +1,76 @@
monolog:
channels:
- infoclinica
- deprecation
- bitrix
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event", "!http_client"]
http_client:
type: stream
path: "%kernel.logs_dir%/http_client.log"
level: debug
channels: ["http_client"]
messenger:
type: stream
path: "%kernel.logs_dir%/messenger.log"
level: debug
channels: ["messenger"]
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
infoclinica:
type: rotating_file
path: "%kernel.logs_dir%/infoclinica-%kernel.environment%.log"
formatter: monolog.formatter.json
channels: ["infoclinica"]
bitrix:
type: rotating_file
path: "%kernel.logs_dir%/bitrix-%kernel.environment%.log"
formatter: monolog.formatter.json
channels: ["bitrix"]
when@dev:
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event", "!doctrine"]
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
infoclinica:
type: rotating_file
path: "%kernel.logs_dir%/infoclinica-%kernel.environment%.log"
formatter: monolog.formatter.json
channels: ["infoclinica"]
when@prod:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
path: "%kernel.logs_dir%/%kernel.environment%.log"
excluded_http_codes: [404, 405]
buffer_size: 50
formatter: monolog.formatter.json
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
console:
type: rotating_file
path: "%kernel.logs_dir%/console-%kernel.environment%.log"
max_files: 7
level: debug
channels: ["!event", "!doctrine"]
formatter: monolog.formatter.json
+26
View File
@@ -0,0 +1,26 @@
nelmio_api_doc:
documentation:
servers:
- url: https://api.sovamed.ru/
description: Public API - sovamed
- url: https://api.wmtmed.ru/
description: Public API - wmtmed
info:
title: Public API
description: Справочник методов доступных в Public API
version: 1.0.0
areas:
path_patterns: [
'^/filial/list$',
'^/department/list$',
'^/specialist/list$',
'^/specialist/schedule$',
'^/pricelist/list$',
'^/pricelist/department$',
'^/news($|/)',
'^/promo($|/)',
'^/disease($|/)',
'^/medical-center($|/)',
'^/article($|/)',
'^/site-services($|/)'
]
+12
View File
@@ -0,0 +1,12 @@
nelmio_cors:
defaults:
origin_regex: true
allow_credentials: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
allow_headers: ['Content-Type', 'Authorization']
expose_headers: ['Link']
max_age: 3600
skip_same_as_origin: true
paths:
'^/': ~
+12
View File
@@ -0,0 +1,12 @@
framework:
notifier:
chatter_transports:
texter_transports:
channel_policy:
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
urgent: ['email']
high: ['email']
medium: ['email']
low: ['email']
admin_recipients:
- { email: admin@example.com }
+3
View File
@@ -0,0 +1,3 @@
framework:
property_info:
with_constructor_extractor: true
+11
View File
@@ -0,0 +1,11 @@
framework:
router:
strict_requirements: false
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
#default_uri: http://localhost
when@prod:
framework:
router:
strict_requirements: null
+3
View File
@@ -0,0 +1,3 @@
framework:
scheduler:
enabled: false
+38
View File
@@ -0,0 +1,38 @@
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
api:
pattern: ^/
stateless: true
provider: app_user_provider
jwt: ~
main:
lazy: true
provider: app_user_provider
login_throttling:
max_attempts: 3
interval: '15 minutes'
logout:
path: user_logout
access_control:
# - { path: ^/api/auth, roles: PUBLIC_ACCESS }
# - { path: ^/api, roles: ROLE_USER }
when@test:
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4
time_cost: 3
memory_cost: 10
+5
View File
@@ -0,0 +1,5 @@
framework:
serializer:
enabled: true
default_context:
date_format: 'Y-m-d'
+5
View File
@@ -0,0 +1,5 @@
framework:
default_locale: ru
translator:
default_path: '%kernel.project_dir%/translations'
providers:
+6
View File
@@ -0,0 +1,6 @@
twig:
file_name_pattern: '*.twig'
when@test:
twig:
strict_variables: true
+4
View File
@@ -0,0 +1,4 @@
# Enable stateless CSRF protection for forms and logins/logouts
framework:
csrf_protection:
check_header: true
+12
View File
@@ -0,0 +1,12 @@
framework:
validation:
email_validation_mode: html5
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []
when@test:
framework:
validation:
not_compromised_password: false
+13
View File
@@ -0,0 +1,13 @@
when@dev:
web_profiler:
toolbar: true
framework:
profiler:
collect_serializer_data: true
when@test:
framework:
profiler:
collect: false
collect_serializer_data: true
+5
View File
@@ -0,0 +1,5 @@
<?php
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}
+1891
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
controllers:
resource:
path: ../src/Controller/
namespace: App\Controller
type: attribute
+4
View File
@@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error
+12
View File
@@ -0,0 +1,12 @@
# Expose your documentation as JSON swagger compliant
app.swagger:
path: /api/doc.json
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger }
## Requires the Asset component and the Twig bundle
## $ composer require twig asset
app.swagger_ui:
path: docs
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
+3
View File
@@ -0,0 +1,3 @@
_security_logout:
resource: security.route_loader.logout
type: service
+8
View File
@@ -0,0 +1,8 @@
when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
prefix: /_profiler
+12
View File
@@ -0,0 +1,12 @@
<?php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return function (ContainerConfigurator $container): void {
$stubMode = $_ENV['INTEGRATIONS_STUB_MODE'] ?? $_SERVER['INTEGRATIONS_STUB_MODE'] ?? 'false';
$appEnv = $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? 'prod';
if (filter_var($stubMode, FILTER_VALIDATE_BOOL) || in_array($appEnv, ['dev', 'test'], true)) {
$container->import('./services_stub.yaml');
}
};
+232
View File
@@ -0,0 +1,232 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
app.timezone: 'Europe/Moscow'
upload_directory: '%kernel.project_dir%/public/uploads'
api.baseurl: '%env(string:API_BASE_URL)%'
api.public_url: '%env(default:api_base_url_default:API_PUBLIC_URL)%'
api_base_url_default: '%env(API_BASE_URL)%'
env(WIDGET_API_URL): ''
widget_api_url: '%env(default:mis_url_default:WIDGET_API_URL)%'
mis_url_default: '%env(MIS_URL)%'
mailer_from_email: 'noreply@sova.clinic'
mailer_from_name: 'Sova Clinic'
mailer_access_token: ''
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
Psr\Log\LoggerInterface: '@logger'
App\MessageHandler\SchedulerDefaultMessageHandler:
tags: ['monolog.logger']
# arguments:
# $application: '@console.messenger.application'
App\Serializer\Normalizer\SpecialistNormalizer:
public: true
tags: [serializer.normalizer]
App\Serializer\Normalizer\StockNormalizer:
public: true
tags: [serializer.normalizer]
App\Serializer\Normalizer\SpecialistDocsNormalizer:
public: true
tags: [serializer.normalizer]
App\Service\FileUploader\FileUploaderService:
tags: ['monolog.logger']
public: true
arguments:
$targetDirectory: '%upload_directory%'
App\EventListener\JsonExceptionHandler:
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
App\Service\Translite\Interfaces\TransliteServiceInterface:
alias: App\Service\Translite\TransliteService
App\Command\UploadFilialsCommand:
arguments:
$widgetApiUrl: '%widget_api_url%'
tags: ['console.command']
App\Command\UploadDoctorsCommand:
tags: ['console.command']
App\Command\UploadDepartmentsCommand:
tags: ['console.command']
App\Command\UploadPriceDepCommand:
arguments:
$widgetApiUrl: '%widget_api_url%'
tags: ['console.command']
App\Command\UploadPriceCommand:
arguments:
$widgetApiUrl: '%widget_api_url%'
tags: ['console.command']
App\Command\BitrixUpdateDoctorsCommand:
arguments:
$logger: '@logger'
$entityManager: '@doctrine.orm.entity_manager'
$bitrixService: '@App\Service\Bitrix\BitrixService'
tags: ['console.command']
App\Command\BitrixUpdateReviewsCommand:
arguments:
$logger: '@logger'
$entityManager: '@doctrine.orm.entity_manager'
$bitrixService: '@App\Service\Bitrix\BitrixService'
tags: ['console.command']
App\Service\Crypt\AESCryptService:
arguments:
$secretKey: '%env(string:AES_SECRET_KEY)%'
$cipher: '%env(string:AES_CIPHER_METHOD)%'
App\Service\Client\AbstractHttpClientService:
abstract: true
public: false
arguments:
$userAgent: '%env(string:API_CLIENT)%'
$baseUrl: '@api.baseurl'
App\Service\Client\CalltouchClientService:
parent: App\Service\Client\AbstractHttpClientService
arguments:
$userAgent: '%env(string:API_CLIENT)%'
$baseUrl: '%env(string:CT_URL)%'
$params: '%env(string:CT_PARAMS)%'
App\Service\Client\BitrixClientService:
parent: App\Service\Client\AbstractHttpClientService
arguments:
$userAgent: '%env(string:API_CLIENT)%'
$baseUrl: '%env(string:BITRIX_URL)%'
App\Service\Client\InfoclinicaClientService:
parent: App\Service\Client\AbstractHttpClientService
arguments:
$userAgent: '%env(string:API_CLIENT)%'
$baseUrl: '%env(string:MIS_URL)%'
App\Service\Client\SmartCaptchaClientService:
parent: App\Service\Client\AbstractHttpClientService
arguments:
$userAgent: '%env(string:API_CLIENT)%'
$baseUrl: '%env(string:SMARTCAPTCHA_URL)%'
$secret: '%env(string:SMARTCAPTCHA_KEY)%'
App\Service\Client\Sms4bClientService:
parent: App\Service\Client\AbstractHttpClientService
arguments:
$userAgent: '%env(string:API_CLIENT)%'
$baseUrl: '%env(string:SMS4B_URL)%'
$token: '%env(string:SMS4B_TOKEN)%'
$sender: '%env(string:SMS4B_SENDER)%'
App\Service\Client\SmsruClientService:
parent: App\Service\Client\AbstractHttpClientService
arguments:
$userAgent: '%env(string:API_CLIENT)%'
$baseUrl: '%env(string:SMSRU_URL)%'
$token: '%env(string:SMSRU_TOKEN)%'
$sender: '%env(string:SMSRU_SENDER)%'
App\Service\Client\Interfaces\CalltouchClientServiceInterface:
alias: App\Service\Client\CalltouchClientService
App\Service\Client\Interfaces\SmartCaptchaClientServiceInterface:
alias: App\Service\Client\SmartCaptchaClientService
App\Service\Client\Interfaces\SmsClientServiceInterface:
alias: App\Service\Client\SmsruClientService
App\Service\Bitrix\BitrixService:
public: true
arguments:
$connection: '@doctrine.dbal.mysql_connection'
App\Service\PriceList\PriceListService:
arguments:
$priceListRepository: '@App\Repository\PriceListRepository'
App\Service\Location\LocationService:
arguments:
$locationRepository: '@App\Repository\LocationRepository'
App\Service\Specialist\SpecialistService:
arguments:
$messageBus: '@messenger.default_bus'
$specialistRepository: '@App\Repository\SpecialistRepository'
App\Service\Filial\FilialService:
arguments:
$filialRepository: '@App\Repository\FilialRepository'
App\Service\XmlFeedGenerator\XmlFeedGeneratorService:
arguments:
$priceListService: '@App\Service\PriceList\PriceListService'
$departmentService: '@App\Service\Department\DepartmentService'
$specialistService: '@App\Service\Specialist\SpecialistService'
$locationService: '@App\Service\Location\LocationService'
$filialService: '@App\Service\Filial\FilialService'
$apiPublicUrl: '%api.public_url%'
App\Service\XmlFeedGenerator\XmlFeedGeneratorV1Service:
arguments:
$priceListService: '@App\Service\PriceList\PriceListService'
$specialistService: '@App\Service\Specialist\SpecialistService'
$helperService: '@App\Service\Helper\HelperService'
$connection: '@doctrine.dbal.default_connection'
$logger: '@logger'
$apiPublicUrl: '%api.public_url%'
App\Service\ScheduleCache\ScheduleCacheService:
arguments:
$logger: '@logger'
App\Service\ErrorHandler\ScheduleErrorHandlerService:
arguments:
$logger: '@logger'
App\Service\Performance\PerformanceTrackerService: ~
App\Service\Mail\SendMailService:
arguments:
$fromEmail: '%mailer_from_email%'
$fromName: '%mailer_from_name%'
App\Service\Mail\SendMailConfig:
arguments:
$accessToken: '%env(default:mailer_access_token:MAILER_ACCESS_TOKEN)%'
App\MessageHandler\GetScheduleMessageHandler:
arguments:
$clientService: '@App\Service\Client\InfoclinicaClientService'
$cacheService: '@App\Service\ScheduleCache\ScheduleCacheService'
$errorHandler: '@App\Service\ErrorHandler\ScheduleErrorHandlerService'
$performanceTracker: '@App\Service\Performance\PerformanceTrackerService'
tags:
- { name: messenger.message_handler }
+13
View File
@@ -0,0 +1,13 @@
services:
App\Service\Client\Stub\NoopSmsClientService: ~
App\Service\Client\Stub\NoopCalltouchClientService: ~
App\Service\Client\Stub\AlwaysValidSmartCaptchaClientService: ~
App\Service\Client\Interfaces\SmsClientServiceInterface:
alias: App\Service\Client\Stub\NoopSmsClientService
App\Service\Client\Interfaces\CalltouchClientServiceInterface:
alias: App\Service\Client\Stub\NoopCalltouchClientService
App\Service\Client\Interfaces\SmartCaptchaClientServiceInterface:
alias: App\Service\Client\Stub\AlwaysValidSmartCaptchaClientService