Multiple, dynamic database connections in Symfony2 - symfony

I have a Symfony2 application that I want to make multi-tenant by the use of one database pr tenant (some don't consider this to be multi-tenancy, but that's not really the point).
The documentation describes how to accomplish this. However, I want to be able to create tenants dynamically, and writing the new database connection details (and entity managers) to the config.yml file directly seems messy. I would rather have a separate database which holds the tenants and their connections, and then select the proper connection/em based on an identifier (fetched, for instance, from a the subdomain of the app - clientname.app.com).
Using this approach I should be able to accomplish this, but will at the same time probably break the ability to specify the database connection and/or entity manager when running the command line commands for updating database schemas and the likes.
Provided that what I want to do make sense, is there a clever way to achieve this?

I set ours up with a static database to handle login and tenancy information and a secondary database to hold user data
app/config/config.yml:
doctrine:
dbal:
default_connection: default
connections:
default:
driver: "%database_driver%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
tenantdb:
driver: "%database_driver%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
MyCoreBundle: ~
tenantdb:
connection: tenantdb
mappings:
MyAppBundle: ~
And then, in controllers, instead of
$something = $this->getDoctrine()
->getManager()
->getRepository('MyAppBundle:Thing')
->findAll();
we did:
$something = $this->getDoctrine()
->getManager('tenantdb')
->getRepository('MyAppBundle:Thing', 'tenantdb')
->findAll();
which you can find details of here:
http://symfony.com/doc/current/cookbook/doctrine/multiple_entity_managers.html
Then, based on Symfony2, Dynamic DB Connection/Early override of Doctrine Service
I set up a service to switch databases based on the subdomain of the request (e.g. tenant1.example.com tenant2.example.com)
src/MyCoreBundle/Resources/config/services.yml:
services:
my.database_switcher:
class: MyCoreBundle\EventListener\DatabaseSwitcherEventListener
arguments: [#request, #doctrine.dbal.tenantdb_connection]
scope: request
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
MyCoreBundle\EventListener\DatabaseSwitcherEventListener.php
namespace MyCoreBundle\EventListener;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\DBAL\Connection;
class DatabaseSwitcherEventListener {
private $request;
private $connection;
public function __construct(Request $request, Connection $connection) {
$this->request = $request;
$this->connection = $connection;
}
public function onKernelRequest() {
$connection = $this->connection;
if (! $connection->isConnected()) {
$params = $this->connection->getParams();
$subdomain = __GET_SUBDOMAIN__();
$oldname = preg_replace (
"/_tenant_$subdomain|_template/",
'',
$params['dbname']
);
$params['dbname'] = $oldname . ($subdomain ? "_tenant_$subdomain"
: "_template");
$connection->__construct(
$params,
$connection->getDriver(),
$connection->getConfiguration(),
$connection->getEventManager()
);
$connection->connect();
}
}
}
For convenience sake, we have an "extra" tenant database called XXX_template which system admins connect to when making global changes.
The plan is that this database is copied to tenant databases on tenant create.

Create a service that produces your custom entity managers based on the user's credential.
$this->get('my.db.service')->getEmForUser('bob');
Then your service would be something like this
class EntityManagerService
{
function __construct($doctrine)
{ ... }
function getEmForUser($user)
{
//look up Bob's connection details in your connection db
//and get them using the globally configured entity manager
//create Entity Manager using bob's config
return $em.
}
This is the most reusable way to do things and it fits with the Dependency Injection pattern Symfony2 uses.
You'll want to return instances of this class
https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/EntityManager.php

Don't know if I gresped the extent of your question, but I connect to different databases using this:
$connectionFactory = $this->container->get('doctrine.dbal.connection_factory');
$conn = $connectionFactory->createConnection(array(
'driver' => 'pdo_mysql',
'user' => 'mattias',
'password' => 'nkjhnjknj',
'host' => 'fs1.uuyh.se',
'dbname' => 'csmedia',
));
return $conn;

We had the same problem on our project.
We created a new service in vendor bundle named : connectionManager.
The service is a multiton which return an entityManager by parameters.

Related

How to hide Api-plaform Docs from Nelmio Docs

I hope someone could help me to use Api-platorm with Nelmio.
I use Api-plaform and Nelmio. I need to hide the Api-platform docs from Nelmio.
I need to have 3 routes:
/internal -> API-Platform Docs
/external -> NELMIO-Docs
/admin -> NELMIO-Docs
My config of Nelmio:
# config/packages/nelmio_api_doc.yaml
nelmio_api_doc:
documentation:
info:
title: ...
description: ...
version: 0.2.0
areas: # to filter documented areas
default:
path_patterns: [ ^/external ]
external:
path_patterns: [ ^/external ]
admin:
path_patterns: [ ^/admin ]
My config of Nelmio (routes):
# config/routes/nelmio_api_doc.yaml
app.swagger:
path: /{area}/json
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger, area: default }
app.swagger_ui:
path: /{area}
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui, area: default }
My config of API-Platform:
# config/routes/api_platform.yaml
api_platform:
resource: .
type: api_platform
prefix: /internal/
But if I go to http://localhost/external or http://localhost/admin I see always not only needed routes, but also the routes from API-Platform:
I know this question is old by now, but I am facing the same situation and I found a workaround that may help some one, so I am posting it.
API Platform lets you decorate Swagger so you can customize the final documentation output. I took advantage of this to get rid of the entire api platform documentation when not asking for it.
<?php
namespace App\Swagger;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
final class SwaggerDecorator implements NormalizerInterface
{
private $decorated;
private $requestStack;
public function __construct(NormalizerInterface $decorated, RequestStack $requestStack)
{
$this->decorated = $decorated;
$this->requestStack = $requestStack;
}
public function normalize($object, $format = null, array $context = [])
{
if ('/internal/docs' !== $this->requestStack->getCurrentRequest()->getPathInfo()) {
// request is not for internal docs (maybe it is for external or admin one) so get rid of api platform docs
return null;
}
$docs = $this->decorated->normalize($object, $format, $context);
// here you can customize documentation
return $docs;
}
public function supportsNormalization($data, $format = null)
{
return $this->decorated->supportsNormalization($data, $format);
}
}
I hope this helps someone, happy coding!
UPDATE
In order to enable that decorator, you must declare it as so in your services file:
App\Swagger\SwaggerDecorator:
decorates: 'api_platform.swagger.normalizer.api_gateway'
arguments: [ '#App\Swagger\SwaggerDecorator.inner' ]
autoconfigure: false
Then, in the class itself, replace '/internal/docs' with the actual URL you are using for your API Platform documentation.
Hope this helps.
On your nelmio configuration yaml file, use a regex to exclude the docs. For instance, for excluding the /external/doc you should:
external:
path_patterns: [ ^/external(?!/doc$) ]

