symfony2 create doctrine collection from query - symfony

Not sure if this is possible or not but im looking to create a doctrine collection from a query. The idea is to populate the collection with some pre-set values so i can update the database think of it like an import/generate users from an old system into a new one. Im struggling with the repository bit.
Entity
// Portal\UserBundle\Entity\User.php
namespace Portal\UserBundle\Entity;
use Doctrine\ORM\Mapping AS ORM;
/**
* #ORM\Entity
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, nullable=false)
*/
private $fistname;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
// etc...
}
Repository
namespace Portal\UserBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
public function getGenerateNewUsers()
{
// acts as an import from an old user table
$sql = " SELECT firstname, surname, other FROM old_user_table ";
$userCollection = .... not sure how I link query?
return $userCollection;
}
}
Calling it inside the controller
With the above I intend to be able to fetch the newly generated users loop over them and have access to my entity methods objects etc.
class SetupController extends Controller
{
public function indexAction(){
$repository = this->getDoctrine()->getRepository('UserBundle:User');
$newUsers = $repository->getGenerateUsers();
// I can now have access to the users with something like
foreach($newUsers as $user){
$user->setFirstName('testing');
$user->save();
}
}
}

It's usually the case with imports like this that your legacy table doesn't directly map to your new one (in terms of field names, constraints, etc), and may not even be in the same DBMS, so really the best option is a slightly manual approach. Execute your SQL query against your legacy database in your favourite old-fashioned way to get your users as simple arrays, then loop through them and create entities:
//Set up an empty collection
$collection = new ArrayCollection();
/*Assuming PDO where you have set up and executed a PDO statement $pdoStatement,
but mysql_fetch_assoc or whatever is appropriate for your DBMS would work equally
well. $oldUser should just be a plain associative array*/
while($oldUser = $pdoStatement->fetch(PDO::FETCH_ASSOC)){
//Initialise an empty User entity
$newUser = new User();
//Set the properties on the entity using the values from your array
$newUser->setFirstName($oldUser['firstname']);
//etc
//Add your user to the collection
$collection->add($newUser);
}
return $collection
I notice you're thinking of calling save() on your User objects in your controller, but it doesn't generally work that way in Doctrine as your entities will be plain objects which don't inherit from anything and don't have any special methods. The way to save the entity to your new database is to grab the entity manager and call its persist method.
In your controller:
$entityManager = $this->get('Doctrine')->getManager();
foreach($users as $user){
//Manipulate the user here if you need to
//Then persist it
$entityManager->persist($user);
}
As an aside - if you wanted to get a collection of entities by executing a query against your new database that's a slightly different problem to which there's a much more elegant solution. Doctrine Query Language allows you to query your database in a SQL-like way while using the language of your objects. With DQL, the results of your queries will by default be hydrated into Doctrine entites.

Hogan mentions DQL. Here is what that would look like, but you'd have to make sure your old database was wired up. The result is a collection of entities, off which you could use method calls to store part or all of the data as you see fit.
namespace Portal\UserBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
public function getGenerateNewUsers()
{
$qb = $this->getEntityManager()
->getRepository('Bundle:Old_User')->createQueryBuilder('o');
$query = $qb->getQuery();
$results = $query->getResult();
return $results;
}
}

Related

Symfony / Doctrine: Restful API design and design pattern about repository injection

