How do I perform queries in an entity?
namespace Entities\Members;
/**
* #Entity(repositoryClass="\Entities\Member\MembersRepository")
* #Table(name="Members")
* #HasLifecycleCallbacks
*/
class Members extends \Entities\AbstractEntity
{
/**
* #Id #Column(name="id", type="bigint",length=15)
* #GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #Column(name="userid", type="bigint", length=26, nullable=true)
*/
protected $userid;
/**
* #Column(name="fname", type="string", length=255,nullable=true)
*/
protected $fname;
/**
* #OneToMany(targetEntity="\Entities\Users\Wall", mappedBy="entry", cascade={"persist"})
*/
protected $commententries;
public function __construct()
{
$this->commententries = new \Doctrine\Common\Collections\ArrayCollection();
}
}
Example I would like to have a function inside this entity called: filter()
and I want to be able to filter the commententries collection. It should return a collection with a certain condition such id=1. Basically it should be filtering the data received from the join query.
So something like this:
$this->commententries->findBy(array('id' => 1));
But obviously this does not work.
Your ArrayCollection already implements a filter() method, you need to pass a Closure to get it to work your entities (here, the commentEntries).
$idsToFilter = array(1,2,3,4);
$member->getComments()->filter(
function($entry) use ($idsToFilter) {
if (in_array($entry->getId(), $idsToFilter)) {
return true;
}
return false;
}
);
(not tested)
Note that such method will iterate and eager load over all your Comments, so in case where a User has a lot it may be a big bottleneck;
In most case, you want to use a custom repositories, where you can place such logic.
As timdev suggested, you can create a MemberService which will wrap such call by being aware of the EntityManager.
Separating Entities from the Peristance Layer is a big improvement over Doctrine 1, and you should not break that rule.
Generally speaking, you shouldn't do this.
Entities, as a rule of thumb, should not know about the entitymanager (directly, or via some intermediary object).
The reason for this is mostly testability, but in my experience, it helps keeps things organized in other ways.
I'd approach it by designing a service class that handles the lookups for you. Your controller (or whatever) would drive it like this:
<?php
// create a new service, injecting the entitymanager. if you later wanted
// to start caching some things, you might inject a cache driver as well.
$member = $em->find('Member',$member_id); //get a member, some how.
$svc = new MemberService($em);
$favoriteCommentaries = $svc->getFavoriteCommentaries($member);
As I hint in the comment, if you decide later that you want to add caching (via memcached, for instance) to avoid frequent lookups, you'd do that somewhere near or in this service class. This keeps your entities nice and simple, and easily testable. Since you inject your entitymanager into the service at construction-time, you can mock that as needed.
getFavoriteCommentaries() could use various implementations. A trivial one would be to proxy it to Member::getFavoriteCommentaries(), which would actually load everything, and then filter out the "favorite" ones. That probably won't scale particularly well, so you could improve it by using the EM to fetch just the data you need.
Use a custom repository for queries
You should not write queries in your entities, but you should use a repository for that. This is also explained in the doctrine documentation 7.8.8 Custom Repositories. It will allows you to build your custom queries on a central spot and keeps your entity definitions clean.
Use criteria to filter collections:
But if you want to filter inside your collection in a get method you can use Criteria. You can read on how to use Criteria in the Doctrine documentation 8.8 Filtering collections. To filter like you want to do would look something like this:
Declare at the top of your entity class the Criteria class
use Doctrine\Common\Collections\Criteria
In your getCommentEntries method use the class to filter:
public function getCommentEntries()
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq('id', 1));
$filteredCommentEntries = $this->commententries->matching($criteria);
return $filteredCommentEntries;
}
Your question was really hard to understand, please try and work on how you structure your questions in the future. For instance, you say "return back the same result" but "filter", which could mean anything. Do you want to use the same result set (why on earth would you ever choose to do that), and just use array_filter or array_walk to filter the results or do you actually want to use a conditional join? It's incredibly ambiguous.
Anyway.. answer ( after reading your question 4 times).
$q = $qb->select ( "m","w" )
->from ( "Members", "m" )
->leftJoin( "m.commententries","w",'WITH', 'w.id = :id')
->setParameter ( "id", $id )
->getQuery ();
I agree with "timdev". You shouldn't define query in your entities class. My way to define a service class support the entities are repository classes. For example: User (entity -- YourBundle/Entity/User.php) will have UserRepository (service class -- YourBundle/Repository/UserRepository.php). Your "filter" method should be in here. You just need to map this service class in your entity class. In your controller, you can always access the "filter" via its repository. It's documented very detail in the Symfony2 book
Related
I want to use doctrine ODM for my project and I had the idea to have a seperate database for each of my clients. I want to be able to manage clients at runtime, through my API. My question now is:
When I setup doctrine ODM I have to set my database settings in my parameters.yml but I want to be able to select the database at runtime. I will have one main database with all my fixture collections and client index to know which database to select, but the client specific stuff will then be in those client databases. Each Document class will still be linked to a collection like in the normal situation, but then in a different database.
Is there a way to select the database for a Document class at runtime?
So lets say I go to www.myproject.com/client1/item/list
I will list every item in the dbclient1.Items collection, and if I go to www.myproject.com/client2/item/list I will list all items in the dbclient2.Items collection.
I hope I made clear what I want to reach here... I couldn't find anything about this, but I think it would be weird if I was the first person to have a question about this... There must have been people before me with the same idea right?
Is there a way to select the database for a Document class at runtime?
Yes, you could call $dm->getConfiguration()->setDefaultDb('some_db_name') each time you need to change database but this can lead to unexpected behaviour when write comes into play.
Based on my experience (and few years practice on multi-tenant apps) the most bulletproof way is to create separate instances of DocumentManager per each context. You can achieve that by having one DocumentManager configured normally with your parameters but never used directly - instead you need to have a class that will manage its instances, fe:
use Doctrine\ODM\MongoDB\DocumentManager;
class DocumentManagerFactory
{
/**
* DocumentManager created by Symfony.
*
* #var DocumentManager
*/
private $defaultDocumentManager;
/**
* All DocumentManagers created by Factory so far.
*
* #var DocumentManager[]
*/
private $instances = array();
public function __construct(DocumentManager $dm)
{
$this->defaultDocumentManager = $dm;
}
public function createFor(Context $ctx)
{
$databaseName = $ctx->getDatabaseName();
if (isset($this->instances[$databaseName])) {
return $this->instances[$databaseName];
}
$configuration = clone $this->defaultDocumentManager->getConfiguration();
$configuration->setDefaultDB($databaseName);
return $this->instances[$databaseName] = DocumentManager::create(
$this->defaultDocumentManager->getConnection(),
$configuration,
$this->defaultDocumentManager->getEventManager()
);
}
}
Thanks to this approach documents from few databases will never be managed by one DocumentManager, you have one class responsible for meddling with ODM configuration and framework-approach preserved (event subscribers et al are same for each DocumentManager).
I also needed to default the databaseName depending on the domainName (i.e. seballot.mysite.com will use seballot database)
I made this hack, which not very nice but works great. I added those line in the DocumentManager construct
$dbName = isset($_SERVER["HTTP_HOST"]) ? explode('.', $_SERVER["HTTP_HOST"])[0] : 'default_database_of_my_root_website';
$this->config->setDefaultDB($dbName);
Well I am finally started to learn Symfony and I think peoples will understand my question (I Hope) and my wish to structure my code...
Well, I would like to create a Class which is called Reception and this class has a sql let's say in each methods/function. Each and evry methods can return a different nombre of column results.
Example :
Sql 1 : Jo;DATE;
Sql 2 : Client;Car;Time
Let's tell that I don't want to create a Entity to use it with doctrine...
I would like to use DBAL (pdo doctrine sql query) to do execute my queries... like in normal PHP poo programming.
Finally the question is : wether I should do this class as a Service, Entity? or I can simple put the pdo query in the controller....
Thanks in advance for your answers...
I avoid doctrine for the moment because I principaly doing some statistiques and also to play a bit with symfony and to increase the difficutly level progresivly...
thanks for your understanding...
Good Day
A Service (docs) is usually just a class which is responsible for doing some specific task. Lets say you have some statistics you need to be updated whenever specific events occur (i.e. file is downloaded, favored, etc) and you have multiple controllers where those different events occur. It would be a really bad idea to simply "copy-paste" your code. A better way would be to create a service and call it.
An Entity(docs) is an object which represents your database table. This object can later be used to generate forms in Symfony. Once you create an Entity, you can then create an EntityRepository. It is used to store more comprehensive sql queries. You can, for instance, have a method like this:
public function findUsersWithOrders() {
// Here you can:
// 1. use queryBuilder, which generates the query for you
// 2. write your DQL (Doctrine Query Language) manually
// 3. write a plain SQL query and return the results
}
I would strongly advice you to use this approach - it will save you a lot of time once you get a hold of it and IMHO is a better coding practice.
If you still decide you would want to pursuit your idea of storing queries in a class:
Yes, you could create a Service and use it for that purpose. You should use Symfony >= 2.3 because of Lazy Services which optimizes service loading. Here is an example of how your service might look like:
// App\BaseBundle\Services\MyServiceName.php
namespace App\BaseBundle\Services;
use Doctrine\ORM\EntityManager;
class MyServiceName {
/**
* #var \Doctrine\ORM\EntityManager
*/
private $em;
/**
* #var \Doctrine\DBAL\Connection
*/
private $connection;
public function __construct(EntityManager $entityManager) {
$this->em = $entityManager;
$this->connection = $entityManager->getConnection();
}
public function getUsers(){
// Update query
$this->connection->query('UPDATE statistics SET counter=counter+1 WHERE id = 1')->execute();
// Prepare (you can use just query() as well)
$select = $this->connection->prepare('SELECT * FROM users WHERE username LIKE :username');
$select->bindValue(':username', '%sample%');
$select->execute();
return $select->fetch(\PDO::FETCH_ASSOC);
}
}
Then, in your services.yml file you need to put this:
app.myservicename:
class: App\BaseBundle\Services\MyServiceName
arguments: [ #doctrine.orm.entity_manager ]
Now, whenever you call $this->get('app.myservicename') from a Controller, you will get an instance of your class.
Of course, you can put your sql code in the controller as well. This is not a good practice and you should avoid doing it, though. This example shows you how to do it:
/**
* #Route("/some/route")
* #Template()
*/
public function indexAction()
{
/**
* #var \Doctrine\ORM\EntityManager $em
*/
$em = $this->getDoctrine()->getManager();
// some business logic ...
try {
$em->getConnection()->query('UPDATE statistics SET counter=counter+1 WHERE id = 1')->execute();
} catch(\Doctrine\DBAL\DBALException $e){
// the query might fail, catch the exception and do something with it.
}
// other business logic...
return array('name' => 'Hello World');
}
I would advice you to have a look at the symfony best practices to see what are the best approaches to common problems. Also reading the main documentation will help you clear a lot of questions.
Say that I have an entity Profile which has an association with an Account entity. I want to fetch the profile with profileCode = 12345 and where its related Account has the e-mail address of my#email.com. So, I need to specify a condition on both entities.
For this I have created a custom repository Repository\Profile and now I am wondering how to implement this. I know that I can solve all of this with a "raw" DQL query or by using the query builder. However, I feel as if it is not as pretty as I would like, because it is very close to raw SQL. Sure the syntax is a bit different, but conceptually, I'd be thinking more in terms of SQL than OOP. I will be doing these kind of things a lot, so I am really trying to do it the best way.
I have done a bit of reading on the Criteria object (the documentation is sparse), and it really seems great as long as I am filtering on a single entity. I was unable to find any solution to using Criteria when filtering on associated entities.
So basically my question is: is there any way in which I can use the Criteria object for filtering on multiple entities directly from the database, and within a custom repository? I would really prefer to code this within a custom repository than the entity itself as I have seen some people do. Is this possible, or are there any good alternatives, or is plain DQL or the query builder really the way to go?
Criteria can only filter the associations on the entity itself, but if I understand your use case, you need to filter 2 levels deep, so it won't work.
You are trying to shape Doctrine into something that is not, by wanting to do things "your way". It will only hurt you in the long run. The solution is already there, it is perfectly fine to use DQL, especially in repositories.
I do not see why not, though not sure how your association is related. One-to-One, One-to-Many, or Many-to-Many
If you're trying to retrieve records from the 3rd association depth level such as Account.profile -> Profile.code -> Code.xxx (not id) you would need to use DQL or or the QueryBuilder to establish what Profile and Code were joining Account with or resolve the Code entity to provide it to Criteria.
Assuming a One-to-Many where one Account has many Profiles. You can also optionally just define it within the entity itself to make it easier to manage and reduce having the extra call to getProfiles().
Repository Usage
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Collections\Criteria;
class AccountRepository extends EntityRepository {
/**
* #var string|Account $accountOrEmail
* #var string $code
* #return Profile|null
*/
public function getAccountProfileByEmailAndCode($accountOrEmail, $code)
{
try{
$account = ($accountOrEmail instanceof Account ? $accountOrEmail: $this->findOneBy(['email' => $accountOrEmail]));
if (!$account instanceof Account) {
throw new \InvalidArgumentException('Unknown Account Specified.');
}
$criteria = Criteria::create()->setMaxResult(1);
$expr = $criteria::expr();
$criteria->where($expr->eq('code', $code));
return $account->getProfiles()->matching($criteria)->first() ?: null;
} catch(\Exception $e) {
return null;
}
}
}
$accountRepo = $em->getRepository('\Entities\Account');
$profile = $accountRepo->getAccountProfileByEmailAndCode('my#email.com', '12345');
Entity usage
use Doctrine\Common\Collections\Criteria;
/**
* #ORM\Entity(repositoryClass="AccountRepository")
*/
class Account {
// ...
public function getProfiles(Criteria $criteria = null)
{
if ($criteria instanceof Criteria) {
return $this->profiles->matching($criteria);
}
return $this->profiles;
}
/**
* #var string $code
* #return null|Profile
*/
public function getProfileByCode($code)
{
$criteria = Criteria::create()->setMaxResult(1);
$expr = $criteria::expr();
$criteria->where($expr->eq('code', $code));
return $this->getProfiles($criteria)->first() ?: null;
}
}
$account = $em->getRepository('\Entities\Account')->findOneBy(['email' => 'my#email.com']);
$profile = $account->getProfileByCode('12345');
I'm working with a Symfony 2.1 application and I have a lot of parameters being sent via a POST request and I'm looking for a smarter way to take each request parameter and populate the my entity class. I'm looking to avoid writing $entity->setMyParam($my_param) expressions for n request parameters. For example, here is a snippet of my entity:
namespace Brea\ApiBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Brea\ApiBundle\Entity\Distributions
*
* #ORM\Table(name="distributions")
* #ORM\Entity
*/
class Distributions
{
/**
* #var string $recordType
*
* #ORM\Column(name="record_type", type="string", nullable=false)
* #Assert\NotBlank()
* #Assert\Choice(choices = {"a", "b", "c", "d", "e"}, message = "Choose a valid record type")
*/
private $recordType;
/**
* Set recordType
*
* #param string $recordType
*/
public function setRecordType($recordType)
{
$this->recordType = $recordType;
}
/**
* Get recordType
*
* #return string
*/
public function getRecordType()
{
return $this->recordType;
}
}
My controller attempts to take each request, camelcase the parameters and set the value of the request param to the entity:
public function createRecordAction(Request $request, $id)
{
$distribution = new Distributions();
$params = $request->request;
foreach ($request->request->all() as $param=>$value)
{
if ($param == "_method")
continue;
$function = "set".str_replace(' ','',ucwords(preg_replace('/[^A-Z^a-z^0-9]+/',' ',$param)));
$distribution->$function($value);
}
}
It works, but my misgiving about this approach is that I would need to run this code in every controller that does a similar thing. I can refactor it into a parent class as a method to avoid duplicating code, but I'm curious if this is a good practice. I looked for something in the Symfony framework that does this already but all I could find were examples of binding the request to a form.
First of all: Warning!!
As I commented earlier, I would be very careful about using the code provided in your original post since you said it's data from a POST request, which means a client could inject any kind of data in it and call functions you might not have wanted on your object (or just cause a failure in your script by sending you non-existant function names).
I would actually read the conclusion first..! :) Then come back to Alt. 1 & 2.
Alternative 1:
With that being said, an alternative solution to your problem would be to give objects the responsability to fetch their own data. With granulated enough objects, you should not end up with bloated code, and you can define in each class which parameters to look for and which functions to call (and localize changes when you make a change to a class):
class BookInformation{
private $publisher;
private $name;
private $price;
public static createFromRequest($req){
$publisher = Publisher::createFromRequest($req);
$book = new BookInformation($publisher, $req['book_name'], $req['book_price']);
$book->setABC($req['abc']);
//...
return $book;
}
public __construct($publisher, $name, $price){
//...
}
}
class Publisher{
private $name;
private $address;
public static createFromRequest($req){
return new Publisher($req['publisher_name'], $req['publisher_address']);
}
public __construct($name, $address){
//...
}
}
Like I said before, one big advantage with this method is that if you need to add new attributes to any of those classes, you don't have to edit the controllers at all and can just edit your "initialization from request method". Future changes will be localized to the modified class.
Of course, don't forget to validate any data sent from a user request (but that's just common sense).
Alternative 2:
Note that the first alternative is very similar to the Factory pattern (based on GoF's Abstract Factory), and that you could also implement a solution using that pattern:
class BookFactory{
public createBookInformation($req){
$publisher = $this->createPublisher($req);
$book = new BookInformation($publisher, $req['book_name'], $req['book_price']);
$book->setABC($req['abc']);
//...
return $book;
}
public createPublisher($req){
return new Publisher($req['publisher_name'], $req['publisher_address']);
}
//createAnythingRelatedToBooks($req)...
}
That way, you have all the initializing procedures in a very cohesive class which only responsability is to initiliaze a certain family of objects based on a request object (and that is a VERY good thing). However, if you add attributes to one of those classes, you have to edit the appropriate Factory method too.
Conclusion
Please note that these two alternatives are actually not really alternatives... They can be used along with your initial code (especially the Factory one). They really only solve your last problem (the "where to put the code" problem).
However, even if you do sanitize the POST request and only call functions that are annoted (as stated earlier), I wouldn't really suggest it because I have the feeling that more complex business rules would ruin the design pretty fast (but maybe you've got it all covered already (?)). That is, I don't think you can easily plug in business rules in the initializing process since it's all automatic (it can't do any validation of the values since it could be any kind of value) and I feel like you'd end up "undo-ing" stuff after initialization (which I personally hate.. Lots of room for errors)!
For example, take the same two classes in Alternative 1 (BookInformation and Publisher).
Let's say a Book can only have a Publisher if that Publisher is already registered in a database and that their address has been confirmed (new publishers need to be created using another interface and then have their address confirmed before they can be linked to a book).
Else, regardless of the request data, publisher should be set to XYZ. I have the feeling (I might be wrong) that to support those kind of rules, you'd have to actually construct the object (automatically) and then destroy/reassign the publisher attribute if it does not match certain rules. Now, if you have a pool of those Publisher objects in memory, you also need to remember to delete the wrongly created Publisher in that pool. And that's just one rule!
One thing you could do with your code to "fix" that issue would be to have a validation method for each setter (validXYZ()), but that's starting to look like a design that would fall apart pretty quickly if validations are dependent on other objects/data...
I don't really have anything else to discourage you from using that code, but if you do, please keep us updated on how it works out after a year or two (once there has been some maintenance/new features added, etc...).
I looked for something in the Symfony framework that does this already but all I could find were examples of binding the request to a form.
I would use Forms for this. Even if the HTTP request is not performed from an HTMl form, you can just bind the Request to a form instance: it will take care of all the data injection and validation.
And plus, in case you'll ever need HTML forms, you'll have them ready ^^.
I have set up a bundle that has a test object which holds a number of testQuestion objects each of which is a question and the given answer (or 0 if no answer). From twig I want to be able to get the information from the test object to say how many questions there are and how many have been answered.
I have created a query to pull this out of the db, and in the Test Entity I have created 2 new properties to store the number of questions and the number answered. I have created a TestRepository inside which the query resides. The Test object checks to see if the object has the value set and if not loads it when needed as I won't always need this information.
However I'm stuck on how to link the repository code to the test object, both to call the repo function and for the repo function to save the values to the relevant Test object.
Acme/Quizbundle/Test/Test.php
namespace Acme\QuizBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Acme\QuizBundle\Entity\TestRepository;
/**
* #ORM\Entity(repositoryClass="Acme\QuizBundle\Entity\TestRepository")
* #ORM\Table(name="test")
*/
class Test {
protected $numQuestions = null;
protected $numQuestionsAnswered = null;
public function getNumQuestionsAnswered () {
if (is_null($this->numQuestionsAnswered)) {
$repository = $this->getEntityManager()->getRepository('\AcmeQuizBundle\Test');
$values = $repository->calculateNumQuestions();
}
return $this->numQuestionsAnswered;
}
Acme/Quizbundle/Test/TestRepository.php (There's a matching method for getNumQuestions())
namespace Acme\QuizBundle\Entity;
use Doctrine\ORM\EntityRepository;
class TestRepository extends EntityRepository {
private function calculateNumQuestions() {
$qb = $this->getEntityManager()
->createQueryBuilder();
$query = $this->getEntityManager()->createQueryBuilder()
->select('COUNT(id)')
->from('testquestion', 'tq')
->where('tq.test_id = :id')
->setParameter('id', $this->getId())
->getQuery();
$result = $query->getSingleScalarResult();
var_dump($result);
}
There are a number of different patterns you can use to achieve this result, the simplest of which is to simply use an aggregate field. This stores the information after it's modified, rather than calculating it each time that it's needed.
An alternate solution is to create a one-to-many association between your Test and TestQuestion repositories (assuming that there isn't one already), then in your twig template you can simply use {{ testEntity.questionsAnswered.count() }} - you can even tell Doctrine to make this an "extra-lazy" association so that it uses the COUNT SQL statement to look up how many answered questions there are (by default it actually fetches the question entities when you try to enumerate the association).
Finally, there's the method that I wouldn't recommend highly, but might be required depending on your situation. Similar to the approach that you use in your question, you fetch the question count in your repository, but to keep with Symfony's simple Model approach, you don't kick off the query from inside the entity (as the entity should never have information about the entity manager/repository).
Instead, you can use a Doctrine EventListener to be informed whenever an instance of your Test entity is loaded (see here, using the postLoad event), then call your repository method and set it on the entity from there.