Symfony2 Doctrine MongoDB rollback - php

I am trying to build a secure of set of tests with Symfony2, Doctrine and MongoDB.
What I need to do is to load a lot of fixtures when a test begin, and unload them once it ends. I thought of doing it with a transaction, but... I couldn't find documentation on how to do it with Doctrine and Mongo!
I found good documentation in the Doctrine docs regarding how to do transactions with the ORM, but not regarding the ODM.
So I took a look at the source code of the Connection.php class used by Doctrine-Mongo too and I haven't found the beginTransaction, commitand rollback methods that the dbal version uses.
I was clueless, then I asked myself "Is it even possible to rollback in MongoDB?", and the answer if found in the MongoDB FAQ was:
MongoDB does not use traditional locking or complex transactions with rollback
:( So I guess that's why there is no beginTransaction or whatsoever in the ODM...
But my problem remains: how can I implement a sort of rollback for my tests?
The only idea I got right now is to manually get all the ids of the Document I load and then remove them in the tearDown(). But, well... it kinda sucks, doesn't it?
Other ideas??
EDIT:
After my first comment to this question, regarding the fact that I want to have the same DB in test and development, I thought: why don't use a separate test database, where the development database gets copied when the tests start, and that can be light-heartedly dropped?
Could it be a better idea? It actually looks easier and more secure to me. What do you guys think?
Thanks :)

