Lucky you! You found an early release chapter - it will be fully polished and published shortly!
Rest assured, the gnomes are hard at work
completing this video!
We know that bundles give us services and services do work. Ok. But what if we need to write our own custom code that does work? Should we... put that into our own service class? Absolutely! And it's a great way to organize your code.
We are already doing some work in our app. In the browse()
action: we make
an HTTP request and cache the result. Putting this logic in our controller is fine.
But by moving it into its own service class, it'll make the purpose of the code
more clear, allow us to reuse it from multiple places... and even enable us to
unit test that code if we want to.
That sounds amazing, so let's do it! How do we create a service? In the src/
directory, create a new PHP class wherever you want. It seriously doesn't matter
what directories or subdirectories you create in src/
: do whatever feels good
for you.
For this example, I'll create a Service/
directory - though again, you could
call that PizzaParty
or Repository
- and inside of that, a new
PHP class. Let's call it... how about MixRepository
. "Repository" is a pretty common
name for a service that returns data. Notice that when I create this, PhpStorm
automatically adds the correct namespace. It doesn't matter how we organize our
classes inside of src/
... as long as our namespace matches the directory.
One important thing about service classes: they have nothing to do with Symfony.
Our controller class is a Symfony concept. But MixRepository
is a class we're
creating to organize our own code. That means... there are no rules! We don't
need to extend a base class or implement an interface. We can make this class look
and feel however we want. The power!
With that in mind, let's create a new public function
called, how about,
findAll()
that will return
an array
of all of the mixes in our system. Back
in VinylController
, copy all of the logic that fetches the mixes and paste that
here. PhpStorm will ask if we want to add a use
statement for the
CacheItemInterface
. We totally do! Then, instead of creating a $mixes
variable,
just return
.
There are some undefined variables in this class... and those will be a problem.
But ignore them for a minute: I first want to see if we can use our shiny new
MixRepository
.
Head into VinylController
. Let's think: we somehow need to tell Symfony's service
container about our new service so that we can then autowire it in the same way
we're autowiring core services like HtttpClientInterface
and CacheInterface
.
Whelp, I have a surprise! Spin over to your terminal and run:
php bin/console debug:autowiring --all
Scroll up to the top and... amaze! MixRepository
is already a service in
the container! Let me explain two things here. First, the --all
flag is not that
important. It basically tells this command to show you the core services like
$httpClient
and $cache
, plus our own services like MixRepository
.
Second, the container... somehow already saw our repository class and recognized it
as a service. We'll learn how that happened in a few minutes... but for now, it's
enough to know that our new MixRepository
is already inside the container and
its service id is the full class name. That means we can autowire it!
Back over in our controller, add a third argument type-hinted with MixRepository
-
hit tab to add the use
statement - and call it... how about $mixRepository
. Then,
down here, we don't need any of this $mixes
code anymore. Replace it with
$mixes = $mixRepository->findAll()
.
How nice is that? Will it work? Let's find out! Refresh and... it does! Ok,
working in this case means that we get an Undefined variable $cache
message
coming from MixRepository
. But the fact that our code got here means
that autowiring MixRepository
worked: the container saw this, instantiated
MixRepository
and passed it to us so that we could use it.
So, we created a service and made it available for autowiring! We are so cool!
But our new service needs the $httpClient
and $cache
services in order to do
its job. How do we get those? The answer is one of the most important concepts
in Symfony and object-oriented coding in general: dependency injection. Let's talk
about that next.
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"knplabs/knp-time-bundle": "^1.18", // v1.19.0
"symfony/asset": "6.1.*", // v6.1.0-RC1
"symfony/console": "6.1.*", // v6.1.0-RC1
"symfony/dotenv": "6.1.*", // v6.1.0-RC1
"symfony/flex": "^2", // v2.1.8
"symfony/framework-bundle": "6.1.*", // v6.1.0-RC1
"symfony/http-client": "6.1.*", // v6.1.0-RC1
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/runtime": "6.1.*", // v6.1.0-RC1
"symfony/twig-bundle": "6.1.*", // v6.1.0-RC1
"symfony/ux-turbo": "^2.0", // v2.1.1
"symfony/webpack-encore-bundle": "^1.13", // v1.14.1
"symfony/yaml": "6.1.*", // v6.1.0-RC1
"twig/extra-bundle": "^2.12|^3.0", // v3.4.0
"twig/twig": "^2.12|^3.0" // v3.4.0
},
"require-dev": {
"symfony/debug-bundle": "6.1.*", // v6.1.0-RC1
"symfony/maker-bundle": "^1.41", // v1.42.0
"symfony/stopwatch": "6.1.*", // v6.1.0-RC1
"symfony/web-profiler-bundle": "6.1.*" // v6.1.0-RC1
}
}