Reputation: 141829
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?
Upvotes: 3
Views: 1978
Reputation: 11762
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
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.
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();
}
}
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
.
Upvotes: 3
Reputation: 8915
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:
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);
}));
}
}
Upvotes: 1
Reputation: 5225
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:
"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"
Using translator
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
Upvotes: 2