Symfony4: How to toggle entities using dotenv and multiple Entity Manager? - doctrine

How it is possible through dotenv select different entity path with same name of entities.
Originally, We had application in Symfony 3 for people meetings on events of our organization. Then we decided to offer this application to our partners.
One of the partners asked us to customize the application for them with their data and specifications. We basically created a new instance of Symfony application with copy of database tables (with different prefix), changes in Entities to reflect new db table names, and some translation text changes.
It seems that other partners will follow this trend of customized instances.
Therefore, I am trying to update core application to Symfony 4 and I am trying to use multiple Entity Managers and dotenv to differentiate between partners database tables, as described bellow.
In a nutshell, I am trying to use multiple Entity Manager to switch db table names by prefix.
.env
###> doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Configure your db driver and server_version in config/packages/doctrine.yaml
DATABASE_URL=oci8://user:pass#127.0.0.1:1521/XE
EM_TYPE=OpenAccess
###< doctrine/doctrine-bundle ###
Only in security.yaml working env good
security:
encoders:
App\Entity\%env(EM_TYPE)%\Osoba:
providers:
our_db_provider:
entity:
class: App\Entity\%env(EM_TYPE)%\Osoba
property: username
When i tried get %env(EM_TYPE)% in default_entity_manager, then give error You have requested a non-existent service "doctrine.orm.%env(EM_TYPE)%_entity_manager". doctrine.yaml
parameters:
env(DATABASE_URL): ''
doctrine:
dbal:
default_connection: '%env(EM_TYPE)%'
connections:
MeetingTool:
driver: 'oci8'
charset: UTF8
schema_filter: /^MT_/
url: '%env(resolve:DATABASE_URL)%'
OpenAccess:
driver: 'oci8'
charset: UTF8
schema_filter: /^OA6_/
url: '%env(resolve:DATABASE_URL)%'
orm:
auto_generate_proxy_classes: true
default_entity_manager: '%env(EM_TYPE)%'
entity_managers:
MeetingTool:
connection: MeetingTool
mappings:
Main:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity/MeetingTool'
prefix: 'App\Entity\MeetingTool'
alias: App2
OpenAccess:
connection: OpenAccess
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity/OpenAccess'
prefix: 'App\Entity\OpenAccess'
alias: OpenAccess
and biggest problem is use, how i targeting right entity here? for example loginController.php
<?php
namespace App\Controller;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\OpenAccess\LogPrihlaseni;
//use App\Entity\MeetingTool\LogPrihlaseni;
class LoginController extends AbstractController {
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function logPrihlaseni() {
$log = new LogPrihlaseni();

Related

How to connect to remote oracle database using typeorm in nestjs?

I was wondering how to connect to remote oracle database from nestjs using typeorm.
I installed typeorm and oracle package using following command.
npm i --save #nestjs/typeorm typeorm oracle
npm install oracledb --save
and then tried configuring in app.module.ts using TypeOrmModule.forRoot but it was not succesfull.
Here are my configuration settings.
TypeOrmModule.forRoot({
type: 'oracle',
host: 'ip of hostname',
port: port number,
username: 'username',
password: 'password',
serviceName: 'servicename',
synchronize: false,
entities: []
})
Can anybody help me out what am I missing? Also would like to know how can I execute the query once this connection is succesfully? If any example that would be helpfull.
Got it.
one missing thing was database name.
Added
database: 'databasename' in above configuration and it worked.
But, still my question is how to use this connection in service to fetch/push the data from/to oracle databse?
If you provide a name in your connection details you should be able to refer to the database connection using that. Otherwise, if no name is provided I believe it assigns it the name 'default'.
Basically these are the steps you should perform to use the database connection: (examples below each)
Create a model - this is how TypeORM knows to create a table.
export class Photo {
id: number
name: string
description: string
filename: string
views: number
isPublished: boolean
}
Create an Entity. - this should match your model, with the appropriate decorators. At minimum you should have the #Entity() decorator before your class definition and #Column() before each field.
import { Entity, Column } from "typeorm"
#Entity()
export class Photo {
#Column()
id: number
#Column()
name: string
#Column()
description: string
#Column()
filename: string
#Column()
views: number
#Column()
isPublished: boolean
}
Create your data source - looks like you have already done this. But I would give it a name field and you will need to pass your entities into the entity array you have.
const AppDataSource = new DataSource({
type: "postgres",
name: "photos",
host: "localhost",
port: 5432,
username: "root",
password: "admin",
database: "test",
entities: [Photo],
synchronize: true,
logging: false,
})
Then you can use repositories to manage data in the database:
const photo = new Photo()
photo.name = "Me and Bears"
photo.description = "I am near polar bears"
photo.filename = "photo-with-bears.jpg"
photo.views = 1
photo.isPublished = true
const photoRepository = AppDataSource.getRepository(Photo)
await photoRepository.save(photo)
console.log("Photo has been saved")
const savedPhotos = await photoRepository.find()
console.log("All photos from the db: ", savedPhotos)
For more details I would spend some time reading through the typeORM website, all the examples I pulled were from there:
https://typeorm.io/

How to set namespace and item expiration for a symfony5 redis cachepool

I try to configure two cache pools in my Synfony5 app to use a certain namespace and set a default expiration date for the items. After trying for the umpteenth time the umteenth variation I get the feeling that my configuration is going in circles.
What I understood so far:
In the constructor of the RedisAdapter you can set the namespace and the default expiration time
In the createConnection method you set the url of your redis server.
However the constructor of the RedisAdapter seems to already need a redis client (= redis connection?)
RedisAdapter:
/**
* #param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient The redis client
* #param string $namespace The default namespace
* #param int $defaultLifetime The default lifetime
*/
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
{
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
}
How can I inject my namespaces and defaultLifetimes into the RedisAdapter?
What I tried so far:
cache.yaml:
framework:
cache:
pools:
cache.sap:
adapter: cache.adapter.redis
provider: app.service.puc_sap_redis_adapter
cache.pers:
adapter: cache.adapter.redis
provider: app.service.puc_pers_redis_adapter
services.yaml:
app.my_redis_adapter:
class: 'Redis'
factory: ['Symfony\Component\Cache\Adapter\RedisAdapter', 'createConnection']
arguments:
- 'redis://%env(string:REDIS_URL)%:%env(int:REDIS_PORT)%'
- { retry_interval: 2, timeout: 5 }
app.service.puc_sap_redis_adapter:
class: Symfony\Component\Cache\Adapter\RedisAdapter
arguments:
$redisClient: '#app.my_redis_adapter'
$namespace: 'sapData'
$defaultLifetime: '%env(SAP_CACHE_TIMEOUT)%'
app.service.puc_pers_redis_adapter:
class: Symfony\Component\Cache\Adapter\RedisAdapter
arguments:
$redisClient: '#app.my_redis_adapter'
$namespace: 'persData'
$defaultLifetime: '%env(CACHE_TIMEOUT)%'
This gets me the error message:
line: 62,
file: "/var/www/vendor/symfony/cache/Traits/RedisTrait.php",
message: "\"Symfony\\Component\\Cache\\Traits\\RedisTrait::init()\"
expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\\ClientInterface,
\"Symfony\\Component\\Cache\\Adapter\\RedisAdapter\" given."
How can I configure the namespaces and expiration time for my two cache pools?
After several days of blood, sweat and tears I leave this here so nobody else will have to experience this deep desperation.
This is how it works. You will need no extra class "just" this nifty cache.yaml in the folder for your environment:
framework:
cache:
pools:
cache.sap:
adapter: app.cache.adapter.sap_redis # custom namespace and item expiration defined there
provider: app.cache.custom_redis_provider # Which server connection should be used
cache.pers:
adapter: app.cache.adapter.pers_redis # custom namespace and item expiration defined there
provider: app.cache.custom_redis_provider # Which server connection should be used
services:
app.cache.custom_redis_provider: # this defines our connection to the redis server
class: \Redis
factory: ['Symfony\Component\Cache\Adapter\RedisAdapter', 'createConnection']
arguments:
- 'redis://%env(string:REDIS_URL)%:%env(int:REDIS_PORT)%' # this defines the url to the redis server. "redis" up front is mandatory
- { retry_interval: 2, timeout: 5 } # defines number of connection retries and connection timeout (not item expiration!)
app.cache.adapter.sap_redis: # here we pass namespace and expiration timeout into the constructor of the redis adapter
parent: 'cache.adapter.redis'
tags:
- { name: 'cache.pool', namespace: 'sapData', default_lifetime: '%env(int:SAP_CACHE_TIMEOUT)%' }
app.cache.adapter.pers_redis: # here we pass a different namespace and expiration timeout into the constructor of the redis adapter
parent: 'cache.adapter.redis'
tags:
- { name: 'cache.pool', namespace: 'persData', default_lifetime: '%env(int:CACHE_TIMEOUT)%' }
You can also set those parameters within the usual cache-pool configuration.
framework:
cache:
default_memcached_provider: 'memcached://localhost'
# could also replace with
# default_redis_provider: 'redis://localhost' # or '%env(REDIS_DSN)%'
pools:
# creates a "custom_thing.cache" service
# autowireable via "CacheInterface $customThingCache"
# uses the "app" cache configuration
custom_thing.cache:
adapter: cache.app
# creates a "my_cache_pool" service
# autowireable via "CacheInterface $myCachePool"
my_cache_pool:
adapter: cache.adapter.filesystem
# uses the default_memcached_provider from above
acme.cache:
adapter: cache.adapter.memcached
# control adapter's configuration - customised provider adaptor & DSN
foobar.cache:
adapter: cache.adapter.memcached
provider: 'memcached://user:password#example.com'
# uses the "foobar.cache" pool as its backend but controls
# the lifetime and (like all pools) has a separate cache namespace
short_cache:
adapter: foobar.cache
default_lifetime: 60
The page (linked above) goes on to say how to tag a service for a specific namespace, but the various configured pools already have one set by default:
Each pool manages a set of independent cache keys: keys from different pools never collide, even if they share the same backend. This is achieved by prefixing keys with a namespace that’s generated by hashing the name of the pool, the name of the compiled container class and a configurable seed that defaults to the project directory.

Doctrine with two Entitymanagers looks at wrong database

I did follow these rules of setting up a second connection and a second entity manager in doctrine. The "default" database is called revee and the "source" database reveesrc.
What works
When I dry-run a migration with this code in the postUp() method:
/** #var EntityManager $em */
$em = $this->container->get('doctrine.orm.entity_manager');
/** #var EntityManager $emSrc */
$emSrc = $this->container->get('doctrine.orm.source_entity_manager');
var_dump($emSrc->getConnection()->getDatabase());
$dates = $emSrc->getRepository('App:Dates')->findAll();
Weirdly, I get the database reveesrc written our correctly! Meaning that the mapping from the connection to the entity manager works just fine.
What doesn't work
However, the next line produces the error.
Base table or view not found: 1146 Table 'revee.dates' doesn't exist"
As dates was defined in the Entity folder attached to the second source entity manager I thought doctrine would know where to look for the table. What do I have to do to map the Entity to the other source database?
doctrine.yaml
parameters:
# Adds a fallback DATABASE_URL if the env var is not set.
# This allows you to run cache:warmup even if your
# environment variables are not available yet.
# You should not need to change this value.
env(DATABASE_URL): ''
doctrine:
dbal:
# configure these for your database server
default_connection: default
connections:
default:
url: '%env(resolve:DATABASE_URL)%'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
source:
url: '%env(resolve:DATABASE_URL_SOURCE)%'
driver: 'pdo_mysql'
server_version: '5.7'
charset: utf8mb4
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
orm:
default_entity_manager: default
auto_generate_proxy_classes: true
entity_managers:
default:
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
connection: default
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
source:
naming_strategy: doctrine.orm.naming_strategy.underscore
connection: source
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/EntitySrc'
prefix: 'App\EntitySrc'
alias: App
src\Entity\Dates.php (first lines)
<?php
namespace App\EntitySrc;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\DateSrcRepository")
*/
class Dates
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
src\Repository\DateSrcRepository.php
<?php
namespace App\Repository;
use App\EntitySrc\Dates;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
class DateSrcRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Dates::class);
}
If anybody experiences the same problem, I fixed it by specifying the table explicetly in the entity, prefixing it with the database. If anyone knows if that is not the correct way of solving it I would be interested.
<?php
namespace App\Entity\Src;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\DateSrcRepository")
* #ORM\Table(name="reveesrc.dates")
*/
class Dates
{

Doctrine migrations: create code for mysql and postgresql

I am using Doctrine ORM 2.6.1 in a Symfony 3.4.4 project.
Some of my instances work on a MySQL database, some on Postgresql, and a few installations even access a MicosoftSQL server. This works fine without any special changes to my project or entities, I only have to configure the corresponding connection parameters.
But: if I create migrations, only statements compatible with the current database connection are created in the migration file.
I develop with a postgres-conncection, so I only produce postgresql-statements, like:
class Version20180430083616 extends AbstractMigration
{
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('DELETE FROM document_category');
$this->addSql('DROP SEQUENCE document_category_id_seq CASCADE');
$this->addSql('DROP TABLE document_category');
}
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
//...
}
}
My Question: How can I tell the migrations bundle to create statements for each platform, like:
class Version20180430083616 extends AbstractMigration
{
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
if($this->connection->getDatabasePlatform()->getName() == 'postgresql'){
$this->addSql('DELETE FROM document');
$this->addSql('DELETE FROM document_category');
$this->addSql('DROP SEQUENCE document_category_id_seq CASCADE');
$this->addSql('DROP TABLE document_category');
} else if($this->connection->getDatabasePlatform()->getName() == 'mysql'){
...
} else if ($this->connection->getDatabasePlatform()->getName() == 'mssql') { // MicrosoftSQL ?
...
}
}
}
Edit:
So, I think the only solution to my problem is to define multiple database connections and entity managers, and to always create a distinct migration for each connection type. According to this article, I can define several connections as:
I found a doable solution:
inf config.yml I define one connection and one EntityManager per database type:
doctrine:
dbal:
default_connection: pgdb
connections:
pgdb:
driver: pdo_pgsql
host: db
port: 5432
name: pgdb
user: postgres
password: example
charset: utf8
mapping_types:
enum: string
mysql:
driver: pdo_mysql
host: mysqlhost
port: 3306
name: mydb
dbname: mydb
user: root
password: xxx
charset: utf8mb4
default_table_options:
collate: utf8mb4_unicode_ci
mapping_types:
enum: string
mssql:
driver: pdo_sqlsrv
host: mssqlhost
port: 1433
name: msdb
dbname: testdb
user: sa
password: xxx
charset: utf8
mapping_types:
enum: string
orm:
auto_generate_proxy_classes: false
proxy_dir: '%kernel.cache_dir%/doctrine/orm/Proxies'
proxy_namespace: Proxies
entity_managers:
default:
connection: pgdb
naming_strategy: doctrine.orm.naming_strategy.underscore
mappings:
AppBundle: ~
my:
connection: mydb
naming_strategy: doctrine.orm.naming_strategy.underscore
mappings:
AppBundle: ~
ms:
connection: msdb
naming_strategy: doctrine.orm.naming_strategy.underscore
mappings:
AppBundle: ~
Then, I can issue the diff-command 3 times instead of only once
$ bin/console doctrine:migrations:diff --em=default
$ bin/console doctrine:migrations:diff --em=my
$ bin/console doctrine:migrations:diff --em=ms
This creates three migrations each starting with a fence line:
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mssql', 'Migration can only be executed safely on \'mssql\'.');
in which I exchange abortIf by skipIf, such that the migration process is not aborted if the current migration if for a different database type, but just skipped:
$this->skipIf($this->connection->getDatabasePlatform()->getName() !== 'mssql', 'Migration can only be executed safely on \'mssql\'.');
I hope this helps somebody.

