Using Doctrine 2 Annotation reader for customized annotations - php

I'm pretty new to Doctrine 2 and am using annotations to do my database mapping. I want to take things a little bit further and use some custom annotations. The aim is to be able to make forms and such that can have settings created through annotations. I'm having trouble reading ANY annotations - even class ones such as #Table aren't being returned from the parser.
I am using Codeigniter 2 and Modular Extensions. In my controller I have:
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setDefaultAnnotationNamespace('MyCompany\Annotations');
$reflClass = new ReflectionClass('models\User');
$classAnnotations = $reader->getClassAnnotations($reflClass);
print_r($classAnnotations);
Which returns an empty array.
I then have a file in my libraries/annotations folder, Bar.php:
namespace MyCompany\Annotations;
class Bar extends \Doctrine\Common\Annotations\Annotation
{
public $foo;
}
and lastly, my user model:
/**
* #Entity
* #Table(name="user")
* #MyCompany\Annotations\Bar(foo="bar")
* #MyCompany\Annotations\Foo(bar="foo")
*/
class User {
}
I am trying to follow this example:
http://www.doctrine-project.org/projects/common/2.0/docs/reference/annotations/en#setup-and-configuration
Thanks for your help in advance!
Mark.

use
Doctrine\Common\Annotations\AnnotationRegistry
AnnotationRegistry::RegisterLoader($universalClassLoader);
AnnotationRegistry::RegisterFile(__DIR__ . ' PATH_TO_DoctrineAnnotations.php ');

