I need to keep an archive database to an application in Symfony2.
In it I'll keep all records older than 90 days. I was thinking that I could use just one entity manager (because both databases are identical).
First of all, I'm not sure if this is the best approach/solution.
And, besides that, I don't know how to implement this idea (I've just found 2 entity managers for 2 databases).
I'm sorry if this is a dumb question, but I've been looking for some solution for it for 2 days now.
We use distinct em for saving history, works fine.
Code looks like this.
Somewhere in your config...
yourpath\app\config\parameters.yml
parameters:
database_driver: pdo_mysql
database_host: site1.ru
database_port: 3346
database_name: db1
database_user: roof
database_password: jump
database_history_driver: pdo_mysql
database_history_host: site2.ru
database_history_port: 10001
database_history_name: history
database_history_user: sea
database_history_password: deep
etc...
Somewhere in your history bundle...
/**
* We make history!
**/
class historyController extends Controller
{
public function showAction($historyId)
{
// get secondary manager
$emHistory = $this->getDoctrine()->getManager('history');
// get default manager
$em = $this->getDoctrine()->getManager('default');
}
}
Somewhere in services of history bundle
class HistoryBundleUtils {
protected $em;
public function __construct($arguments) {
// get secondary manager
$this->em = $arguments['entityManager']->getManager('history');
# etc...
}
}
This isn't possible, each Entity Manager can only use one DB connection, the docs seem quite clear about it.
So I think you'll be stuck with using two EMs. Each will be configured with a duplicate set of your mappings. The detail of how you use them is up to you though:
You could just manually choose one or the other as required in your app
You could somehow abstract it away into a class of your own which has both EMs, and then when you run queries etc it will be worrying about where to get the data from (and possibly how to combine data from both EMs)
If the only activity which really needs both EMs is the archive process itself, that's an obvious thing to hide away in a class nicely
I suppose it also depends on what the point of the archive DB is. If it's some architectural thing like it needs to be on a different server or whatever, then you're stuck as above. On the other hand, if you really just want old data not to show up in day-to-day queries (without specifically asking for it), then it might be better to implement some kind of "archived" flag and a Doctrine Extension which magically hides archived items away until you ask for them, very similar to SoftDeleteable
I don't know if it is a good practice but I have been using one EM for two Databases successfully in Symfony2. The project I was working on required access to two databases. There are some limitations however. First the database_user and _password needs to be the same for both databases. You can access both databases, but you can only create (with console doctrine:database:create) and write the tables (console doctrine:schema:update) of the one defined in parameters.yml.
You can read, write, update, delete on both databases, but you need to specify the database name of your second database in the model, like:
#ORM\Table(name="my_other_database.my_table")
Basically, you can use one EM for two Databases, if one database already exists and you only need to access it.
Related
If you have a Vendor with a list of Contacts, in DDD which is the better approach for adding a contact to a Vendor?
Here's some sample C# code using a CQRS command.
Given the following command, how should we implement adding a Contact to a Vendor
AddVendorContactCommand()
{
string vendorId;
string contactName;
}
Should we add a contact through the Vendor:
AddVendorContactHandler(AddVendorContactCommand command)
{
var vendor = await dbContext.Vendors.FindAsync(command.vendorId);
vendor.AddContact(command.contactName);
dbContext.Save();
//doesn't require a dbSet for VendorContacts???
}
Or should we reference the VendorContact and bypass the Vendor entirely.
AddVendorContactHandler(AddVendorContactCommand command)
{
//handler
var newVendorContact = new VendorContact(command.vendorId, command.contactName);
dbContext.VendorContacts.Add(newVendorContact);
dbContext.Save();
//requires a dbSet for VendorContacts;
}
I feel like the better approach is to go through the Vendor, but that requires our AddVendorContactCommand to read from the database first. In CQRS Commands, it generally suggests avoid reads. The second approach to use VendorContacts directly will have higher performance than if we go through Vendor.
Argument to go through the Vendor are the following:
What if the Vendor doesn't exist
What if the Vendor isn't allowed any more contacts.
What if the Vendor is deleted, disabled or otherwise readonly
What's the correct DDD approach?
First, as a developer, I'm obligated to say there is no single correct approach to anything.
Now that is out of the way, given the information you have provided, I'm going to assume that the Vendor entity you described can (and in my opinion should) be the Aggregate Root. With that in mind, I would definitely go with the first option you described.
I think you have a misconception about CQRS Commands. It is perfectly fine to get data from the database inside commands. The thing you have to avoid is fetching the data from the query side, which could be a totally different database.
You are also correct, you won't need a DbSet<> for VendorContact entity, and you should keep it that way on the Command side, as you want to protect the invariants inside your Vendor Aggregate Root.
I have several entities, each with its form type. I want to be able, instead of saving the entity straight away on save, to save a copy of the changes we want to perform and store it in DB.
We'd send a message to the user who can approve the change, who will review the original and the changed field(s) and will approve or not. If approved the entity would be properly flushed.
To solve the issue I was thinking about:
1) doing a persist
2) getting the changesets (both the one related to "normal" fields, and the one relative to collections)
3) storing it in DB
4) Performing $em->refresh() to discard changes.
Later what I need is to get the changset(s) back, ask the (other) user to approve it and flush it.
Is this doable? What I'm especially concerned about is that the entity manager that generated the first changeset is not the same we are going to use to perform the flush, I basically need to "load" a changeset.
Any idea on how to solve the issue (this way, or another way ;) )
Another solution (working only for "normal" fields, not reference ones that come from other entities to the current one, like a many to many) would be to clone the current entity, store it, and then once approved copy the field(s) from the cloned to the original one. But it does not work for all fields (if the previous solution does not work we'd limit the feature just to "normal" fields).
Thank you!
SN
Well, you could just treat the modifications as entities themselves, so that every change is stored in the database, and then all the changes that were approved are executed against the entity.
So, for example, if you have some Books stored in the database, and you want to make sure that all the modifications made to these are approved, just add a model that would contain the changeset that has to be processed, and a handler that would apply these changes:
<?php
class UpdateBookCommand
{
// If you'll store these commands in a database, perhaps this field would be a relation,
// or you could just store the ID
public $bookId;
public $newTitle;
public $newAuthor;
// Perhaps this field should be somehow protected from unauthorized changes
public $isApproved;
}
class UpdateBookHandler
{
private $bookRepository;
private $em;
public function handle(UpdateBookCommand $command)
{
if (!$command->isApproved) {
throw new NotAuthorizedException();
}
$book = $this->bookRepository->find($command->bookId);
$book->setTitle($command->newTitle);
$book->setAuthor($command->newAuthor);
$this->em->persist($book);
$this->em->flush();
}
}
Next, in your controller you would just have to make sure that the commands are somehow stored (in a database or maybe even in a message queue), and the handler gets called when the changesets could possibly get applied.
P.S. Perhaps I could have explained this a bit better, but mostly the inspiration for this solution comes from the CQRS pattern that's explained quite well by Martin Fowler. However, I guess in your case a full-blown CQRS implementation is unnecessary and a simpler solution should work.
I've started a Symfony2 project from scratch where I then installed FOSUserBundle.
Then, I have written (actually, generated with ORM Designer) some entities that need to have relations between them, and with the User entity.
I have Items belonging to Users, Collections belonging to Users that group Items, and so on.
Since I used FOSUserBundle I only have a basic User class (https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/index.md , step 3a) defined using annotations, no config/doctrine folder and no User.yml file in it.
I then created the MyBundle/Resources/config/doctrine folder and added the yml files mentioned above.
When I try to generate the entities with the command-line tool everything works fine: it will create the Entities from my yml files.
However, at this point, trying to load up in browsers the url where the login previously worked (when I only had the FOSUserBundle installed) will throw this error:
MappingException: No mapping file found named
'/var/www/concert/src/X/MyBundle/Resources/config/doctrine/User.orm.yml'
for class 'X\MyBundle\Entity\User'.
Following actions, such as generating the CRUD logic, will not work as long as I have an *.orm.yml file in the config/doctrine folder. If I remove those, CRUD generation will work, but generation of actual mysql tables won't.
Juggling with these gets me to a point where I can also get the tables, but then the actual app doesn't work if I try to use any of the url's where the newly generated CRUD is involved because since the entities are based on yml (which I remove to get things "working") it won't have any mapping knowledge.
Is this inherently wrong? To have yml-based entities in relationship with an User entity based on the FOSUserBundle and still be able to get the nice command-line generation tools?
The problem you describe stems from mixing configuration formats (yaml and I assume annotations). You can easily fix this by ditching the annotations in your models and replacing them with yaml-files like you would do in your own models.
Unfortunately the FOSUserBundle-docs only show you how to use annotations, so here is a quick transformation into yaml format when your X\MyBundle\Entity\User extends FOSUSerBundle's UserEntity:
X\MyBundle\Entity\User:
type: entity
table: fos_user
id:
id:
type: integer
strategy: { generator: "AUTO" }
The remaining stuff is taken care of by FOSUserBundle, as the BaseModel is a mapped-superclass and already describes the stuff in the User.orm.xml, but you could just as well replace the existing values or add additional values just like you would do with your own models.
If you don't use annotations throughout your app, you might also want to disable them in your app/config/config.yml to prevent side effects.
I'm using Symfony2 with Doctrine2. I want to achieve the following:
$place = $this->getDoctrine()->getRepository('TETestBundle:Place')->find($id);
And on that place will be the info of the place (common data + texts) on the user language (in session). As I am going to do that hundreds of times, I want to pass it behind the scenes, not as a second parameter. So an English user will view the place info in English and a Spanish user in Spanish.
One possibility is to access the locale of the app from an EntityRepository. I know it's done with services and DI but I can't figure it out!
// PlaceRepository
class PlaceRepository extends EntityRepository
{
public function find($id)
{
// get locale somehow
$locale = $this->get('session')->getLocale();
// do a query with the locale in session
return $this->_em->createQuery(...);
}
}
How would you do it? Could you explain with a bit of detail the steps and new classes I have to create & extend? I plan on releasing this Translation Bundle once it's ready :)
Thanks!
I don't believe that Doctrine is a good approach for accessing session data. There's just too much overhead in the ORM to just pull session data.
Check out the Symfony 2 Cookbook for configuration of PDO-backed sessions.
Rather than setting up a service, I'd consider an approach that used a Doctrine event listener. Just before each lookup, the listener would pick out the correct locale from somewhere (session, config, or any other place you like in the future), inject it into the query, and like magic, your model doesn't have to know those details. Keeps your model's scope clean.
You don't want your model or Repository crossing over into the sessions directly. What if you decide in the future that you want a command-line tool with that Repository? With all that session cruft in there, you'll have a mess.
Doctrine event listeners are magically delicious. They take some experimentation, but they wind up being a very configurable, out-of-the-way solution to this kind of query manipulation.
UPDATE: It looks like what you'd benefit from most is the Doctrine Translatable Extension. It has done all the work for you in terms of registering listeners, providing hooks for how to pass in the appropriate locale (from wherever you're keeping it), and so on. I've used the Gedmo extensions myself (though not this particular one), and have found them all to be of high quality.
I am building a SaaS application and I would like to retain the single code base I have. I would like to be in separate sub-domains cust1.saascompany.com, cust2.saascompany.com, etc.
However, I don't have any TenantID's and would prefer for multiple reasons to stay with separate databases for each customer (primary one is that it's already coded that way and doesn't make sense to change it until usage warrants). The database has the user login membership within it.
I'm guessing I would need separate web.configs for connection strings? Or should I create a separate database that stores all the connection strings and any application level variables/constants? Eventually, I would like to be able to automate this provisioning (again, when usage warrants it).
Are there some articles or posts that anyone can point me to regarding how to set this up with steps? I haven't been able to find what I'm looking for.
Technically, this is simple. We do this for years. Although we use a different convention (my.domain.com/cust1, my.domain.com/cust2 plus url rewriting) this doesn't change anything.
So, this is what you do. You create an abstract specification of a connection string provider:
public interface ICustomerInformationProvider
{
string GetConnectionString( string CustomerId );
... // perhaps other information
}
then you provide any implementation you want like:
public class WebConfigCustomerInformationProvider : ICustomerInformationProvider { ... }
public class DatabaseConfigCustomerInformationProvider : ICustomerInformationProvider { ... }
public class XmlConfigCustomerInformationProvider : ICustomerInformationProvider { ... }
and you map your interface onto the implementation somehow (for example, using an IoC Container of your choice).
This gives you the chance to configure the provider during the deployment, for example, a one provider can be used by developers (reads connection strings from a file) and another one in the production environment (reads connection strings from a database which can be easily provisioned).
If you have other questions, feel free to ask.