Gedmo Tree in Symfony3

So today at work I’ve had to rethink the way we work with products in an app, and it turns out the best way to face the problem is having a tree based architecture, as products can have sub-products and so on and so forth… so instead of reinventing the wheel, why not use a well established library that uses trees right? Gedmo please!

So it’s actually really easy to install Gedmo extensions to your project.

1) First of all composer install it

php -d memory_limit=-1 /usr/local/bin/composer require stof/doctrine-extensions-bundle

(yes I don’t like playing with my php.ini so for this command I rather pass the memory_limit parameter, also remember to put your composer path correctly, as it won’t read your alias if you pass parameters to php)

2) After that you need to include it to you Kernel

$bundles = [
...
// Gedmo
new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
...
];

3) And the final step is adding the extra configuration in your config.yml

Under the doctrine.orm configuration:

# Doctrine Configuration
doctrine:
...

orm:
auto_generate_proxy_classes: "%kernel.debug%"
# naming_strategy: doctrine.orm.naming_strategy.underscore
# auto_mapping: true

default_entity_manager: default
entity_managers:
default:
connection: default
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
dql:
numeric_functions:
ACOS: DoctrineExtensions\Query\Mysql\Acos
COS: DoctrineExtensions\Query\Mysql\Cos
RADIANS: DoctrineExtensions\Query\Mysql\Radians
SIN: DoctrineExtensions\Query\Mysql\Sin
mappings:
gedmo_translatable:
type: annotation
prefix: Gedmo\Translatable\Entity
dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translatable/Entity"
alias: GedmoTranslatable # (optional) it will default to the name set for the mapping
is_bundle: false
gedmo_translator:
type: annotation
prefix: Gedmo\Translator\Entity
dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Translator/Entity"
alias: GedmoTranslator # (optional) it will default to the name set for the mapping
is_bundle: false
gedmo_loggable:
type: annotation
prefix: Gedmo\Loggable\Entity
dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Loggable/Entity"
alias: GedmoLoggable # (optional) it will default to the name set for the mappingmapping
is_bundle: false
gedmo_tree:
type: annotation
prefix: Gedmo\Tree\Entity
dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity"
alias: GedmoTree # (optional) it will default to the name set for the mapping
is_bundle: false

Yes, it’s important to remove the auto_mapping as it won’t recognize the key under doctrine.orm

And under the stof_doctrine_extensions configuration:

# Stof Configuration
stof_doctrine_extensions:
default_locale: "%locale%"
translation_fallback: true
persist_default_translation: true

# Only used if you activated the Uploadable extension
uploadable:
#stof_doctrine_extensions.uploadable.validate_writable_directory
validate_writable_directory: true

# Default file path: This is one of the three ways you can configure the path for the Uploadable extension
default_file_path: "%kernel.root_dir%/../web/uploads"

# Mime type guesser class: Optional. By default, we provide an adapter for the one present in the HttpFoundation component of Symfony
mime_type_guesser_class: Stof\DoctrineExtensionsBundle\Uploadable\MimeTypeGuesserAdapter

# Default file info class implementing FileInfoInterface: Optional. By default we provide a class which is prepared to receive an UploadedFile instance.
default_file_info_class: Stof\DoctrineExtensionsBundle\Uploadable\UploadedFileInfo
orm:
default:
translatable: true
blameable: false
timestampable: true
tree: true
uploadable: false
sluggable: true

That’s it! You’re set, super easy right?

Now let’s go to the entity, in my case it’s the Product. Don’t forget to import the Gedmo annotation and then add the annotation to the class, like so:

use Gedmo\Mapping\Annotation as Gedmo;

/**
* @ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
* ...
* @Gedmo\Tree(type="nested")
*/
class Product
{
/**
* @Gedmo\TreeLeft
* @ORM\Column(name="lft", type="integer")
*/
private $lft;

/**
* @Gedmo\TreeLevel
* @ORM\Column(name="lvl", type="integer")
*/
private $lvl;

/**
* @Gedmo\TreeRight
* @ORM\Column(name="rgt", type="integer")
*/
private $rgt;

/**
* @Gedmo\TreeRoot
* @ORM\ManyToOne(targetEntity="Product")
* @ORM\JoinColumn(name="tree_root", referencedColumnName="id", onDelete="CASCADE")
*/
private $root;

/**
* @Gedmo\TreeParent
* @ORM\ManyToOne(targetEntity="Product", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $parent;

/**
* @ORM\OneToMany(targetEntity="Product", mappedBy="parent")
* @ORM\OrderBy({"lft" = "ASC"})
*/
private $children;

...
}

Running the command

bin/console doc:gen:entities AppBundle:Product

will generate all the getters and setters, pretty handy…

And now the repository:

use Gedmo\Tree\Entity\Repository\NestedTreeRepository;

/**
* ProductRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class ProductRepository extends NestedTreeRepository
{
...
}

The NestedTreeRepository has very useful functions, play with them before making your custom ones, cause they’ve already thought with pretty much everything, for instance the function childrenHierarchy() gives you the nested array, with decoration output if you pass parameters as options. You can find more info in the RepositoryInterface and RepositoryUtilsInterface from Gedmo\Tree namespace.

PS: Don’t forget to run your schema update command

bin/console doc:sch:update --force

Now you focus on what really matters, you project’s logic.

Happy coding! :)


Originally published at Joey Masip Romeu.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.