As you've figured out, you need to include your custom annotation files/classes before you can use them.
Though including them in your controller would work, why not do it the Doctrine way!
Doctrine2's ORM has a file called DoctrineAnnotations.php in the Doctrine/ORM/Mapping/Driver/ folder. It looks like this:
...
require_once __DIR__.'/../GeneratedValue.php';
require_once __DIR__.'/../Version.php';
require_once __DIR__.'/../JoinColumn.php';
require_once __DIR__.'/../JoinColumns.php';
require_once __DIR__.'/../Column.php';
...
So, what I've done is created a similar file in my library and load my annotations by including this "driver" (eg. in your bootstrap).
In my ZF-based app (using Guilherme Blanco's fabulous Zf1-D2 set-up), I just added my "annotations driver" to my application.ini, like this (all on one line):
resources.doctrine.orm.entityManagers.default
.metadataDrivers.annotationRegistry.annotationFiles[]
= APPLICATION_PATH "/path/to/my/library/ORM/Mapping/Driver/MyAnnotations.php"

Related

Codeception DataFactory interaction between helper and other factories

I'm looking into using DataFactory's in Codeception for seeding of data, and for use in our acceptance tests. In the documentation there's mention of 2 approaches, one using the helper file and one using factories files.
We load both options using this snippet from our acceptance.suite.yml
class_name: AcceptanceTester
modules:
enabled:
- Db
- WebDriver
- \Helper\Acceptance
- Doctrine2:
connection_callback: getEntityManager
- DataFactory:
factories: tests/_support/factories
depends: Doctrine2
- \Helper\Factory
Both of the options seem to load correctly. As per the documentation I can then define factories like this, which will allow interaction with Doctrine.
// tests/_support/Helper/Factory.php
class Factory extends Module
{
/**
* #param array $settings
* #throws \League\FactoryMuffin\Exceptions\DefinitionAlreadyDefinedException
* #throws \Codeception\Exception\ModuleException
*/
public function _beforeSuite($settings = [])
{
/** #var Module\DataFactory $factory */
$factory = $this->getModule('DataFactory');
/** #var EntityManager $em */
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(User::class,[
// generate random user name
'name' => Faker::name(),
]);
parent::_beforeSuite($settings);
}
}
As per the other option, I can also create factories by loading all files from within tests/_support/factories, such as below:
// tests/_support/factories/seed.php
use League\FactoryMuffin\Faker\Faker;
/** #var \League\FactoryMuffin\FactoryMuffin $fm */
$user = $fm->create(User::class);
dd($user);
However, the seed.php version cannot seem to share the Factory, and errors with:
The model definition 'User' is undefined.
I wondered if maybe this could be solved by moving the Factory.php logic into the initialize() method but this seems to be called before FactoryMuffin has been initiliazed.
The documentation for this with codeception seems a bit sparse, and the FactoryMuffin docs, while better, don't cover Codeception integration. Just trying to work out if i'm missing something, or I just need to repeat the code in each place if I want to use both files/methods.
This is an old question and technology moves fast so the documentation has likely changed since this was originally asked but I'll make an attempt in case anyone else stumbles across it like I did.
You're using the DataFactory module which is great as it comes with the integration for Codeception out of the box. The two methods you've described are actually ways of integrating DataFactory with your data. By creating factory files, you've given DataFactory a means of generating data. But what if you have some data already in the database that you'd like to use in your tests as well? That's where you would use the Helper class. According to the DataFactory Module docs:
In cases you want to use data from database inside your factory definitions you can define them in Helper. For instance, if you use Doctrine, this allows you to access EntityManager inside a definition.
As for your issue of seed.php not finding the User model, you need to specify it according to the definition given in your factory. For example, if your factory file looks similar to this
<?php
use League\FactoryMuffin\Faker\Facade as Faker;
$fm->define('app\models\User')->setDefinitions([
'name' => Faker::name(),
... // the rest of your properties here
]);
Then seed.php would look like
// tests/_support/factories/seed.php
use League\FactoryMuffin\Faker\Faker;
$user = $fm->create('app\models\User');
Once you have the DataFactory module installed and configured, you can simply call it within the appropriate testing suite via have, haveMultiple, or make. See the Codeception Docs

In phpdoc, define class properties in separate file

I am using a framework that allows adding new components to the framework's base class. Is there any way to document these new methods without changing the frameworks files so I can click through to the method in my IDE.
I highly recommend against trying to inject a subclass. If the framework instantiates the class you're extending directly, you'll need to find a way to get it to use your subclass instead.
NetBeans and PhpStorm (and probably many others) will combine elements from multiple definitions of the same class/interface. This allows you to add properties and methods to any existing class without modifying the original source.
/**
* Framework controller base class.
* Provides helpers via __call().
*/
class Framework_Controller { ... }
Now create a file in your code base that you never require containing the same class definition. Your IDE should still parse it and merge its elements with the class above:
if (false) { // Safety first!
/**
* ACME Co. controller base class.
*
* #method ACME_Model_User getUser Load user via authentication helper
*/
class Framework_Controller { /* nothing to add */ }
}
You can try using an interface and declaring the methods normally instead of with #method. That is what we did with Zend_View since it's already an interface. I haven't tried mixing class with interface to see if PhpStorm likes it.
It depends mainly on IDE you are using. I think you should extend base class and add there some new methods/properties with phpdoc comments. Of course changing framework's files is no solution as you already mentioned
You could extend the original class, redefine the function, with new doc, and call the parent function.
e.g.
class newClass extends originalClass
{
/**
* New PHPDoc
*/
function functionName($a)
{
return parent::functionName($a);
}
}
Try to use #see or inline #link directives.

Using Doctrine with static YAML databases in Symfony2

I am familiar with PHP, but only just learning Symfony2 and Doctrine. I am wondering what the best practice is for static data, as in data that is only ever updated when deploying a new version of the web application to production.
I would prefer to specify static data (not schemas) in YAML, because then modifying that data is easy for everyone, whether they know any PHP/Doctrine or not. I would like for non-developers to be able to add an achievement by modifying the .yml file. An example of a static YAML database that I would like to maintain is:
Achievements:
Conservative:
Difficulty: 2
Description: >
Description of Conservative Achievement.
Dedicated:
Difficulty: 3
Description: >
Description of Dedicated Achievement.
Persistent:
Difficulty: 2
Description: >
Description of Persistent Achievement.
Now imagine I have an entity representing a User
// src/Paulpro/ExperimentingBundle/Entity/User.php
namespace Paulpro\ExperimentingBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
class User {
protected $name;
protected $achievements;
public function __construct(){
// Collection of achievements as defined by achievements.yml
// $this->achievements = new ArrayCollection();
}
}
I want to use Doctrine as normal for Users, so that they are stored in the database and I want Users to be able to earn achievements. A user can have multiple of each achievement, so in my User entity I need some way to represent a collection of achievements with quantities. I do not want the achievements difficulties and descriptions to be stored in the database, only in the .yml file, unless there is a good reason to store the achievements themselves in the database and a good way to import the static data into the database as part of automatic deployment.
I have three main questions related to this problem:
Is there a better way to do this, keeping in mind that I want non-developers to be able to add achievements easily and I will probably want to overwrite the achievements.yml file for different locales?
Where in my Symfony2 bundle should I put the achievements.yml file(s)?
How should I modify the User entity so that the resulting database can maintain quantities of achievements per user?
I do not want the achievements difficulties and descriptions to be
stored in the database, only in the .yml file, unless there is a good
reason to store the achievements themselves in the database and a good
way to import the static data into the database as part of automatic
deployment.
A good reason: It will be easier to manage the relations between Users and Achievements.
A way to import the static data into the database: DoctrineFixturesBundle
1. Define your static configuration
The best way to do so, is to expose a semantic configuration.
In your case, you will have the following 2 files:
// src/Paulpro/ExperimentingBundle/DependencyExtension/Configuration.php
<?php
namespace Paulpro\ExperimentingBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
/**
* Defines the configuration tree for the bundle
*
* #return \Symfony\Component\Config\Definition\Builder\TreeBuilder
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('paulpro_experimenting');
$rootNode
->children()
->arrayNode('Achievements')->addDefaultsIfNotSet()
->children()
->arrayNode('Conservative')->addDefaultsIfNotSet()
->children()
->integerNode('Difficulty')->defaultValue(2)->end()
->scalarNode('Description')->defaultValue('Description of Conservative Achievement.')->end()
->end()
->end()
->arrayNode('Dedicated')->addDefaultsIfNotSet()
->children()
->integerNode('Difficulty')->defaultValue(3)->end()
->scalarNode('Description')->defaultValue('Description of Dedicated Achievement.')->end()
->end()
->end()
->arrayNode('Persistent')->addDefaultsIfNotSet()
->children()
->integerNode('Difficulty')->defaultValue(2)->end()
->scalarNode('Description')->defaultValue('Description of Persistent Achievement.')->end()
->end()
->end();
return $treeBuilder;
}
}
and:
<?php
namespace Paulpro\ExperimentingBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
class PaulproExperimentingExtension extends Extension
{
/**
* Load the configuration for the bundle
*
* #param array $configs
* #param \Symfony\Component\DependencyInjection\ContainerBuilder $container
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
foreach($config as $key => $value)
{
$container->setParameter('paulpro_experimenting.'.$key, $value);
}
}
}
Doing so, you will be able to have a better way to manage how your users are using the config. To see a sample of the default config result, you can use the command:
php app/console config:dump-reference PaulProExperimentingBundle
That should result as following:
Default configuration for "PaulProExperimentingBundle"
paulpro_experimenting:
Achievements:
Conservative:
Difficulty: 2
Description: Description of Conservative Achievement.
Dedicated:
Difficulty: 3
Description: Description of Dedicated Achievement.
Persistent:
Difficulty: 2
Description: Description of Persistent Achievement.
This means your users can put this sample in the config.yml under the app\config folder and change it depending on their need. The only condition is that whatever information they put in this file has to be validate by the Configuration tree you have defined.
2. Define your entities
Define the Achievement entity as it best fits your need. You could for example use:
// src/Paulpro/ExperimentingBundle/Entity/Achievement.php
namespace Paulpro\ExperimentingBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
class Achievement{
protected $name;
protected $difficulty;
protected $description;
/**
* #ManyToMany(targetEntity="User", mappedBy="achievements")
* #JoinTable(name="users_achievements")
**/
private $users;
public function __construct() {
$this->users = new ArrayCollection();
}
}
You will keep your User entity the way it is except you have to add the relation to Achievement:
// src/Paulpro/ExperimentingBundle/Entity/User.php
namespace Paulpro\ExperimentingBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
class User {
protected $name;
/**
* #ManyToMany(targetEntity="Achivement", mappedBy="users")
**/
protected $achievements;
public function __construct(){
$this->achievements = new ArrayCollection();
}
}
3. Fill up the database when deploying the app
This is the last step and it uses exclusively the DoctrineFixturesBundle.
You will have to create the fixture for your Achivement entity:
// src/Paulpro/ExperimentingBundle/DataFixtures/ORM/LoadAchivementData.php
<?php
namespace Paulpro\ExperimentingBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Paulpro\ExperimentingBundle\Entity\Achivement;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class LoadTypesData implements FixtureInterface, ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function load(ObjectManager $manager)
{
foreach($this->container->getParameter('paulpro_experimenting.Achievements') as $key => $value)
{
$achivement = new Achivement();
$achivement->setName($key);
$achivement->setDifficulty($value['Difficulty']);
$achivement->setDescription($value['Description']);
$manager->persist($achivement);
}
$manager->flush();
}
}
This fixture will go through the config for paulpro_experimenting.Achievements and load the defined Achievements from here.
Lastly, to load the data in the database, you will have to run the following command:
php app/console doctrine:fixtures:load
Et voila, you should now be able to add/remove achievements from your users.
First things first,
If you are concerned about what the achievement thing say or the language used, you can use the translator component. Basically what it does is to store your strings in translation files (YAML). So, where you want to add some string, say the achievement name you add some key achievement.name and the translator looks for the string for that key. You can have multiple translations and Symfony automatically picks the right one based on the locale of the client. The translator is built into the templates so there is nothing to add but you can use it as well anywhere on the framework.
If you want to get data from files (maybe you want not only the translation but also some structured data), there are some things you can do, you don't actually need Doctrine for this.
1) Using config.ini
On every Symfony2 project there is a fille called config.ini (on newer versions of the framework it changed to config.yaml) in the app/config/ directory. That file is where things like database configuration values are meant to be stored BUT you can add as many options as you want, just adding them to the file:
[parameters]
database_driver="pdo_mysql"
database_host="192.168.1.1"
...
secret="..."
my_param= "my value"
Getting those values is easy, from a controller just call:
$this->container->getParameter('my_param'); // returns "my value"
It might work for you, but depending on what kind of data you want to store (for ini files there is just key/value way to go, and for the newer versions using yaml files, I'm just not sure if the framework would parse a hierarchical structure for you (it might, give it a try)).
Other thing to keep in mind is that if someone deletes a configuration value your system will come down (probably after the cache is manually cleared) and there is sensitive data (a.k.a. passwords) that we generally don't want everybody to know!
2) Using YAML component of Symfony2
First create your YAML file in whatever way it makes sense to you, then place it somewhere on your server (maybe in "resources" or "config" dir?)
Then, if you are on a controller, just write something like:
//This line goes at the begining of your file where all the *use*s are
use Symfony\Component\Yaml\Yaml;
$file = __DIR__.'/../Path/To/file.yml'; //case sensitive on Unix-like systems
if(!file_exists($file)){
throw new HttpException(404, "File not found.");
}
//This line does the magic
$index = Yaml::parse(file_get_contents($file));
//Now $index contain an associative array containing all your data.
And you're done! I've used this to load things like menus or generating new sections on a website without touching the code or the database, let your imagination fly!
You can create a console command that import data (from any source you can imagine) to your database and then use that command in some script as part of your deployment process, it's up to you decide if it's worth the effort.
Finally let's just talk about the entity relations thing.
It's not all clear what you are trying to do, but there is no (clean/nice) way to add relationships between a Doctrine entity and a random configuration file. The problem is that they are meant to perform different roles in the app.
I'd suggest you do something like this:
On your database:
Add a table "achievements" with an id and a column called nameKey and maybe descriptionKey ... These __Key columns will contain translator keys (as mentioned before). I.e:
"ID":1, "nameKey":"achievement.1.name", "descriptionKey":"achievement.1.description"
"ID":2, "nameKey":"achievement.2.name", "descriptionKey":"achievement.2.description"
"ID":3, "nameKey":"achievement.3.name", "descriptionKey":"achievement.3.description"
Then modify your User so it links to the achievements table.
Keeping this in your database will help you and enable you to use some other nice Symfony tools (like forms, validators, ...) and help you keep integrity in your data.
Using translator
Create a translation file for each lang. you are interested, they will look like:
achievement.1.name: "Best player ever"
achievement.1.description: "Blah blah blah"
achievement.2.name: "Better than average"
achievement.2.description: "Blah blah blah"
just follow the documentation to see how to output your strings translated wherever you want to put them.
To add new records you would need to add a record on the database (if you want to make it easier, just write a small web form for that) and the translation keys.
Symfony is so powerful yet easy that will make you lazy and productive at the same time
I would propose to use a class to represent your Achievements.
I would also propose to use a relational database to store relations between Users and Achievements (logic :) )
I mean that I would prefer to store achievement in database.
Using alice it's quite easy, and your yaml file has not that much to change.
It's a 2 steps process:
modifiy yaml to fit alice needs
create an Achievement entity and add relations between user and it
Achievement:
Conservative:
id: 1
Difficulty: 2
Description: >
Description of Conservative Achievement.
<?php
class Achievement
{
public $id;
public $difficulty;
public $description;
}
Follow then the alice steps, and you have a working solution.
Another possibility is to simply store the Achievement keys as serialized array in your user entity:
<?php
class User
{
/** #ORM\Column(type="array") **/
protected $achievements = array();
public function addAchievementByName($name)
{
$this->achievements[] = $name;
}
function getAchievements()
{
$self = $this;
$allAchievements = Yaml::load(__DIR__.'/path/yo/yaml/file');
return array_flip(array_filter(array_flip($allAchievements), function($achievement) use ($self) {
return in_array($achievement, $self->achievements);
}));
}
}

