This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com
Login to bookmark this video
Buy Access to Course
32.

Metadata & PHP CS Fixer

|

Share this awesome video!

|

Keep on Learning!

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:

<?php
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:

6 lines | object-translation-bundle/.gitignore
// ... lines 1 - 4
.php-cs-fixer.cache

Next, we're going to use PHPStan to run static code analysis on our bundle's code!