I'm new to Symfony and these questions were brought about in the recent course of learning.
Take a store as an example, I'll create two entities, Product and Category, which have a bidirectional Many-to-One relationship.
class Product
{
private $id;
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=false)
*/
private $category;
}
class Category
{
private $id;
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="category")
*/
private $products;
}
So my 1st question is:
If I want to get all the products in a particular category, should the URL be
/categories?categoryId=1&limit=20&orderBy=name
(I know it's a bit silly, but should a Category record contains all the Product information?)
or
/products?categoryId=1&limit=20&orderBy=name
For the latter one, here comes the 2nd question:
I injected the ProductRepository to the ProductController
class ProductController extends Controller
{
private $productRepository;
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
...
}
In order to get all the product in a category, I wrote a method like this:
public function findByCategory(Category $category): array
{
return $this->createQueryBuilder('p')
->andWhere('p.category = :category')
->setParameter('category', $category)
->orderBy('p.name', 'ASC')
->setMaxResults(20)
->getQuery()
->getResult()
;
}
So, in the ProductController, how should I get the Category object from the query string 'categoryId' in the URL? Should I inject CategoryRepository as well or should I simply inject an entity manager object?
Marco Pivetta aka Ocramius (one of the main developers of Doctrine) said:
Avoid bi-directional associations
Bi-directional associations are overhead
Code only what you need for your domain logic to work
Hack complex DQL queries instead of making them simpler with bidirectionality
So maybe
you don't need bi-directional association here.
For your first question, the second solution is better in my opinion:
/products?categoryId=1&limit=20&orderBy=name
For your second question yes, you should inject the CategoryRepository if you want to access Category object, avoid accessing the whole entityManager in your controller even if it is possible.
You should inject services in your controllers. Your services should expose public methods to perform custom CRUD access to the DB through data mappers. Note that a repository is not a data mapper but it
mediates between the domain and data mapping layers, acting like an in-memory domain object collection.
P of EAA Catalog - Martin Fowler
Repositories are services in fact so that's fine to inject them into the controller.
Some people defend the position that repositories should not contain CREATE, UPDATE or DELETE but only READ. They say that these operations make collections (which are accessible through repositories) inconsistent.
This post can help too: How should a model be structured in MVC?
To me, the problem here is a very clear confusion between what the ORM does in Symfony and Database Design, Modelling and Querying.
In the database (using PhpMyAdmin), you'll notice that on the product table there is a column called category (or category_id). Keep it simple, to get products belonging to a category, all you need is the category_id. Take that knowledge to Symfony, you DO NOT need the Category Object, please just use the category ID that you got from the request. Also, just use the EntityManager in the controller, don't complicate stuff, especially since it seems you're just starting out.
use Symfony\Component\HttpFoundation\Request;
class ProductController extends Controller
{
public function get_product_from_categoryAction(Request $request)
{
$category_id = (int) $request->get('category');
$limit = (int) $request->get('limit');
$orderBy = strip_tags($request->get('orderBy'));
$em = $this->getDoctrine()->getManager();
$products = $em
->getRepository('AppBundle:Products')
->queryProductsByCategoryId($category_id, $limit, $orderBy);
}
...
}
And the repo
public function queryProductsByCategoryId(int $category_id, int $limit = 10, string $orderBy = 'name')
{
return $this->createQueryBuilder('p')
->andWhere('p.category = :category')
->setParameter('category', $category_id)
->orderBy('p.name', 'ASC')
->setMaxResults($limit)
->getQuery()
->getResult()
;
}
Keep things simple, then when you got to be more advanced, try the more fancy stuff if you so please.

How to store historical data with symfony and doctrine?

I want to store store historical data with symfony2 and doctrine2. For example i am having 2 entities:
class Shop
{
private $id;
private $url;
private $version;
}
and the second entity:
class Version
{
private $id;
private $software;
private $version;
}
The Version entity stores specific shop-versions, for example Magento 1.2 or OXID eShop 4.7 - so a entry for a version-entity should be reusable.
Every time the version for a Shop is changed, i want to store this change to have a historical view for the version-changes.
How can i do that with symfony2 and doctrine2? I have tried many-to-many mappings, but i cant figure out the right way using the correct mapping.
Thanks for your help!
There's a few things you have to set properly in order for this to work.
First, you need to tell Doctrine that $versions is related to Version:
class Shop
{
private $id;
private $url;
/**
* #ORM\ManyToMany(targetEntity="Version", cascade={"persist"})
* #ORM\JoinTable(name="shop_version",
* joinColumns={#ORM\JoinColumn(name="shop_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="version_id", referencedColumnName="id")}
* )
*/
private $versions;
}
Since it's a ManyToMany relationship (documentation), $versions will be treated like an ArrayCollection by Symfony. Thus, you need to create methods to handle it accordingly.
Constructor
public function __construct()
{
$this->versions = new ArrayCollection();
}
Getter
public function getVersions()
{
return $this->versions;
}
Adder
public function addVersion(Version $version)
{
$this->versions[] = $version;
}
Remover
public function removeVersion(Version $version)
{
$this->versions->removeElement($version);
}
That's it. Don't forget to add the use statment for ArrayCollection!
use Doctrine\Common\Collections\ArrayCollection;
In your case instead of reinventing the wheel i would recommend Doctrine2 extension: EntityAudit that allows full versioning of entities and their associations. Usage:
$auditReader = $this->container->get("simplethings_entityaudit.reader");
// find entity state at a particular revision
$articleAudit = $auditReader->find('SimpleThings\EntityAudit\Tests\ArticleAudit', $id = 1, $rev = 10);
// find Revision History of an audited entity
$revisions = $auditReader->findRevisions('SimpleThings\EntityAudit\Tests\ArticleAudit', $id = 1);
// find Changed Entities at a specific revision
$changedEntities = $auditReader->findEntitiesChangedAtRevision( 10 );
and more on: https://github.com/simplethings/EntityAudit
Another available package for entity versioning is https://github.com/madmis/ActivityLogBundle. This package includes a revision control system that saves each state of your desired entities and properties.
To enable logging, add the following annotation to your entity class
#Gedmo\Loggable(logEntryClass="ActivityLogBundle\Entity\LogEntry")
Make sure to import the annotation
use Gedmo\Mapping\Annotation as Gedmo;
Add the following annotations to the properties where you want to log changes of
#Gedmo\Versioned
The package offers methods to easily retrieve logentries for an entity
public function getLogEntriesQuery($entity)
This will return log entries with the following methods
$logEntry->getVersion() //returns entities revision version
$logEntry->getOldData() //returns data state before updating
$logEntry->getData() //returns data state after updating
$logEntry->getLoggedAt() //returns when de log was created
In order to retrieve logEntries for a given timeframe you can extend the querybuilder that's returned from the following method, which is also available in the LogEntryRepository:
public function getLogEntriesQueryBuilder($entity)

Make another entity from entity by Doctrine

I have two tables, user and userAttr.
'user' and 'userAttr' are tied as onebyone.
I would like to insert a row in userAttr when row is inserted user.
So this is my idea.
Make new data row of userAttr In prePersist() method in user entity.
in Acme/UserBundle/Entity/User.php
class User extends BaseUser implements ParticipantInterface
{
public function prePersist()
{
$userAttr = new userAttr();
$userAttr->setUser($this);
$userAttr->setEnabled(true);
$this->setUserAttr($userAttr);
$em = $this->getDoctrine()->getManager();
$em->persist($userAttr);
$em->flush();
but it shows error like this.
Fatal error: Call to undefined method Acme\UserBundle\Entity\User::getDoctrine() in
There are two quesions.
1.Is my basic idea correct?
2.How can I get the instance of doctrine in entity class?
Generally you got a little fallacy in there. Once the entity manager persists a entry it will also persist the related one-to-one connection. So if you persist $userAttr - which is one-to-one connected to your instance of User - it will persist User before it should get persisted. Causing double writings in the database. You can avoid this by adjusting your prePersist() to
public function prePersist()
{
$userAttr = new userAttr();
$userAttr->setUser($this);
$userAttr->setEnabled(true);
$this->setUserAttr($userAttr);
}
This avoids finding a way to get a instance of the entity manager too.
I'll answer your second question first.
How can I get the instance of doctrine in entity class?
You can't and you shouldn't. Your entity class is just a model, it has no knowledge of Doctrine, Symfony or the Entity Manager. Persistence will be handled at a higher level.
Is my basic idea correct?
No. As I said in the previous point, persistence shouldn't be a worry at this level. Here, you're just defining the properties and relations of your model.
I imagine your entity looks somewhat like this:
class User extends BaseUser implements ParticipantInterface
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
/**
* #ORM\OneToOne(targetEntity="UserAttr", cascade={"persist"})
*/
private $userAttr;
public function setUserAttr(UserAttr $userAttr = null)
{
$this->userAttr = $userAttr;
return $this;
}
public function getUserAttr()
{
return $this->technician;
}
}
Note the cascade={"persist"} option in the relation with UserAttr. This is what tells Doctrine that it should insert that into the database, too.
Further reading
Doctrine: One-To-One, Unidirectional
Doctrine: Transitive persistence / Cascade Operations
Symfony: Persisting Objects to the Database

How to call doctrine in an entity class using symfony

Am using symfony framework for my application. And to save records in database I want call the $this->getDoctrine()->getManager(); method in my entity class. But when I did that it gave me the error:
Call to undefined method getDoctrine(),
Can some one tell me what is the right way to do this.
My entity class is like:
namespace Acme\SuperbAppBundle\Entity;
use Symfony\Component\DependencyInjection\Container;
use Doctrine\ORM\Mapping as ORM;
class Users
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $firstName;
/**
* #var string
*/
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstName
*
* #param string $firstName
* #return Users
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get firstName
*
* #return string
*/
public function getFirstName()
{
return $this->firstName;
}
function __construct($firstName){
$this->setFirstName($firstName);
}
function save(){
$em = $this->getDoctrine()->getManager();
$em->persist($create);
$em->flush();
}
}
And my controller method is like:
public function test(){
$create = new Users('Rajat');
$create->save();
}
Your save method is attempting to call
$this->getDoctrine();
Whereby $this is the current Class, and any other Class it inherits. As it stands, your current Class, User, is standalone, and does not have a getDoctrine() method. If your Class were to extend the Controller Class, it would have access to that method:
class User extends Controller
I believe this simple fix will work, although it probably doesn't make real sense for it to extend Controller, as it is a User Entity, and unrelated to a Controller. A preferred, more advanced method, would be to inject the Doctrine service into the User class.
Ok, first of all Doctrine Entities :
Handle the entity generation and configuration
Declare the operations on the setters and getters.
If you wana save an object into your entity there it's your User, you have two way to store this user:
One:
You can use entity manager to store a user and the entity will help you to create the right object using the seters and getters:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use PATH\TO\Users;
class ExampleController extends Controller
{
public function examplefunction()
{
$em = $this->getDoctrine()->getManager();
$entity = new Users();
$entity->setFirstName('Rajat');
$em->persist($entity);
$em->flush();
}
}
The other way is to create this entry using QueryBuilder but it's a bad way in your case.
Oh, i forgot please delete the save method in your entity Doctrine manager allready implement it.
Your controller probably doesnt extends Symfony\Bundle\FrameworkBundle\Controller\Controller ...
You should have controller defined like this example:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
}
Entity class does not extends ContainerAware / Controller, so you can't call $this->getDoctrine()->getManager(). I don't think your Entity class should extend to a Controller. Because your entity class will become a controller instance just because you want to access the doctrine manager. That's a not good practice. What you can do is inject doctrine manager to your Entity class through services.
I wrote a blog few weeks ago regarding injecting services container and accessing through constructor. You can inject doctrine entity manager in the same way you inject services container. You can take a look at that if you like :- http://anjanasilva.com/blog/injecting-services-in-symfony-2/
Here's a nice question regarding injecting doctrine manager. Make sure you read the answer as well. :- Symfony 2 EntityManager injection in service
And another nice tutorial on injecting custom repository manager instead of injecting the whole entity manager. Which I believe even a good solution. :- http://php-and-symfony.matthiasnoback.nl/2014/05/inject-a-repository-instead-of-an-entity-manager/
Hope this helps to increase your understanding about Symfony 2.
Cheers!