Work with different schemas in Doctrine

I am using doctrine2 with oracle. There are several schemas schema1 and schema2. When I create a form with the following content
// ....
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ....
$builder
->add('userPartner', 'entity', array(
'class' => 'SoftclubTopbyBundle:Party',
'property' => 'legalName',
'placeholder' => '',
'multiple' => true,
))
;
// ....
}
//...
the symfony throws me an exception:
MappingException in MappingException.php line 37:
The class 'Softclub\TopbyBundle\Entity\Nsi\NsiChainStore' was not found in
the chain configured namespaces Softclub\TopbyBundle\Entity\Topby
I have the following setting in the config.yml
entity_managers:
default:
connection: default
mappings:
SoftclubTopbyBundle: { type: yml, dir: Resources/config/doctrine/topby, prefix: Softclub\TopbyBundle\Entity\Topby }
nsi:
connection: nsi
mappings:
SoftclubTopbyBundle: { type: yml, dir: Resources/config/doctrine/nsi, prefix: Softclub\TopbyBundle\Entity\Nsi }
and also the following relationship between the two entities
Softclub\TopbyBundle\Entity\Topby\Party:
manyToOne:
chainStore:
targetEntity: Softclub\TopbyBundle\Entity\Nsi\NsiChainStore
cascade: { }
mappedBy: null
inversedBy: null
joinColumns:
CHAIN_STORE_ID:
referencedColumnName: ID
orphanRemoval: false
what can I do wrong?
Thank you all for the answers. As Matteo said, the entities were placed in a separate bundle. The problem was solved as follows
default:
connection: default
mappings:
SoftclubTopbyBundle: ~
SoftclubNsiBundle: ~
# for generate entities
topby:
connection: default
mappings:
SoftclubTopbyBundle: ~
nsi:
connection: nsi
mappings:
SoftclubNsiBundle: ~
You can not make doctrine relations over different connections. You can use event listener for that purpose.
For instance, one entity (say Note) has some property which is reference to another entity (say User) belonging to another entity manager (connection). The Note entity persists User's ID as foreign key.
Event listener is used to instantiate User object by using it's ID whenever the Note object is loaded (postLoad event).
http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html
Doctrine events:
http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#lifecycle-events
Regarding the form, put option entity manager with 'nsi' if 'userPartner' is mapped to that connection.
'em'=>'nsi'
Have not worked with Oracle, hope this helps.

Resources