PHP annotations used in YII

Going through the auto generated code of YII for a Model class, I understand that the table columns are injected into the Model class through Annotations (#property)
<?php
/**
* This is the model class for table "tbl_project".
*
* The followings are the available columns in table 'tbl_project':
* #property integer $id
* #property string $name
*/
class Project extends CActiveRecord
{
Here property $id and $name become part of the Project class and can be accessed like such:
$proj = new Project();
$proj->id = 1;
I tried to look up annotations in PHP but found nothing as all links point to either PHPDoc . I am more interested in the Dependency Injection part of it. Can someone explain the concept please and point to a list of available annotations.
Yii does not use annotations.
It uses table schema extracted from database.
If you remove annotations all will work.
This would be interesting for you http://www.yiiframework.com/doc/api/1.1/CDbTableSchema
And here are some instructions how to speed up your app. One of ways is to enable schema caching. http://www.yiiframework.com/doc/blog/1.1/en/final.deployment
The Block comments in are only to use with PHPDoc or for your own sense.
Although my IDE (PhpStorm) uses the phpdoc block comments and its properties for code inspection and code hinting.
As said in the comments, Yii does not parse the block comments for dependency purposes.

Do I really need to move/write entities myself with Doctrine 2?

I write my entities in let's say models/ folder.
something like:
namespace Organisation\User;
/**
#Entity
*/
class Customer {
/**
* #Column(type="integer") #GeneratedValue
*/
protected $_id;
}
}
So I'll instantiate my entity with $customer = new \Organisation\User\Customer();
Ok, but if I use doctrine orm:generate-entities library/ it will generate it under the following directory :
library/Organisation/User/Customer.php
ANd that's ok, but if I look at the code, there aren't any of my annotation, and therefore when I try to use it, I get doctrine\ORM\Mapping\MappingException: Class Organisation\User\Customer is not a valid entity or mapped super class. because there aren't any annotation.
So what I need to do is to remove the namespace, generate into the same directory as the entities with metadata inforations are, move to my library folder, and add the namespace to work with.
It looks ugly, do I have missed something?
edit: I forgot to tell that orm:generate-entities doesn't work recursively, then, I can't even use my actual structure within my entities metadata
If you've written your entity classes already, why are you trying to generate them?
They typical way to drive a new project is to write your annotated entity classes, and use orm:schema-tool:create to generate your database schema.
Most examples I've seen do stick the Entities under library/, in some nested directory based on the namespaced name of the class, just as you've described. This is generally a good thing, as it works with the default Doctrine2 autoloader setup.
If you're not trying to fit Doctrine2 onto an already existing database schema, I'd recommend that you simply stick all your entity classfiles someplace like library//Entity/.php, and use orm:schema-tool:update and orm:schematool:update to manage the database for you.
Use the arg --generate-annotations.

Categories