I am not using two separate DBs for development and testing
That's the first thing to address - because without a testing db, running tests will affect your development db and vice versa which is a terrible idea. You should be able to run tests in your production environment with absolute confidence that nothing you do in a test will affect your deployed site.
Setup a test connection
So, modify your parameters.yml to have something like this:
database.host: localhost
database.port: 27017
database.db: myappname
database.test.host: localhost
database.test.port: 27017
database.test.db: myappname-test
In addition, in your app/config/config_test.yml file override the default connnection so that anything you trigger as part of a test which requests the default document manager will receive a manager pointing at your test db:
doctrine_mongodb:
document_managers:
default:
database: %database.test.db%
Prepare for tests with fixtures
Then, what you want to do effectively is:
truncate relevant collections
load fixtures
on your test db before each test.
Here's an example abstract test class:
<?php
use Doctrine\Common\DataFixtures\Executor\MongoDBExecutor as Executor,
Doctrine\Common\DataFixtures\Purger\MongoDBPurger as Purger,
Doctrine\Common\DataFixtures\Loader,
Doctrine\Common\DataFixtures\ReferenceRepository,
Symfony\Bundle\FrameworkBundle\Test\WebTestCase,
Symfony\Bundle\FrameworkBundle\Console\Application;
abstract class AbstractTest extends WebTestCase
{
/**
* Array of fixtures to load.
*/
protected $fixtures = array();
/**
* Setup test environment
*/
public function setUp()
{
$kernel = static::createKernel(array('environment' => 'test', 'debug' => false));
$kernel->boot();
$this->container = $kernel->getContainer();
$this->dm = $this->container->get('doctrine.odm.mongodb.document_manager');
if ($this->fixtures) {
$this->loadFixtures($this->fixtures, false);
}
}
/**
* Load fixtures
*
* #param array $fixtures names of _fixtures to load
* #param boolean $append append data, or replace?
*/
protected function loadFixtures($fixtures = array(), $append = true)
{
$defaultFixtures = false;
$loader = new Loader();
$refRepo = new ReferenceRepository($this->dm);
foreach ((array) $fixtures as $name) {
$fixture = new $name();
$fixture->setReferenceRepository($refRepo);
$loader->addFixture($fixture);
}
$purger = new Purger();
$executor = new Executor($this->dm, $purger);
$executor->execute($loader->getFixtures(), $append);
}
}
Use fixtures in your tests
With the previous abstract test class, you can then write tests which use your fixture data - or not - as appropriate. Below is a trivial example.
<?php
use Your\AbstractTest,
Your\Document\Foo;
class RandomTest extends AbstractTest
{
/**
* fixtures to load before each test
*/
protected $fixtures = array(
'APP\FooBundle\DataFixtures\MongoDB\TestFoos',
'APP\FooBundle\DataFixtures\MongoDB\TestBars'
);
...
/**
* Check it gets an ID (insert succeeded)
*
*/
public function testCreateDefaults()
{
$foo = new Foo();
$this->dm->persist($foo);
$this->dm->flush();
$this->assertNotNull($foo->getId());
$this->assertSame('default value', $foo->getSomeProperty());
// etc.
}
/**
* Check result of something with a given input
*
*/
public function testSomething()
{
$foo = $this->dm->getRepository(APPFooBundle:Foo)->findByName('Some fixture object');
$foo->doSomething();
$this->assertSame('modified value', $foo->getSomeProperty());
// etc.
}
Before each test, the fixtures you've defined will be loaded (truncating the collections they affect), giving a consistent db state on which to base your tests.

Just drop your MongoDB database before each test and then load the fixtures you need. This way each test will be fully isolated.

Related

How to get SQL from Doctrine to INSERT an entity into another database / connection?

I am working on a Symfony 3.4 + Doctrine based project. The production database has grown quite large and I would like to be able to copy some of the data / entities to a second database which can be used as sandbox for running tests, evaluations, etc. on the data.
Adding a second database connection to the project was no problem. Second step was query the database schema / table structure form Doctrine to re-create the exact same tables for some entities in this second DB.
Now I would like to query entities from the production DB using the entity manager as usual and persist them to the second DB. Since the second DB holds only some of the data/entities/tables I cannot use a second entity manager here but have to insert the data manually.
I am looking for something like:
// Load entity from production DB via entity manager
$repo = $this->em->getRepository(SomeEntity::class);
$entity = $repo->findOneById('xy12');
// Pseudocode(!): Get SQL code to save the entity
$saveQuery = $repo->getSaveQueryForEntity(entity); <<< HOW TO DO THIS?
$saveSql = saveQuery->getSql();
// Run SQL on sandbox connection
$sandboxConnection = $doctrine->getConnection('sandbox');
$sandboxConnection->executeQuery($saveSql);
Of course I could create the INSERT query completely manually. However, this would be quite cumbersome and error prone. On the other hand creating the SQL code already build into Doctrine and all I need is a way to access/get this code to run it on a different connection?
Or is this approach completely wrong and there is a better way to get an entity from one DB to the other?
EDIT:
Dumping the complete database and importing it into a sandbox DB is not an option. The database holds data of many registered users and each user can decide if and when he wants to transfer some data to the sandbox. Copying a several GB large database with all user data to a sandbox because User 123 wants to run some tests on entities A and B is not very effective, is it?
I do not want to describe the complete internal logic of the project here, since this does not really help the question. So the question is how to copy / move a single entity to another database by getting the SQL from doctrine :-)
You said:
Since the second DB holds only some of the data/entities/tables I cannot use a second entity manager here but have to insert the data manually.
but you can still declare two different entity managers, that both map the same entity, but with different mapping options (maybe you don't want to map all fields with the other entity manager, for example).
You will need to have two distinct mappings that are bound to the same entity, so better go with separate YML files (instead of annotations). You can have something like:
config/orm/default/User.orm.yml // User mapping for the default EM
config/orm/other/User.orm.yml // User mapping for the other EM
Also, loading the entity with the default entity manager and persisting with the other will not work as expected. You will have to use merge() instead of persist(), since the entity will be managed by the default entity manager:
$user = $defaultEntityManager->getRepository(User::class)->find(1);
$otherEntityManager->merge($user);
$otherEntityManager->flush();
Given it's absolutely unsuitable to do it on a database level using mysqldump, I would probably enable two entity managers for the project and maintain exactly the same data schema for both of them. It gives an opportunity to persist similar objects when needed. When your user would select entities to copy on a web page, we can pass those ids to a handler to fetch entity from the main entity manager and sync it into a sandbox one. It should be pretty straightforward and less hacky than getting insert SQL queries from Doctrine. Here is a simple example to give you a starting point.
<?php
declare(strict_types=1);
namespace App\Sync;
use Doctrine\ORM\EntityManagerInterface;
class SandboxDbSyncHandler
{
/** #var EntityManagerInterface */
private EntityManagerInterface $em;
/** #var EntityManagerInterface */
private EntityManagerInterface $sandboxEm;
public function __construct(EntityManagerInterface $em, EntityManagerInterface $sandboxEm)
{
$this->em = $em;
$this->sandboxEm = $sandboxEm;
}
public function sync(string $class, array $ids): void
{
$repo = $this->em->getRepository($class);
$sandBoxRepo = $this->sandboxEm->getRepository($class);
$entities = $repo->findBy(['id' => $ids]);
foreach ($entities as $entity) {
if (!$entity instanceof UpdatableFromEntity) {
continue;
}
$sandBoxEntity = $sandBoxRepo->find($entity->getId());
if (!$sandBoxEntity) {
$sandBoxEntity = $class()::createFromEntity();
}
$sandBoxEntity->updateFromEntity($entity);
$this->sandboxEm->persist($entity);
}
$this->sandboxEm->flush();
}
}
<?php
declare(strict_types=1);
namespace App\Entity;
interface UpdatableFromEntity
{
public static function createFromEntity($entity);
public function updateFromEntity($entity): void;
}
<?php
declare(strict_types=1);
namespace App\Entity;
class SomeEntity implements UpdatableFromEntity
{
private string $id;
private ?string $name;
public function __construct(string $id)
{
$this->id = $id;
}
public function getId(): string
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function updateFromEntity($entity): void
{
if (!$entity instanceof SomeEntity::class) {
throw new \Exception('Cannot update an entity from the entity of a different type');
}
$this->name = $entity->getName();
}
public static function createFromEntity($entity)
{
if (!$entity instanceof SomeEntity::class) {
throw new \Exception('Cannot create an entity from the entity of a different type');
}
return new static($entity->getId());
}
}

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

Doctrine does not see mapping on dev env but it does on test env

I have some weird behaviour of doctrine. I've been fighting it since yesterday and I cannot solve the problem.
I have class A that has unidirectional mapping to class B. I have created test for class A that retrives object B
public function setUp()
{
self::bootKernel();
$this->container = self::$kernel->getContainer();
$this->em = $this->container->get('doctrine.orm.entity_manager');
}
public function testStreamingMatchReturnsGuthMatchTable()
{
$a = $this->em->getRepository('AppBundle:A')->find(1);
var_dump($a->getB()); // it returns object B
}
it is working as expected and it returns object B
However when I run the same code but in dev envirorment(from the browser) not CLI it does not return mapping
the code:
public function getAction($id)
{
$a = $this->get('doctrine.orm.entity_manager');
$a = $this->em->getRepository('AppBundle:A')->find(1);
var_dump($a->getB()); // it returns null
}
Relation is marked as:
class A
{
/*
* #ORM\OneToOne(targetEntity="B")
* #ORM\JoinColumn(name="b_id", referencedColumnName="id")
*/
private $b;
}
It isn't original code but it illustrates the problem.
So my question is, how to make relations work in dev environment or what can cause the problem in dev env?
What I've tried already:
clear cache
disable cache
change mapping
check with DQL // same problem
try run it with test config on browser
Nothing worked so far and I've got out of ideas.
Edit:
I've noticed that it creates proxy class for test env but it does not create it for dev env.
Edit 2
I've tried to run it on build in Symfony2 server and it works. It does not work on Nginx(vagrant).

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);
}));
}
}