Can't send emails using SwiftMailer (Symfony4): wrong SMTP parameters?

I'm coding a small website using Symfony 4.
There's a simple contact form that is supposed to send emails, seemed easy until I realize I can't configure it ^^
I've followed Symfony doc instructions from here:
[https://symfony.com/doc/current/email.html][1]
Meaning mainly having the Swift mailer dependy:
composer require mailer
And my Controller looks like this:
/**
* #Route("/contact", name="contact_handling")
*/
public function contactHandler(Request $request, \Swift_Mailer $mailer)
{
$contact = new Contact();
$form = $this->createForm(ContactType::class, $contact);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$message = (new \Swift_Message('Hello Email'))
->setFrom('send#example.com')
->setTo('myownemail#hotmail.fr')
->setBody("plopppp mail")
;
$mailer->send($message);
$contact = $form->getData();
return $this->render('home.html.twig', array(
'form' => $form->createView(),
));
}
return $this->render('home.html.twig', array(
'form' => $form->createView(),
));
No matter what it does next in the return (I'm also trying to figure out how to avoid the page reload and just return an answer like "OK" or "not OK" then my Javascript pops up a message)
For dev environment (I'll have the same problem when moving to prod by the way),
my .env conf file has this parameter:
MAILER_URL=smtp://smtp-mail.outlook.com:587?encryption=tls&username=myownemail#hotmail.fr&password=mypwd
while trying to use my own email account, which could be my problem
Smtp address, port and encryption are some parameters found on website like this one:
[https://www.lifewire.com/what-are-the-outlook-com-smtp-server-settings-1170671][1]
Of course, I've never received anything.
If someone familiar whith this could help me it would be very nice :)
I'm using Windows10 + PhpStorm + php7 + built-in symfony server
Thanks!
Edit:
Output from: php bin/console debug:config SwiftmailerBundle
swiftmailer:
default_mailer: default
mailers:
default:
url: '%env(MAILER_URL)%'
spool:
type: memory
path: 'C:\www\h4h\var\cache\dev/swiftmailer/spool'
id: null
transport: smtp
command: '/usr/sbin/sendmail -bs'
username: null
password: null
host: localhost
port: null
timeout: 30
source_ip: null
local_domain: null
encryption: null
auth_mode: null
delivery_addresses: { }
logging: true
delivery_whitelist: { }
Edit 2:
I've just tried putting the conf in the config/packages/swiftmailer.yaml without more success, but at least, php bin/console debug:config SwiftmailerBundle outputs the correct info:
swiftmailer:
transport: gmail
username: mylogin
password: mypwd
host: smtp.gmail.com
port: 587
encryption: ssl
auth_mode: login
spool:
type: file
path: '%kernel.cache_dir%/swiftmailer/spool'
sender_address: ~
antiflood:
threshold: 99
sleep: 0
delivery_addresses: []
disable_delivery: ~
logging: '%kernel.debug%'

Disable foreign keys in Doctrine Migrations

I'm using NDBCLUSTER engine in a MySQL DB. I've added a class for wrapping Connection and adding the engine option:
namespace AppBundle\DBAL;
use Doctrine\DBAL\Connection as BaseConnection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Configuration;
use Doctrine\Common\EventManager;
class Connection extends BaseConnection
{
public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null)
{
if (isset($params['driverOptions']['engine'])) {
$params['defaultTableOptions']['engine'] = $params['driverOptions']['engine'];
}
return parent::__construct($params, $driver, $config, $eventManager);
}
}
I define the engine option in the config.yml file:
doctrine:
dbal:
default_connection: default
connections:
default:
driver: "%database_driver%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
wrapper_class: AppBundle\DBAL\Connection
options:
engine: NDBCLUSTER
orm:
auto_generate_proxy_classes: "%kernel.debug%"
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
AppBundle: ~
Then, if I execute php app/console doctrine:migrations:diff the NDBCLUSTER engine is added to the CREATE statements. However, the foreign keys are added too, and NDBCLUSTER does not accept foreign keys. Is there any way to disable the foreign keys (I mean, not writing them in the migration files)?
I disabled the foreign keys by implementing my own platform_service for that connection:
namespace AcmeBundle\Doctrine;
use Doctrine\DBAL\Platforms\MySQL57Platform;
class CustomMySQLPlatform extends MySQL57Platform
{
public function supportsForeignKeyConstraints()
{
return false;
}
public function supportsForeignKeyOnUpdate()
{
return false;
}
}
Service definition in services.yml:
acme.dbal.service.custom_mysql_platform:
class: AcmeBundle\Doctrine\CustomMySQLPlatform
Doctrine DBAL definition in config.yml:
doctrine:
dbal:
connections:
default:
....
platform_service: acme.dbal.service.custom_mysql_platform

onPush() function not getting hit after push from controller using ZeroMQ in Guinness of Symfony/Websocket bundle in symfony2

Config.yml
gos_web_socket:
topics:
- #app.real_time.subscribe
- #app.real_time.push
server:
port: 8000 #The port the socket server will listen on
host: localhost #The host ip to bind to
router:
resources:
- #AppBundle/Resources/config/pubsub/routing.yml
client:
firewall: main #can be an array of firewalls
session_handler: #session.handler.pdo
pushers:
zmq:
default: true
host: 127.0.0.1
port: 8888
persistent: true
protocol: tcp
pubsub/routing.yml
real_time_push:
channel: all/user
handler:
callback: 'app.real_time.push' #related to the getName() of your topic
services.yml
app.real_time.subscribe:
class: AppBundle\RealTime\Subscribe
arguments: [ #gos_web_socket.websocket.client_manipulator ]
app.real_time.push:
class: AppBundle\RealTime\Push
arguments: [ #gos_web_socket.websocket.client_manipulator ]
The following function is getting hit when client subscribes on this topic. I can see the text in the console.
Subscribe Class onSubscribe() : Subscribe.php
public function onSubscribe(ConnectionInterface $connection, Topic $topic, WampRequest $request)
{
var_dump("This user is subscribed");
}
But when pushed via zmq from controller the onPush() function never got hit. I have already implemented TopicInterdace and PushableTopicInterface in Push.php.
Push Class onPush() : Push.php
public function onPush(Topic $topic, WampRequest $request, $data, $provider)
{
var_dump("Helloooooooooooooooooo0000000000");
}
Please can any one tell me where did I do wrong. Thanks in advance. Here is the controller
$pusher = $this->container->get('gos_web_socket.zmq.pusher');
//push(data, route_name, route_arguments)
$pusher->push(['my_data' => 'data'], 'real_time_push', ['username' => 'sujit']);

Can I put doctrine dbal and orm config in a vendor bundle?

I'm just asking a question, that's can we put all doctrine (orm and dbal) configuration in the config.yml file which is localized inside a vendor bundle ? And how to access to this entity manager outside of this bundle ?
Thanks per advance
EDIT1:
I think it's possible by using the PrependExtensionInterface like that :
//vendor/XXXXBundle/DependencyInjection/XXXXExtension.php
class XXXXExtension extends Extension implements PrependExtensionInterface
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}
public function prepend(ContainerBuilder $container)
{
$configFile = __DIR__ . '/../Resources/config/config.yml';
$content = Yaml::parse(
file_get_contents($configFile)
);
$container->prependExtensionConfig('', $content);
}
}
# vendor/XXXXBundle/Resources/config/config.yml
imports:
- { resource: parameters.yml }
doctrine:
dbal:
default_connection: default_conn
connections:
default:
driver: "%XXXXBundle.database_driver%"
host: "%XXXXBundle.database_host%"
port: "%XXXXBundle.database_port%"
dbname: "%XXXXBundle.database_name%"
user: "%XXXXBundle.database_user%"
password: "%XXXXBundle.database_password%"
charset: UTF8
orm:
default_entity_manager: default_em
auto_generate_proxy_classes: "%kernel.debug%"
auto_mapping: true
entity_managers:
default_em:
connection: default_conn
mappings:
XXXXBundle:
type: annotation
is_bundle: false
dir: %kernel.root_dir%/../vendor/awcb/Aw/Common/Model/XXXXBundle/Entity
prefix: Aw\Common\Model\XXXXBundle\Entity
alias: XXXXBundle
# vendor/YYYYBundle/Resources/config/services.yml
services:
common_business_profile:
class: Aw\Common\Business\ProfileBundle\Lib\ProfileLibrary
arguments: ["#doctrine.orm.default_em"] # already tried default_entity_manager and entity_manager
And I get the error :
The service "common_business_profile" has a dependency on a non-existent service "doctrine.orm.entity_manager".
You can note that a php app/console container:debug shows that there is no doctrine.* that's loaded...
Can you help me more please ? :)
Regards,

Resources