Switched from deprecated --set
option to rector.php
config. Removed Rector upgrade set, as outdated and could cause troubles. Better handled individually per project.
With Symfony 5 upgrade, we need any working Doctrine behaviors. <br> Month later, we have KnpLabs\DoctrineBehaviors 2.0 with full Symfony 5 support.
If you used older Doctrine Behaviors, you're covered with Rector migration path. <br> But what if you're using old broken Gedmo?
I'll show you how you can migrate Gedmo to KnpLabs.
Pick behavior your want to migrate from Gedmo to KnpLabs:
If you use other Gedmo behavior that is not listed here, you might request or better add it to KnpLabs repository via pull-request. I assure you I'll provide stable maintenance support for KnpLabs package. It's not that hard with CI on steroids it has now.
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Timestampable\Traits\TimestampableEntity;
/**
* @ORM\Entity
*/
class Meetup
{
use TimestampableEntity;
}
↓
Knp\DoctrineBehaviors\Model\Timestampable\TimestampableTrait
Knp\DoctrineBehaviors\Contract\Entity\TimestampableInterface
interfacenamespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model\Timestampable\TimestampableTrait;
use Knp\DoctrineBehaviors\Contract\Entity\TimestampableInterface;
/**
* @ORM\Entity
*/
class Meetup implements TimestampableInterface
{
use TimestampableTrait;
}
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @ORM\Entity
*/
class Meetup
{
/**
* @Gedmo\Slug(fields={"name"})
*/
private $slug;
public function getSlug(): ?string
{
return $this->slug;
}
public function setSlug(?string $slug): void
{
$this->slug = $slug;
}
}
↓
Add Knp\DoctrineBehaviors\Model\Sluggable\SluggableTrait
trait
Add Knp\DoctrineBehaviors\Contract\Entity\SluggableInterface
interface
Remove getSlug()
/setSlug()
method that is already in trait
Replace * @Gedmo\Slug(fields={"name"})
with getSluggableFields()
method that contains fields
E.g., from:
use Gedmo\Mapping\Annotation as Gedmo;
// ...
/**
* @Gedmo\Slug(fields={"name", "surname"})
*/
private $slug;
to
/**
* @return string[]
*/
public function getSluggableFields(): array
{
return ['name', 'surname'];
}
In full code:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model\Sluggable\SluggableTrait;
use Knp\DoctrineBehaviors\Contract\Entity\SluggableInterface;
/**
* @ORM\Entity
*/
class Category implements SluggableInterface
{
use SluggableTrait;
/**
* @return string[]
*/
public function getSluggableFields(): array
{
return ['name'];
}
}
This one will be tricky, because:
So if you're using anything but materialized path in Gedmo, you'll have to migrate PHP code (see below) + migrate your database data to materialized path (write your migration or Google one).
The PHP migration looks like this:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @ORM\Entity
* @Gedmo\Tree(type="nested")
*/
class Category
{
/**
* @Gedmo\TreeLeft
* @ORM\Column(name="lft", type="integer")
* @var int
*/
private $lft;
/**
* @Gedmo\TreeRight
* @ORM\Column(name="rgt", type="integer")
* @var int
*/
private $rgt;
/**
* @Gedmo\TreeLevel
* @ORM\Column(name="lvl", type="integer")
* @var int
*/
private $lvl;
/**
* @Gedmo\TreeRoot
* @ORM\ManyToOne(targetEntity="Category")
* @ORM\JoinColumn(name="tree_root", referencedColumnName="id", onDelete="CASCADE")
* @var Category
*/
private $root;
/**
* @Gedmo\TreeParent
* @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
* @var Category
*/
private $parent;
/**
* @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
* @var Category[]|Collection
*/
private $children;
public function getRoot(): self
{
return $this->root;
}
public function setParent(self $category): void
{
$this->parent = $category;
}
public function getParent(): self
{
return $this->parent;
}
}
↓
Knp\DoctrineBehaviors\Model\Tree\TreeNodeTrait
traitKnp\DoctrineBehaviors\Contract\Entity\TreeNodeInterface
interfacenamespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Knp\DoctrineBehaviors\Contract\Entity\TreeNodeInterface;
use Knp\DoctrineBehaviors\Model\Tree\TreeNodeTrait;
/**
* @ORM\Entity
*/
class Category implements TreeNodeInterface
{
use TreeNodeTrait;
}
I recall picking the behavior package for new Lekarna.cz 6 years ago. I was young, and a quantity was more than quality to me, so I was leaning towards Gedmo since it had more downloads.
But it's performance surprised me. Why? The translated item had 1:many dependency on translation table, so for every single item, it joined an X extra lines forever single translated column.
So even if you use Symfony 4 and everything works well for you, consider comparing performance with KnpLabs translations. Who knows, it might get your multi-lingual application 10x faster.
namespace App\Entity;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Translatable\Translatable;
/**
* @ORM\Entity
*/
class Category implements Translatable
{
/**
* @Gedmo\Translatable
* @ORM\Column(length=128)
*/
private $title;
/**
* @Gedmo\Locale
*/
private $locale;
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
public function setTranslatableLocale($locale)
{
$this->locale = $locale;
}
}
↓
Knp\DoctrineBehaviors\Contract\Entity\TranslatableInterface
Knp\DoctrineBehaviors\Model\Translatable\TranslatableTrait
namespace App\Entity;
use Knp\DoctrineBehaviors\Model\Translatable\TranslatableTrait;
use Knp\DoctrineBehaviors\Contract\Entity\TranslatableInterface;
class Category implements TranslatableInterface
{
use TranslatableTrait;
}
So, where is the title property we need to translate? Every translated property is in the new <entity>Translation
class.
This approach makes sure that the complexity of 1 item with dozens of translation stays 1:1 = it's super fast!
namespace App\Entity;
use Knp\DoctrineBehaviors\Contract\Entity\TranslationInterface;
use Knp\DoctrineBehaviors\Model\Translatable\TranslationTrait;
class CategoryTranslation implements TranslationInterface
{
use TranslationTrait;
/**
* @ORM\Column(length=128)
*/
private $title;
}
In short:
Usage stays the same:
$category->getTitle();
That's all for the migration. Oh, you're still reading? Are you waiting for some easy solution to cover it all?
namespace App\Entity;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Category
{
/**
* @Gedmo\Blameable(on="create")
*/
private $createdBy;
/**
* @Gedmo\Blameable(on="update")
*/
private $updatedBy;
public function getCreatedBy()
{
return $this->createdBy;
}
public function getUpdatedBy()
{
return $this->updatedBy;
}
}
↓
Knp\DoctrineBehaviors\Contract\Entity\BlameableInterface
interfaceKnp\DoctrineBehaviors\Model\Blameable\BlameableTrait
traitnamespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Contract\Entity\BlameableInterface;
use Knp\DoctrineBehaviors\Model\Blameable\BlameableTrait;
/**
* @ORM\Entity
*/
class Category implements BlameableInterface
{
use BlameableTrait;
}
namespace App\Entity;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @Gedmo\Loggable
*/
class Category
{
/**
* @Gedmo\Versioned
* @ORM\Column(name="title", type="string", length=8)
*/
private $title;
}
↓
Knp\DoctrineBehaviors\Model\Loggable\LoggableTrait
traitKnp\DoctrineBehaviors\Contract\Entity\LoggableInterface
interfacenamespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model\Loggable\LoggableTrait;
use Knp\DoctrineBehaviors\Contract\Entity\LoggableInterface;
/**
* @ORM\Entity
*/
class Category implements LoggableInterface
{
use LoggableTrait;
/**
* @ORM\Column(name="title", type="string", length=8)
*/
private $title;
}
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false, hardDelete=true)
* @ORM\Entity
*/
class Category
{
/**
* @ORM\Column(name="deletedAt", type="datetime", nullable=true)
*/
private $deletedAt;
public function getDeletedAt()
{
return $this->deletedAt;
}
public function setDeletedAt($deletedAt)
{
$this->deletedAt = $deletedAt;
}
}
↓
Knp\DoctrineBehaviors\Contract\Entity\SoftDeletableInterface
interfaceKnp\DoctrineBehaviors\Model\SoftDeletable\SoftDeletableTrait
traitnamespace App\Entity;
use Knp\DoctrineBehaviors\Contract\Entity\SoftDeletableInterface;
use Knp\DoctrineBehaviors\Model\SoftDeletable\SoftDeletableTrait;
class Category implements SoftDeletableInterface
{
use SoftDeletableTrait;
}
Happy coding!
Do you learn from my contents or use open-souce packages like Rector every day?
Consider supporting it on GitHub Sponsors.
I'd really appreciate it!