I am new to Symfony2 and I am porting an old Symfony1 app to v2.
In my old app, I defined all of my models in one great big YML file. So the various 'components' were able to reference the same name value pairs (stored in the YML file).
I am now factoring out the functionality into bundles, and I want to completely decouple the bundles. I still need to access the name value pairs, but I want to store it centrally - in a database this time.
I want to keep my code DRY and so want to write the code for accessing the name value pairs only ONCE and some how use that in the separate bundles.
I also, want to provide centralised CRUD facilities for maintaining the name value pairs.
To summarise, my 2 questions are as follows:
How can I provide functionality (implemented once) to access name value pairs stored in a database, and make this functionality available to bundles that require it?
What is the best way to provide CRUD functionality for maintainance of the name value pairs? (is it by creating yet another bundle?)
I am not sure
You can create a Doctrine entity and request the keys you need. In order to code DRY, use a base entity and extend it to each pair type:
Pair entity:
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #MappedSuperclass
*/
class Pair
{
/**
* #Column(type="string", unique=true)
* #var string
*/
protected $name;
/**
* #Column(type="string")
* #var string
*/
protected $value;
// Getters & setters
// ...
}
SportPair entity:
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #Entity(repositoryClass="MyBundle\Entity\PairRepository")
* #Table(name="sport_pairs")
*/
class SportPair extends Pair
{
/**
* #Id
* #GeneratedValue(strategy="AUTO")
* #Column(name="id", type="integer")
* #var int
*/
protected $id;
}
Pair repository:
namespace MyBundle\Entity;
use Doctrine\ORM\EntityRepository;
class PairRepository extends EntityRepository
{
private $cache = array();
public function getValue($key)
{
if (!isset($this->cache[$key])) {
$pair = $this->findOneBy(array('name' => $key));
if (null !== $pair) {
$this->cache[$key] = $pair->getValue();
}
}
return $this->cache[$key];
}
}
Use SensioGenerationBundle to generate CRUD to manager Pair entities.
That's how you should proceed:
Create your application bundle (MyAppBundle).
(optional) Create a shared bundle (MySharedBundle)
Reference the bundles into the application kernel.
Create Pair and PairRepository into MyAppBundle.
(optional) Move those classes to MySharedBundle
Create derived pair classes into MyAppBundle.
Related
I have an entity that stores large files as blobs to the DB.
I would now like to get Symfony to never ever load these blobs unless I specifically request them via the appropriate getter.
In essence I want the same idea as lazy-loading relationships but for a string property.
What I have tried so far is to put all my other properties that hold the file meta data into a trait and then apply that trait to two entities.
namespace App\Entity\Traits;
use Doctrine\ORM\Mapping as ORM;
trait DocumentMetaData
{
/**
* #var int|null
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime|null
*
* #ORM\Column(type="datetime")
*/
private $date_uploaded;
}
One entity has nothing to it but the trait...
namespace App\Entity;
use App\Entity\Traits\DocumentMetaData;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="documents")
* #ORM\Entity()
*/
class Document
{
use DocumentMetaData;
}
...the other has the added blob property:
namespace App\Entity;
use App\Entity\Traits\DocumentMetaData;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="documents")
* #ORM\Entity()
*/
class DocumentFile
{
use DocumentMetaData;
/**
* #var string|null
*
* #ORM\Column(type="blob")
*/
private $blob;
}
Now, if I don't want the blob to be loaded, for example for a listing of files, I simply use the entity that doesn't have the blob.
This approach sort of works but causes issues as I need to point both entities at the same table (see the class level ORM annotations).
Specifically, it makes doctrine freak out when running migrations:
The table with name 'myapp.documents' already exists.
That makes perfect sense and really I'm hoping that someone can point me to a nicer solution.
How can I tell doctrine not to load the blob unless its explicitly asked for?
So as per the comments on my question - the way to do this so that migrations do not break is to leverage doctrine's ability to lazy load relationships between tables.
Basically I had to go and create a new entity that only holds my giant blobs and then establish a one to one relationship between the original entity and the blob entity.
I then set that relationship to load EXTRA_LAZY and as a result I can now control when precisely the blobs of giant data should be loaded.
I don't think this is ideal in terms of normalising the DB design but it works a lot better than anything else so happy with that.
I've got a User Entity defined (mapping in yml)
namespace My\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class User
{
...
And I created a child class that inherits from that entity, so that I can add some custom validation methods and a few fields that I need but do not need to be persisted (e.g. ConfirmPassword, ConfirmEmail fields)
namespace My\SecondBundle\EditModels;
use My\CoreBundle\Entity\User;
class UserModel extends User
{
When the user submit a registration form, I map the request to a UserModel entity, and if it is valid I try to persist the user.
The following code throws an exception
$entityManager->persist($userModel);
//=>The class 'My\SecondBundle\EditModels\UserModel' was not found in the chain configured namespaces My\CoreBundle\Entity
Question: How can I persist $userModel (instance of UserModel) as a User entity class? Possible options:
Do not use an inherited class and add custom fields and validation method to the User entity itself
Copy the fields from the UserModel to the User entity and persist the user entity
I don't think I should use Doctrine inheritance mechanism as I do not want to save the extra fields.
Thank you
I think your problem here, is that you've just configured My\CoreBundle\Entity namespace in Doctrine2, but the entity you actually want to persist is located in My\SecondBundle\EditModels.
Usually when inheriting classes marked as #ORM\Entity() the class you are extending from must have the class annotation #ORM\MappedSuperclass(). But normally you use this for single table inhertiance e.g., not for your usecase.
In my opinion the approach to split database related attributes from the others, is not affordable. I would keep validation related stuff in the model itself - you need it in your create/update action.
I'm not familiar with XML configuration, but when using annotations you need to mark each property to be mapped with database (using #ORM\Column()). So Doctrine will ignore all the other attributes and methods entirely.
So here I share my recently developed AbstractModel for you, to see how I've implemented validation (with respect/validation):
<?php
namespace Vendor\Package\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* Abstract Model
*
* #ORM\MappedSuperclass()
*/
abstract class AbstractModel
{
/**
* #var \Respect\Validation\Validator
*/
protected $validator;
/**
* AbstractModel constructor
*/
public function __construct()
{
$this->validator = static::validation();
}
/**
* Defines validation for this model
*
* #return \Respect\Validation\Validator
*/
public static function validation() : \Respect\Validation\Validator
{
return \Respect\Validation\Validator::create();
}
/**
* Executes validations, defined in validation method.
*
* #return bool
*/
public function isValid() : bool
{
if (is_null($this->validator)) {
$this->validator = new \Respect\Validation\Validator();
$this->validation();
}
return $this->validator->validate($this);
}
}
A model which extends from the AbstractModel needs to implement a static validate method, to define class validation:
<?php
namespace Vendor\Package\Model;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
* #ORM\Table(name="my_model")
*/
class MyModel extends AbstractModel
{
/**
* #var string
* #ORM\Column(type="string")
*/
private $name;
/**
* Defines validation for this model
*
* #return \Respect\Validation\Validator
*/
public static function validation() : \Respect\Validation\Validator
{
return \Respect\Validation\Validator::create()
->attribute('name', \Respect\Validation\Validator::notEmpty()->stringType()->length(null, 32))
;
}
// getter, setter, ...
}
Each entity, persisted to database, will have the $validator property and all these methods, but because I left annotations here (and pretty sure this also works with xml/yaml) Doctrine ignores it.
And this way you also keep validation related stuff out of the model class itself, which is good for readability. The validation itself should be defined in the model itself, imho. But this respect/validation framework is neat way to achive this. Hope this helps :)
I'm creating Symfony project. And now I'm trying to find best practice for adding custom methods.. What is yours?
Visual explanation:
users table
id | name | surname
---+------+--------
1 | John | Smith
2 | Matt | Malone
Entity\User.php
namespace TestBundle\Entity\User;
use Doctrine\ORM\Mapping as ORM;
/**
* User
* #ORM\Table(name="users")
* #ORM\Entity
*/
class User
{
/**
* #ORM\Column(name="id", type="string", length=36)
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=255, nullable=true)
*/
private $name;
/**
* #ORM\Column(name="surname", type="string", length=255, nullable=true)
*/
private $surname;
/**
* OneToMany
*/
private $userCompanies;
{{ Setters and Getters }}
}
Where I should store custom method, like:
function getFullName()
{
return sprintf("%s %s", $this->getName(), $this->getSurname());
}
Or more complex:
function getCurrentUserCompany()
{
foreach ($this->getUserCompanies() as $company) {
if ($company->isActive()) {
return $company;
}
}
return null;
}
Please note, that all data returned via JSON
So far I tried extending class, but annotations not working as expected. Placing custom methods in same file looks trashy, since there will be more than one of them.
But.. but if there is repositoryClass - maby there is place for custom methods as well?
Thanks!
If it's about methods that are used mainly for display purposes then they are very similar to the getters, in my opinion they best fit is in the Entity itself, so inside your User.php class.
The repository is for defining methods for getting the entity from your storage level (DB, cache...), but the view level (your twig) should take the data from the entity itself.
If you need something more complicated or you need to reuse it, like a date filter, then it's better to create a Twig extension.
Methods like that belong to entity class and there is no reason to split code. If many entity classes share some methods, you can always create shared base abstract class or trait for them.
If you really want separated files for sake of your aesthetic, then use traits, but remember that it's not proper and conventional use of them.
I have an entity with a custom id (i.e. UUID) generated on __construct function.
namespace AppBundle\Entity;
use Rhumsaa\Uuid\Uuid;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Person
{
/**
* #ORM\Id
* #ORM\Column(type="string")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
public function __construct()
{
$this->id = Uuid::uuid4()->toString();
}
This entity is used in sonata and also in other part of the project. I need this entity to have id before persisting and flushing it, so I can not use a an auto-increment.
So, the problem is sonata don't let me create entities because it takes the create option as and edit on executing because that entity already has an id, but this entity does not exists at this moment, so it fails.
The problem isn't the library for generating UUID, any value for 'id' fails.
Anyone know how to solve it? Another similar approach to solve the problem?
You shouldn't set your id in the constructor, but rather use the prePersist Doctrine event to alter your entity before persisting it for the first time.
You may use annotations to do so, see the Doctrine Documentation on prePersist.
The issue with setting the id in the constructor is that you may override it when you're retrieving it from the database, in which case it will be incorrect.
I'm searching for a solution for the following problem with a database inheritance using Doctrine 2 built in Symfony 2 framework. This is what I want to do...
I want to create two tables (UredniHodiny, KonzultacniHodiny) with the same interface as the abstract class Hodiny. This is how I'm trying to do it
<?php
// src/CvutPWT/ImportBundle/Entity/Hodiny.php
namespace CvutPWT\ImportBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\MappedSuperclass
*/
abstract class Hodiny
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Osoba")
*/
protected $osoba;
/**
* #ORM\ManyToOne(targetEntity="Mistnost")
*/
protected $mistnost;
/**
* #ORM\Column(type="datetime")
*/
protected $zacatek;
/**
* #ORM\Column(type="datetime")
*/
protected $konec;
}
<?php
// src/CvutPWT/ImportBundle/Entity/KonzultacniHodiny.php
namespace CvutPWT\ImportBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="konzultacnihodiny")
*/
class KonzultacniHodiny extends Hodiny
{
}
<?php
// src/CvutPWT/ImportBundle/Entity/UredniHodiny.php
namespace CvutPWT\ImportBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="urednihodiny")
*/
class UredniHodiny extends Hodiny
{
}
Now when I run php app/console doctrine:generate:entities CvutPWTImportBundle Symfony generates all variables (more precisely columns) from class Hodiny as private variables to both child classes. Now when I'm trying to create those tables with app/console doctrine:schema:update --force I'm getting errors that $id must be protected or weaker. When I change this protection manually I am able to create tables but there is only one column (id). But this is not what I was hoping for. Can somebody give me any advice what I'm doing wrong?
This is not table inheritance. Mapped super classes are just mapping inheritance.
The tables corresponding to your final entities will not be relied together in any way.
If you want real table inheritance (single table or joined table), use this: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/inheritance-mapping.html#single-table-inheritance
If you still want to use mapped super classes, then you will have to put the #ORM\Id definition in both final classes. You can not put ids in mapped super classes.