Get entityManager inside an Entity

I'd like to use, something like:
$em = $this->getEntityManager();
Inside a Entity.
I understand I should do this as a service but for some testing purposes, I want to access it from an Entity.
Is it possible to achieve that?
I've tried to:
$em = $this->getEntityManager();
$profile_avatar = $em->getRepository('bundle:Perfils')->findOneByUser($this-getId());
But isn't working.
Fatal error: Call to undefined method
Proxies\webBundleEntityUserProxy::getEntityManager() in
/opt/lampp/htdocs/web/src/Pct/bundle/Entity/User.php on line
449
Why am I trying to do it this way?
I've 3 kinds of users: Facebook, Twitter and MyOwnWebsite users. Each of them have differents avatar which links facebook's profile, twitter's or otherwise, if its myownwebsite user, I retrieve the avatar from a URL in a database. For now, I don't want to create a service, because I'm just trying to make it working, to test it, not to create a final deployment. So this is why I'm trying to call Entity manager from an Entity. I don't want, by now, to modify configuration files, just this entity.
As pointed out (again) by a commenter, an entity manager inside an entity is a code smell. For the OP's specific situation where he wished to acquire the entity manager, with the least bother, a simple setter injection would be most reliable (contrary to my original example injecting via constructor).
For anyone else ending up here looking for a superior solution to the same problem, there are 2 ways to achieve this:
Implementing the ObjectManagerAware interface as suggested by https://stackoverflow.com/a/24766285/1349295
use Doctrine\Common\Persistence\ObjectManagerAware;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Entity implements ObjectManagerAware
{
public function injectObjectManager(
ObjectManager $objectManager,
ClassMetadata $classMetadata
) {
$this->em = $objectManager;
}
}
Or, using the #postLoad/#postPersist life cycle callbacks and acquiring the entity manager using the LifecycleEventArgs argument as suggested by https://stackoverflow.com/a/23793897/1349295
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
*/
class Entity
{
/**
* #ORM\PostLoad
* #ORM\PostPersist
*/
public function fetchEntityManager(LifecycleEventArgs $args)
{
$this->setEntityManager($args->getEntityManager());
}
}
Original answer
Using an EntityManager from within an Entity is VERY BAD PRACTICE. Doing so defeats the purpose of decoupling query and persist operations from the entity itself.
But, if you really, really, really need an entity manager in an entity and cannot do otherwise then inject it into the entity.
class Entity
{
private $em;
public function __contruct($em)
{
$this->em = $em;
}
}
Then invoke as new Entity($em).
Best way is to use Life Cycle: #ORM\HasLifecycleCallbacks
And you can use the appropriate Event as you want to get result:
#postLoad
#postPersist
...
Calling the Entity Manager from inside an Entity is a bad practice! You should keep your entities as simple as possible.
For what purpose do you need to call the Entity Manager from an Entity?
What I think you should do is, instead of using the Entity Manager inside your entity, is to create a custom repository for your entity.
In your entity ORM file, add an entry as follows (or in your entity class annotations if not using YML):
App\Bundle\Profils:
# Replace the above as appropiate
type: entity
table: (your table)
....
repositoryClass: App\Bundle\CustomRepos\ProfilsRepository
# Replace the above as appropiate.
# I always put my custom repos in a common folder,
# such as CustomRepos
Now, create a new PHP class that has the namespace above:
//Your ProfilsRepository.php
<?php
namespace App\Bundle\CustomRepos;
use Doctrine\ORM\EntityRepository;
class ProfilsRepository extends EntityRepository
{
/**
* Will return the user url avatar given the user ID
* #param integer $userID The user id.
#return string The avatar url
*/
public function getUserProfile($userId)
{
$em = $this->getEntityManager();
$qb = $em->createQueryBuilder();
$qb->select... (your logic to retrieve the profil object);
$query = $qb->getQuery();
$result = $query->getResult();
return $result;
}
}
Finally, in your Controller:
// Your controller
<?php
namespace <class namespace>;
...
use App\Bundle\CustomRepos\ProfilsRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
...
class YourClassNameController extends Controller
{
public function yourAction()
{
$userId = <get the user ID>;
// Pass the name of your entity manager to the
// getManager function if you have more than one and
// didn't define any default
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('Profils');
$avatar = $repo->getUserProfile($userId);
...
}
}
You need to set the services.yml with:
services:
your_service_name:
class: AppBundle\Controller\ServiceController
arguments: [ #doctrine.orm.entity_manager ]
You need to set also the Controller with the following constructor:
public function __construct(\Doctrine\ORM\EntityManager $em)
{
$this->em = $em;
}
and use $this->em in the controller
(for example $connection = $this->em->getConnection();)

Resources