PHPUnit is no longer running after ArrayCollection (Doctrine 2) initialization in Netbeans

I'm working on a PHP project and use PHPUnit and Doctrine 2. I use the latest version at all. I wrote a lot of tests to test all the classes, functions and behavior. The tests are always running. Everything worked fine. Since I use Doctrine, I have applied the best-practice tips and initializes the ArrayCollection in the constructor.
public function __construct($username, $eMail, $password1, $password2) {
$this->quiz = new ArrayCollection();
$this->sessions = new ArrayCollection();
$this->setUsername($username);
$this->setEMail($eMail);
$this->setPassword('', $password1, $password2);
}
The include of the ArrayCollection is:
use Doctrine\Common\Collections\ArrayCollection;
After this point, the PHPUnit tests are no longer running.
When I comment the line with the initialization all test run again. The members quiz and sessions are private. The application generally works.
I'm new with Doctrine 2 and PHPUnit. I've tried many things until now like different includes in the testcase etc. But nothing has helped.
Maybe I forgot something in the testcase. Between the step initialization of the ArrayCollection and without initialization I have changed nothing in the testcase.
The testcase looks like this:
namespace Model;
require_once dirname(__FILE__) . '/../../Model/User.php';
/**
* Test class for User.
* Generated by PHPUnit on 2012-04-03 at 14:48:39.
*/
class UserTest extends \PHPUnit_Framework_TestCase {
/**
* #var User
*/
protected $user;
// constructor of the test suite
function UserTest($name) {
$this->PHPUnit_TestCase($name);
}
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp() {
$username = 'musteruser';
$eMail = 'must#muster.ch';
$pw = 'muster.muster';
$this->user = new User($username, $eMail, $pw, $pw);
}
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown() {
unset($this->user);
}
Right now I really have no idea what could be the problem. Maybe someone has already experienced the same thing or has any idea what could be the problem.
Thanks for any help.
You may need to ensure that the Doctrine\Common folder is on the include path for your test cases when you call PHPUnit.
Are you customizing any class loaders for your application, since you need to do the same for your tests.
I have found a solution for the problem. I created a file with the name bootstrap.php with the following content:
// Import the ClassLoader
use Doctrine\Common\ClassLoader;
// Autoloader for Doctrine
require '/../../php/PEAR/Doctrine/Common/ClassLoader.php';
// Autoloading for Doctrine
$doctrineClassLoader = new ClassLoader('Doctrine', realpath(__DIR__ . '/../../php/PEAR'));
$doctrineClassLoader->register();
The file is placed in the home directory of Test Files, the part for Unit Testing in a NetBeans project. The important thing is, that is nothing to edit in the testcases.

Categories