This course is still being released! Check back later for more chapters.
Metadata & PHP CS Fixer
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
Bundle coding is done, and tests are passing on all our supported Symfony versions. We're on the home stretch!
License File
Let's add a license file to our bundle. In the tutorial directory copy the
LICENSE.md file to the root of our bundle. If you don't see the files
we're copying in your tutorial directory, don't worry! They're all in the
script below:
| Copyright (c) SymfonyCasts <https://symfonycasts.com/> | |
| Permission is hereby granted, free of charge, to any person obtaining a copy | |
| of this software and associated documentation files (the "Software"), to deal | |
| in the Software without restriction, including without limitation the rights | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| copies of the Software, and to permit persons to whom the Software is furnished | |
| to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in all | |
| copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| THE SOFTWARE. |
This is the standard MIT license to match what we have in our composer.json
file.
Documentation
Now, copy the README.md file from the tutorial directory to the root of our
bundle:
| # symfonycasts/object-translation-bundle | |
| This bundle provides a simple way to translate Doctrine entities in Symfony applications. | |
| ## Installation | |
| ### Install the bundle via Composer: | |
| ```bash | |
| composer require symfonycasts/object-translation-bundle | |
| ``` | |
| ### Enable the bundle in your `config/bundles.php` file: | |
| > [!NOTE] | |
| > This step is not required if you are using Symfony Flex. | |
| ```php | |
| return [ | |
| // ... | |
| ObjectTranslationBundle::class => ['all' => true], | |
| ]; | |
| ``` | |
| ### Create the translation entity in your app: | |
| > [!NOTE] | |
| > This step is not required if you are using Symfony Flex. | |
| ```php | |
| namespace App\Entity; | |
| use Doctrine\ORM\Mapping as ORM; | |
| use SymfonyCasts\ObjectTranslationBundle\Model\Translation as BaseTranslation; | |
| #[ORM\Entity] | |
| class Translation extends BaseTranslation | |
| { | |
| #[ORM\Id] | |
| #[ORM\GeneratedValue] | |
| #[ORM\Column] | |
| public int $id; | |
| } | |
| ``` | |
| ### Configure the entity in your `config/packages/object_translation.yaml` file: | |
| > [!NOTE] | |
| > This step is not required if you are using Symfony Flex. | |
| ```yaml | |
| symfonycasts_object_translation: | |
| translation_class: App\Entity\Translation | |
| ``` | |
| ### Create and run the migration to add the translation table: | |
| ```bash | |
| symfony console make:migration | |
| symfony console doctrine:migrations:migrate | |
| ``` | |
| ## Marking Entities as Translatable | |
| To mark an entity as translatable, use the `Translatable` attribute on the entity class | |
| and the `TranslatableProperty` attribute on the fields you want to translate. | |
| ```php | |
| namespace App\Entity; | |
| use Doctrine\ORM\Mapping as ORM; | |
| use SymfonyCasts\ObjectTranslationBundle\Mapping\Translatable; | |
| use SymfonyCasts\ObjectTranslationBundle\Mapping\TranslatableProperty; | |
| #[ORM\Entity] | |
| #[Translatable('product')] | |
| class Product | |
| { | |
| // ... | |
| #[ORM\Column(type: 'string', length: 255)] | |
| #[TranslatableProperty] | |
| public string $name; | |
| #[ORM\Column(type: 'text')] | |
| #[TranslatableProperty] | |
| public string $description; | |
| } | |
| ``` | |
| ## Usage | |
| ### `ObjectTranslator` Service | |
| You can inject the `ObjectTranslator` service to translate entities. | |
| ```php | |
| use SymfonyCasts\ObjectTranslationBundle\ObjectTranslator; | |
| class ProductController | |
| { | |
| public function show(Product $product, ObjectTranslator $objectTranslator) | |
| { | |
| // translate into the current request locale | |
| $translatedProduct = $objectTranslator->translate($product); | |
| $translatedProduct->getName(); // returns the translated name (if available) | |
| $translatedProduct->getDescription(); // returns the translated description (if available) | |
| // ... | |
| } | |
| } | |
| ``` | |
| The second argument of the `translate()` method allows you to specify a locale: | |
| ```php | |
| $product = $objectTranslator->translate($product, 'fr'); // translates into French | |
| ``` | |
| ### `translate_object` Twig Filter | |
| If using Twig, you can use the `translate_object` filter to translate entities directly in templates. | |
| ```twig | |
| {% set translatedProduct = product|translate_object %} {# translates into the current request locale #} | |
| {% set frenchProduct = product|translate_object('fr') %} {# translates into French #} | |
| ``` | |
| ## Managing Translations | |
| The `Translation` database table has the following structure: | |
| - `id`: Primary key (added by you) | |
| - `object_type`: The *alias* defined in the `Translatable` attribute (e.g., `product`) | |
| - `object_id`: The ID of the translated entity | |
| - `locale`: The locale of the translation (e.g., `fr`) | |
| - `field`: The entity property name being translated (e.g., `description`) | |
| - `value`: The translated value | |
| Each row represents a single property translation for a specific entity in a specific locale. | |
| You can manage these translations yourself but two console commands are provided to help: | |
| ### `object-translation:export` | |
| `` | |
| This command exports all entity translations, in your default locale, to a CSV file. | |
| ```bash | |
| symfony console object-translation:export translations.csv | |
| ``` | |
| This will create a `translations.csv` file at the root of your project with the following structure: | |
| ```csv | |
| type,id,field,value | |
| ``` | |
| You can then take this file to translation service for translation. Be sure to keep | |
| the `type`, `id`, and `field` columns intact. The `value` column is what needs to be translated | |
| into the desired language. | |
| ### `object-translation:import` | |
| This command imports translations from a CSV file created by the `export` command after | |
| the `value` column has been translated. | |
| ```bash | |
| symfony console object-translation:import translations_fr.csv fr | |
| ``` | |
| The first argument is the path to the CSV file, and the second argument is the locale | |
| of the translations in that file. | |
| ## Translation Caching | |
| For performance, translations are cached. By default, they use your `cache.app` pool | |
| and have no expiration time. This can be configured: | |
| ```yaml | |
| symfonycasts_object_translation: | |
| cache: | |
| pool: 'cache.object_translation' # a custom pool name | |
| ttl: 3600 # expire after one hour | |
| ``` | |
| ### Translation Tags | |
| If your cache pool supports *cache tagging*, tags are added to the cache keys. Two keys | |
| are added: | |
| - `object-translation`: All translations are tagged with this key. | |
| - `object-translation-{type}`: Where `{type}` is the translatable alias (e.g., `product`). | |
| You can invalidate these tags by using the `cache:pool:invalidate-tags` command: | |
| ```bash | |
| # invalidate all object translation caches | |
| symfony console cache:pool:invalidate-tags object-translation | |
| # invalidate only the translation cache for "product" entities | |
| symfony console cache:pool:invalidate-tags object-translation-product | |
| ``` | |
| ### `object-translation:warmup` Command | |
| This command preloads all translations into the cache for all your | |
| app's enabled locales. | |
| ```bash | |
| symfony console object-translation:warmup | |
| ``` | |
| ## Full Default Configuration | |
| ```yaml | |
| symfonycasts_object_translation: | |
| # The class name of your translation entity. | |
| translation_class: ~ # Required, Example: App\Entity\Translation | |
| # Cache settings for object translations. | |
| cache: | |
| enabled: true | |
| # The cache pool to use for storing object translations. | |
| pool: cache.app | |
| # The time-to-livefor cached translations, in seconds, null for no expiration. | |
| ttl: null | |
| ``` |
Now, the Symfony Bundle best practice guide recommends a doc
directory to house documentation and it to be written in the "reStructuredText"
(or rst) format.
I stray from this recommendation for a couple of reasons. First, rst isn't
rendered as nicely on GitHub as Markdown files are. Second, unless the
documentation starts getting really large, I like to have it shown
immediately when someone visits the GitHub repository. So, I prefer
to have documentation in the readme file.
Let's take a quick review of what I've written. It starts with installation instructions and how to enable and configure the bundle. Next, how to mark your entities as translatable...
A usage section to show how to use the ObjectTranslator service and
the translate_object Twig filter.
The managing translations section gives details about the database structure and how to use the export and import commands.
Next, some information about the cache system, including the warmup command.
Finally, I like to include the full default configuration because it's so easy to generate! Do you remember?
In the terminal, make sure you're at the root of our application, not in the bundle. Then run:
symfony console config:dump-reference symfonycasts_object_translation
Hey! That looks familiar. Whenever you change your configuration, you can easily update this section by re-running that command and copy/pasting the output. Self-documenting configuration for the win!
.editorconfig File
Next, copy the .editorconfig file from the root of our project into the
bundle:
| # editorconfig.org | |
| root = true | |
| [*] | |
| charset = utf-8 | |
| end_of_line = lf | |
| indent_size = 4 | |
| indent_style = space | |
| insert_final_newline = true | |
| trim_trailing_whitespace = true | |
| [{compose.yaml,compose.*.yaml}] | |
| indent_size = 2 | |
| [*.md] | |
| trim_trailing_whitespace = false |
This file ensures consistency in things like spacing and new lines. Many IDEs, including PhpStorm, support this file. It helps prevent weird commits with different white space and line break characters. This is especially helpful when someone is developing on Windows, which uses different line endings than macOS or Linux.
.gitattributes File
At the root of our bundle, create a new file called .gitattributes. Inside,
add /tests export-ignore:
| /tests export-ignore |
This tells Composer to exclude the tests
directory when installing this package as a dependency. There's no need for
our bundle's tests to be included in an end user's project.
PHP CS Fixer
Kind of like the consistency the .editorconfig file provides for white space and
line breaks, it's also important to have a consistent coding style. This is things like
a space between namespace and use statements, or where to put braces. A consistent
coding style makes your code easier to read and helps contributors.
A great tool to enforce and automate this is PHP CS Fixer. Over in the terminal, make sure you're in the bundle directory and run:
symfony composer require --dev php-cs-fixer/shim
If you've used this tool before but this shim package is new to you, it's just a
compiled version of PHP CS Fixer that makes installation easier.
Once installed, grab the .php-cs-fixer.dist.php file from the tutorial directory
and copy it into the root of our object-translation-bundle:
| return (new PhpCsFixer\Config()) | |
| ->setRules([ | |
| '@Symfony' => true, | |
| ]) | |
| ->setFinder( | |
| (new PhpCsFixer\Finder()) | |
| ->in([__DIR__.'/src', __DIR__.'/tests']) | |
| ) | |
| ; |
This file configures the coding style rules for your project. Open it up and take a look.
We're using the Symfony rule set, so our coding style will match Symfony's. Below,
we're telling it where to look for PHP files to fix: the src and tests directories.
To run it, at the terminal, run:
symfony php vendor/bin/php-cs-fixer fix -v
Cool, here's all the files that were modified and the rules that were applied.
The first one, TranslatedObject applied the phpdoc_align rule. Let's open that
file to see what changed. Ah, it added spacing to align the @param variable names.
Generally, they are easier to read when aligned.
This .php-cs-fixer.cache file in the root of our bundle was generated when we
ran it. It's just a cache to make subsequent runs of PHP CS Fixer faster. Add
this to our .gitignore file so it doesn't get committed:
| // ... lines 1 - 4 | |
| .php-cs-fixer.cache |
Next, we're going to use PHPStan to run static code analysis on our bundle's code!