This course is still being released! Check back later for more chapters.
Bundle Service Class
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.
Ok, our bundle is installed and ready to roll. Time to add some functionality by adding our first service class. This will be the star of the show. Our bundle is for translating objects, so this seems like the place to start.
In our bundle's src
directory, create a new PHP class: ObjectTranslator
.
ObjectTranslator
First, mark this class as final. This isn't meant to be extended. When developing
bundles, it's important to be explicit about your class design and their
intentions. This makes it easy to keep backwards compatibility. Removing final
later isn't a breaking change, but adding final
is. We'll explore more of these
tricks as we go along:
// ... lines 1 - 4 | |
final class ObjectTranslator | |
{ | |
// ... lines 7 - 17 | |
} |
Create a method: public function translate(object $object)
. Return type: object
. This will
eventually house the logic for translating objects. For now, just return the passed
$object
:
// ... lines 1 - 4 | |
final class ObjectTranslator | |
{ | |
// ... lines 7 - 13 | |
public function translate(object $object): object | |
{ | |
return $object; | |
} | |
} |
Our service needs a constructor to inject a few goodies. Add public
function __construct(private LocaleAwareInterface $localeAware, private
string $defaultLocale)
. We need the LocaleAwareInterface
service to get
the current locale of the request, and we'll also need our app's default
locale:
// ... lines 1 - 6 | |
final class ObjectTranslator | |
{ | |
public function __construct( | |
private LocaleAwareInterface $localeAware, | |
private string $defaultLocale, | |
) { | |
} | |
// ... lines 14 - 33 | |
} |
Down in translate()
, we can add some easy logic. Grab the current locale with
$locale = $this->localeAware->getLocale()
.
Now, if the current locale is the same as the default locale, we don't need to
do any translating, so add an if ($this->defaultLocale === $locale)
and
just return the raw object in this case:
// ... lines 1 - 6 | |
final class ObjectTranslator | |
{ | |
// ... lines 9 - 21 | |
public function translate(object $object): object | |
{ | |
$locale = $this->localeAware->getLocale(); | |
if ($this->defaultLocale === $locale) { | |
return $object; | |
} | |
// ... lines 29 - 32 | |
} | |
} |
Below is where we'll eventually add the real translation logic, but just
add a comment for now: todo translate object
:
// ... lines 1 - 6 | |
final class ObjectTranslator | |
{ | |
// ... lines 9 - 21 | |
public function translate(object $object): object | |
{ | |
// ... lines 24 - 29 | |
// todo translate object | |
return $object; | |
} | |
} |
Let's use this new service in ArticleController::show()
. Expand this method
a bit and inject it: ObjectTranslator $translator
:
// ... lines 1 - 11 | |
final class ArticleController extends AbstractController | |
{ | |
// ... lines 14 - 22 | |
'/news/{slug:article}', name: 'app_article_show') | (|
public function show(Article $article, ObjectTranslator $translator): Response | |
// ... lines 25 - 36 | |
} |
Run the injected Article
through our new service:
$article = $translator->translate($article)
:
// ... lines 1 - 11 | |
final class ArticleController extends AbstractController | |
{ | |
// ... lines 14 - 23 | |
public function show(Article $article, ObjectTranslator $translator): Response | |
{ | |
$article = $translator->translate($article); | |
// ... lines 27 - 35 | |
} | |
} |
Sweet!
PHP Generics
Notice if we try and access a method on $article
before running through
our service, PhpStorm can auto-complete all the methods on $article
. But...
if we try and access a method on $article
after running it through
$translator->translate()
, we don't have auto-completion. PhpStorm has no idea
what $article
is now - just that it's "an object". This is a drag...
But we can fix this with PHP generics!
Generics are a way to provide additional type information to our editor.
Check this out: above ObjectTranslator::translate()
, generate some doc blocks.
This just matched the method signature and isn't super helpful... so add
@template T of object
above. This declares a template type T
that must
be an object. T
is like an alias, or placeholder and can be any string.
Now, for @param
and @return
, replace object
with T
:
// ... lines 1 - 6 | |
final class ObjectTranslator | |
{ | |
// ... lines 9 - 14 | |
/** | |
* @template T of object | |
* | |
* @param T $object | |
* | |
* @return T | |
*/ | |
public function translate(object $object): object | |
// ... lines 23 - 33 | |
} |
This tells our editor: "Whatever object type is passed to this method, the return type will be the same object type."
Back in ArticleController::show()
, after we call translate()
, try auto-completing
again on $article
. Boom! PhpStorm knows exactly what $article
is now. I
love this!
Remove that extra code - the translated article is now passed to our template so our work here is done.
Jump back to our browser and visit an article page... An error... "Cannot autowire argument $translator..."
Symfony doesn't know about our bundle's service - it's just a plain PHP class still...
Let's fix that next!