Add a custom function in a vendor repository - symfony

I'm using CCDNForum's ForumBundle (the bundle itself is not relevant, my question isn't related to this bundle specifically but is more general) in my Symfony website, and I want to customize it, so naturally I created a AcmeForumBundle whose parent is CCDNForumForumBundle. The only thing I want to change is to add a new custom function in let say the CategoryRepository associated to the Category entity.
So I created a CategoryRepository.php in my AcmeForumBundle extending the CategoryRepository.php of CCDNForum, and this is not sufficient because the default repository of the Category entity is CCDNForum's CategoryRepository.
The next thing I did was to create a new AcmeForumBundle Category entity extending CCDNForum's Category entity, changing of course the default associated repository
namespace Acme\ForumBundle\Entity;
use CCDNForum\ForumBundle\Entity\Category as BaseCategory;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection as ArrayCollection;
/**
* #ORM\Entity
* #ORM\Entity(repositoryClass="Acme\ForumBundle\Repository\CategoryRepository")
* #ORM\Table(name="CC_Forum_Category")
*/
class Category extends BaseCategory
{
}
I added the #ORM\Table line because I had an SQL error "acme_category" table not found. The class is of course empty as I have nothing to change in the entity.
Using this code everything works fine, I added my custom function in the CategoryRepository without problem, but the problem comes when I want to update the database schema. I have the error when running php app/console doctrine:schema:update --dump-sql
[Doctrine\DBAL\Schema\SchemaException]
The table with name 'acme.cc_forum_category' already exists.
which I can understand since CCDNForum's Category entity and my Acme Category entity are using the same table (the C_Forum_Category table).
My question is: am I doing all this right ? Isn't there a simpler way to add a custom function in a vendor repository ?
Thanks!

Related

Doctrine: Custom repository method with inheritable entity

I have a base entity class called User, which has 3 possible different child-classes Employee, Customer and Student. This is implemented with a single table inheritance.
The base class User has two fields called nameand prename, which all the child classes inherit.
Now my problem: I have a custom repository called UserRepository with the method findByFullTextSearch(string $searchterm): User[] in which I implemented a somewhat Full-Text-Search on those two fields to fit my needs (unfortunately, I do not have the possibility to use DoctrineExtensions for this project to implement the MySQL function FIND_IN_SET or an actual Full-Text-Search).
Is there any possibility for me to only write this method once in UserRepository, so when I use $doctrine->getRepository(Student::class)->findByFullTextSearch('john doe'); I only get results fitting to the Repository i called it from?
My workarounds so far:
Implement all repository classes, pass get_called_class() or $this->_entityName to parent class.
Call the base repository class and pass the child entity class as parameter to findByFullTextSearch
Here I found a related question, but not quite the same.
Is there a better solution to this? Thanks in advance.
I just found the solution by simply adding the base class as repository for each child entity:
<?php
namespace UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* UserBundle\Entity\Customer.
*
* #ORM\Entity(repositoryClass="UserBundle\Repository\UserRepository")
*/
class Customer extends User
{
// ...
This way I wont need empty child repositories just for doctrine to find a possible entry point.

Symfony assert type vs One-to-One mapping

In the Symfony documentation about Embed forms, I just read this :
class Task{
/**
* #Assert\Type(type="AppBundle\Entity\Category")
* #Assert\Valid()
*/
protected $category;
// ...
}
They later say that
The Category instance is accessible naturally via $task->getCategory()
and can be persisted to the database or used however you need.
How is that different from a Many-To-One mapping ? (many tasks for one category of course)
Well, ORM mapping map the php class to the doctrine metadata.
Assert is a mecanism to validate objects.
It means you could use assert on objects wich are not entities or you could not use a mapped field in your formType
ManyToOne map an object to another from the doctrine point of view.
Assert\Type indicate that this attribute of your form is validated like another related object, wich is Category

Doctrine2 bug imports 'private' variables from MappedSuperClass when generating entities

I'm using Symfony's entity classes in conjunction with Doctrine's ORM annotation to persist the values to the database. Most tables need a few standard fields, so I have created a base entity that all other entities can extend. According to the documentation this is called a MappedSuperClass: http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html
// src/Acme/Bundle/Entity/Base.php
namespace Acme\Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\MappedSuperclass
* #ORM\HasLifecycleCallbacks()
*/
class Base {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// more values...
}
I then create multiple entities that extend this base:
// src/Acme/Bundle/Entity/View.php
namespace Acme\Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
View extends Entity\Base
{
// entity definitions
}
Is this the best way to set default definitions for an entity? If you have better suggestions, let me know.
Next, when I generate entities via:
php app/console doctrine:generate:entities Acme\Bundle
It works great the first time to create the getters and setters, but if I make changes to an entity and generate again, it gives an error like this:
Fatal error: Access level to Acme\Bundle\Entity\View::$id must be protected (as in class Acme\Bundle\Entity\Base) or weaker in /src/Acme/Bundle/Entity/View.php
This is happening because doctrine:generate:entities is importing the 'protected' variables from the MappedSuperClass into the extending entity as 'private'.
Others have complained about this error in other contexts without a solution:
FOSUserBundle generate:entities does not work, Access level of fields too high
https://github.com/FriendsOfSymfony/FOSUserBundle/issues/102
QUESTION: Is doctrine supposed to be importing the protected variables? And is it supposed to be setting them as "private"? Or is this just a known Symfony bug?
It seems like it should not import the protected variables since the #ORM definitions are in the MappedSuperClass already and those are not imported (and when I delete the imported private variables it works fine). But if it does import them, it should not be setting them as private...
I literally have to do a search and replace through all my entities to delete these. Every. Single. Time.
What is the suggested course of action here? If this is a bug, has someone reported this and what is the timeline for fixing? How should I search for this issue on github and report it if it's not reported?
Question 2: As long as this bug exists, is there a way to just generate the getters/setters on a Single Entity?
e.g. php app/console doctrine:generate:entities Acme\Bundle\Entity\View (this doesn't work of course). If I could generate only one entity at a time, it would be less of a hassle deleting all the imported private variables across all my entities.
[EDIT: I have answered this question below]
The answer to #2 is, yes, you can generate entity getters/setters for just one single entity.
From the command line, if you type:
php app/console doctrine:generate:entities --help
You get a list of options. There you will see how to limit entities to bundle, single entity in a bundle, or an entire namespace:
You have to limit generation of entities:
* To a bundle:
php app/console doctrine:generate:entities MyCustomBundle
* To a single entity:
php app/console doctrine:generate:entities MyCustomBundle:User
php app/console doctrine:generate:entities MyCustomBundle/Entity/User
* To a namespace
php app/console doctrine:generate:entities MyCustomBundle/Entity
For the question 1, you need to redefine each primary key when entities are inherited.

How can I use Doctrine Annotations to change a column name in an entity subclass?

I'm using FOSUserBundle with a new Symfony project that has to work with an existing schema. My user entity extends FOS\UserBundle\Entity\User as the instructions say, but the email column is named "email_addr" instead of "email". Since the parent defines $email I can't re-declare it to attach my annotation.
/**
* #var string $emailAddr
*
* #ORM\Column(name="email_addr", type="text", nullable=false)
*/
protected $email;
The exception I get is:
[Doctrine\ORM\Mapping\MappingException]
Property "email" in "Foo\DataBundle\Entity\User" was already declared, but it must be declared only once
My question is either:
How can a Doctrine2 subclass use an annotation to alter something defined in the parent?
Or how can I override column names in the FOSUserBundle?
I found an answer:
More about Doctrine implementations
If you need to change the mapping (for instance to adapt the field names to a legacy database), the only solution is to write the whole mapping again without inheriting the mapping from the mapped superclass. In such case, your entity should extend directly from FOS\UserBundle\Model\User (and FOS\UserBundle\Model\Group for the group).
It looks like doctrine 2.3 added these features. Unfortunately, SO decided that I had to duplicate information already present on their site to avoid a "trivial answer".
#AssociationOverride and #AttributeOverride in new Doctrine 2.3

Running "bin/vendors install" with local modifications on the "vendors" folder in Symfony2

In Symfony2, when I run the command bin/vendors install I get the following message:
?? src/Symfony/Component/Validator/Constraints/Alphanumeric.php
?? src/Symfony/Component/Validator/Constraints/AlphanumericValidator.php
?? src/Symfony/Component/Validator/Constraints/GreaterThan.php
?? src/Symfony/Component/Validator/Constraints/GreaterThanValidator.php
"symfony" has local modifications. Please revert or commit/push them before running this command again.
The files listed, are custom constraint validators created by me following the cookbook entry here.
Is there a way to update the deps files ignoring the changes I made? My goal is to install a new bundle while keeping the constraint validator files created by me.
UPDATE: Peter solution was right, the only thing left is to "use" the correct namespace inside the entity like this:
(The code has words in Spanish and I will assume again that I'm in the DemoBundle just for consistency)
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Acme\DemoBundle\Component\Validator\Constraints as CustomAssert;
/**
* #ORM\Entity
*/
class Employee
{
//...
/**
* #ORM\Column(type="string", length=20)
* #Assert\NotBlank()
* #CustomAssert\Alphanumeric()
*/
protected $alfanum;
//...
}
You should never change anything contained in the vendor directory. If you have new feature to add (and you always should have to), put these features in your application specific bundles (in the src directory).
Your namespaces/classes scheme should follow the framework convention. For instance your constraint validator should go in 'src/MyNamespace/MyBundle/Validator/Constraint' (and your namespace should be 'MyNamespace\MyBundle\Validator\Constraint').
Notice that the vendor directory should be ignored by your version manager.
No, don't do this. Custom validators (or custom anything) you write should be a part of your namespace, not Symfony's. And they certainly shouldn't be vendors directory at all (unless you write your own vendor). This is the whole point of namespacing and vendor management - to avoid collisions!
So, you need to move your custom validators to your application's source. Using the AcmeDemoBundle as an example...
Create directories for the following path
src/Acme/DemoBundle/Component/Validator/Constraints
And then move your custom validators into this folder. Then update the namespace for each validator class accordingly
namespace Acme\DemoBundle\Component\Validator\Constraints
Cheers
You can put your custom validator in your own bundle and reference it in following way,
# in validation.yml
# define namespace identifier
namespaces:
namespace_name: Path\To\Your\Validator\Namespace\
# then in your entity
FQCN\Of\Entity:
constraints:
- Path\To\Your\Validator\Namespace\Alphanumeric
# or
- "namespace_name:Alphanumeric": ~

Resources