gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Ready to move a chunk of code out of the controller? Well good for you.
Step 1: create a new PHP class. In AppBundle
, I'll create a new directory called
Service
- but that could be called anything. Inside, add a new PHP class called
MarkdownTransformer
:
... lines 1 - 2 | |
namespace AppBundle\Service; | |
class MarkdownTransformer | |
{ | |
... lines 7 - 10 | |
} |
If you're keeping score at home, that could also be called anything.
Start this with one public function parse()
with a $str
argument:
... lines 1 - 4 | |
class MarkdownTransformer | |
{ | |
public function parse($str) | |
{ | |
return strtoupper($str); | |
} | |
} |
Eventually, this will do all the dirty work of markdown parsing and caching. But for now...
keep it simple and return strtoupper($str)
. But use your imagination - pretend
like it totally is awesome and is parsing our markdown. In fact, it's so awesome
that we want to use it in our controller. How?
Find GenusController
. First, create a new object with $transformer = new MarkdownTransformer()
:
... lines 1 - 13 | |
class GenusController extends Controller | |
{ | |
... lines 16 - 58 | |
public function showAction($genusName) | |
{ | |
$em = $this->getDoctrine()->getManager(); | |
$genus = $em->getRepository('AppBundle:Genus') | |
->findOneBy(['name' => $genusName]); | |
if (!$genus) { | |
throw $this->createNotFoundException('genus not found'); | |
} | |
$markdownParser = new MarkdownTransformer(); | |
... lines 71 - 97 | |
} | |
... lines 99 - 123 | |
} |
Noooothing special here: the new method is purposefully not static, and this means
we need to instantiate the object first. Next, add $funFact = $transformer->parse()
and pass $genus->getFunFact()
:
... lines 1 - 69 | |
$markdownParser = new MarkdownTransformer(); | |
$funFact = $markdownParser->parse($genus->getFunFact()); | |
... lines 72 - 125 |
And that's it! If you're feeling massively underwhelmed... you're right where I want you! I want this to be boring and easy - there are fireworks and exciting stuff later.
Finish this by passing $funFact
into the template so we can render the parsed version:
... lines 1 - 13 | |
class GenusController extends Controller | |
{ | |
... lines 16 - 58 | |
public function showAction($genusName) | |
{ | |
... lines 61 - 69 | |
$markdownParser = new MarkdownTransformer(); | |
$funFact = $markdownParser->parse($genus->getFunFact()); | |
... lines 72 - 92 | |
return $this->render('genus/show.html.twig', array( | |
... line 94 | |
'funFact' => $funFact, | |
... line 96 | |
)); | |
} | |
... lines 99 - 123 | |
} |
Then, open the template and replace genus.funFact
with just funFact
:
... lines 1 - 4 | |
{% block body %} | |
<h2 class="genus-name">{{ genus.name }}</h2> | |
<div class="sea-creature-container"> | |
<div class="genus-photo"></div> | |
<div class="genus-details"> | |
<dl class="genus-details-list"> | |
... lines 12 - 15 | |
<dt>Fun Fact:</dt> | |
<dd>{{ funFact }}</dd> | |
... lines 18 - 19 | |
</dl> | |
</div> | |
</div> | |
<div id="js-notes-wrapper"></div> | |
{% endblock %} | |
... lines 25 - 42 |
Try it out: open up localhost:8000/genus
- then click one of the genuses. Yes!
The fun fact is screaming at us in upper case.
So believe it or not: you just saw one of the most important and commonly-confusing object-oriented strategies that exist anywhere... in any language! And it's this: you should take chunks of code that do things and move them into an outside function in an outside class. That's it.
Oh, and guess what? MarkdownTransformer
is a service. Because remember, a service
is just a class that does work for us. And when you isolate a lot of your code
into these service classes, you start to build what's called a "service-oriented architecture".
OooOOoooOOOo. That basically means that instead of having all of your code in big
controllers, you organize them into nice little services that each do one job.
Of course, the MarkdownTransformer
service isn't actually transforming... any...
markdown - so let's fix that.
A service should have just one function? Or can I create a "TextStyle service" and inside functions like: normal, italic, bold, etc? (just and example). Thanks.
Hey Juan,
No, service could have more than one function, it's just a simple PHP class, so feel free to put there as much methods as you need till it's semantically ok. But if some methods weakly related to the service, create another one for them. However, the more methods your service has - the harder to use it.
Cheers!
4 am here at my country, I'm working, tired but learning with KNP. Victor, thanks for your answer, you can't imagine the importance of answers like this for me.
Thanks again :)
I'm from Colombia and I don't speak English, but I can understand jokes between the videos.
Cheers!
Hey Juan,
Haha, understanding jokes is half the work already ;) Then, I bet you love our course scripts below the video and if you use Google Chrome browser - you can easily translate translate it on the go.
And hello to Colombia!
Of course Victor!
Even I downloaded the PDF to read later (like 1000 times...). I using this to practice my English with the intention of travel to your country and say "thanks" with a good pronunciation.
Talking again about services, I have another question if you can help me, please: Is okay use inheritance between services classes or better use Dependency Injection HA, HA, HA? (I need too parent's variables - $name, $lastname, $example).
Thanks one more time.
Hey Juan,
Haha, that's really cool when you study programming and English at once :)
Hm, it depends! Actually, both inheritance and Dependency Injection (DI) are good.And probably it's a bit incorrect question, because Inheritance and DI are different things. You probably still need to use DI if you use inheritance. But anyway, if you have a few services which do the same task but with different implementation - it's good to use inheritance. But if your services are completely different - their inheritance probably will mislead you and other developers. So start with DI, and if you see something common in those services, create an abstract class and inherit it by those services. Then you can move some common fields to the abstract class to avoid duplication, but... you still need to use DI for those services to inject what you need, even if it's moved to the abstract class. Am I understand you right? Does it make sense for you?
Cheers!
I'll answer you with a translator, excuse the inconsistencies.
Thanks to your answers I think I identified my confusion: I do not know the difference of an Entity, a Service and a Model; Consequently I do not know how to write clean code in Symfony.
Following your videos (KNP) and this answer ( https://stackoverflow.com/a... ) I came to the following conclusions:
- Symfony is not entirely an MVC pattern.
- Entity: Persistence layer with database (that's new for me).
- Model: Can be created and contains business logic (interacts with the database by injecting an Entity object).
- Service: Works as a container that unifies an entity and a model that is called in the controllers using the symfony container.
That is roughly what I have in my head and obviously I do not quite understand how to apply it in the real world. Let me briefly explain my case, Victor.
MVC project without Symfony (and no frameworks, just jQuery): I created a database (mySQL) with a table called Pokémon, a model called Pokémon whose attributes are called equal to the table fields (name, attack, defense, Speed), in addition, within the Model there are some getters who consult the information of the Pokémon in the database.
Then, I created another Model called PokémonGO that inherits Pokémon to use its attributes and data to perform more complex operations with each Pokémon, for example: simulateBatalla (), comparePokemon (), saveFavorite (); Things like that, tools that help fans.
All operations use AJAX with jQuery to print results on the screen or save a favorite Pokémon in the database (in another SQL table, of course).
Knowing all this and trying to do it with Symfony from scratch, I'm doing the following:
- The table called Pokémon is created by an Entity.
- The Model called Pokémon try to see it as a Service and create another Service called PokémonGO that inherits Pokémon. In this part I believe that I can inject a Pokémon object into PokémonGO, although honestly I say it is for trying to understand.
Sorry to ask for your help in this way, but really, I tried adapting the course examples but as you see I am confused. (Symfony used Confusion - it is very effective). I do not ask you the solution to my problem although knowing a logical structure of folders or order of the code would be enough for me.
I am a developer who has always avoided the frameworks until I believe I understand what is behind them, now I just try to adapt to one of the most powerful frameworks.
Victor and the KNP team, thanks for the patience and biting. :)
(Oh, inclusive, I would like to know if there is any video explaining how to deploy a Symfony project on the server, internet. Is it enough to copy the project folder on the server ?, thanks).
Hey Juan,
Wow, that was a huge comment :) You know, sometimes you just needed more practice. You'd probably refactor a lot of your own code over time at the initial stage. So it's impossible to come up with ideal solution. The code that you thought is clean will seem bad to you after a while.
Yes, Symfony is not an MVC but more Request/Response framework. Your application should handle a request and return a response.
What about your project, the important part is an Entity, so you need to start with it first. Entity is just a simple PHP object which holds data. But around this data you'll build your application. So you need to create a Pokemon entity, which will have properties (which holds data) and setters/getters to set and get those properties, and that's it. No any other logic should be in the entity.
To apply complex queries to those entities you need a repository, so you can create PokemonRepository where you will hold queries like findAllMyPokemons(), findTheMostPopularPokemon(), findRandomPokemonForBattle(), etc.
Then, to control entities - you'll need a controller. So you can create PokemonController which will have createAction(), simulateBatallaAction(), comparePokemonAction(), saveFavoriteAction(), etc. You will send GET/POST or AJAX requests to this endpoints and *do* something with your pokemons (your entities) accordingly, executing complex queries from repository, etc.
And only then, when you need to reuse some common logic between controllers, you can create a service, move this common logic there, and call it in different controllers where you need it.
That's it, that was the order you can start coding your pokemon application. I hope it was helpful for you.
What about Symfony deploy, we will have a new course about it soon: https://knpuniversity.com/s... , but this course based on knowledges of Ansible, so you'd probably want to see this course first: https://knpuniversity.com/s... . But Ansistrano is just a tool as many others, so fell free to choose another one. I think this official docs will be useful for you: https://symfony.com/doc/cur... . If you have any question after reading it - just ask ;)
Cheers!
Hi Ryan,
I have a term that confused me a little that I read on some sites, maybe you can help me?
- Modular Programing
- You should write Modular code...
So I understand that modular programming are the same like mentioned this course:
"move a chunk of code out" in a function or class, is this right?
Is than this service-oriented arhitecture, modular programming.
(Sorry about my english :)
Hi Denis!
Basically, I'd say yes :). Both ideas/terms are definitely referring to the same idea: moving chunks of code into their own classes / functions. If you do this, then instead of having controllers with TONS of code, you have many, small "modules" (i.e. classes) of functionality that you can re-use anywhere. So yes, you're thinking correctly!
Cheers!
HI,
should the namespace generate automaticly?
After all the steps I can display the localhost:8000
but when I visit localhost:8000/genus ther's an error "SQLSTATE[42S02]: Base table or view not found: 1146 Table 'symfony.genus' doesn't exist"
I did the db connection all right, so where's the problem???
Hi Konrad!
When you create a new class in PhpStorm, it can automatically add the "namespace" line for you (which is what you see in this tutorial). But, to get that, you need to mark your "src" directory as a "Sources Root" in PhpStorm. We show that at about 1:30 in this tutorial: http://knpuniversity.com/screencast/symfony/first-page
About your error, you do have the database, but you don't yet have the database tables :). You can get them by running:
php bin/console doctrine:migrations:migrate
Let me know if that works! Cheers!
I do see a page after runing that command( previously just an error) but still, no content. Should i add something?
Hey Konrad!
Ah yes, also run:
php bin/console doctrine:fixtures:load
This will populate your database. Btw, if you download the course code, we of the full setup details in the README.md file that's in both the start/ and finish/ directories. If you have any issues, let me know :).
Cheers!
Hi I did the command it repopulated my databese, but it's still empty.In which project there's somthing done to the database?
Hi Konrad!
Hmm, so your database was repopulated, but the page is still empty? Exactly which URL are you going to and what are you seeing? Did you download the code from this page? And if so, are you using the start or finish directory?
Let me know - I'm sure we can figure out the issue :)
Cheers!
Amazing tutorial! Thanks for your tips. It is helping me understand the fundamentals of symfony.
Is there an easy way to redirect to another page from within a service without having to return back all the way to the controller?
My controller calls a member function on a Service. Within that Service member function, I call another member function. Within that sub-function I do a check that needs to redirect to another url.
I imported the Router, but in order to use the statement below, I would need to bubble the response all the way back to the controller, correct?return $this->router->generate('questionnaire');
Is there a way to just end the script (gracefully) and redirect?
Is it bad form in Symfony output a header and die. Something like:header( 'Location: ' . $this->router->generate('questionnaire'));<br />die();
Hey Terry!
The short answer is no. And generally speaking, if you need this, you might have a bit of a design problem (but maybe not). There are two reasons why you might want to redirect:
1) Something "normal" happens (e.g. form save) and you want to send the user somewhere. This should be done in the controller. Typically, a service shouldn't know or care what route/controller is being processed or where the user should be sent next: the controller would call the service to do some work, but ultimately direct traffic itself.
2) Something "exceptional" happens (e.g. in a service, somehow, you find yourself in a situation that shouldn't happen - e.g. perhaps you're processing a new order, but realize that [somehow] the user isn't logged in!). In this case, you should throw an exception from your service. And usually... that's it - I make sure that my errors are logged somewhere so I can address this bug. If, for some reason, this "exceptional" situation isn't so exceptional, and you want to "handle" the situation, then you can add a try-catch block for the specific exception in your controller, and then redirect from there.
That's a long way of asking: can you tell me more about your specific situation? There is a good solution... but you're right that calling header();die();
isn't it :). This prevents Symfony from running different shutdown tasks that your app likely depends on.
So, let me know a bit more! Cheers!
Thanks for the detailed answer... it will help me be a better coder. I ended up solving the issue without the redirect. The issue had to do with cloning an entity and the cache-type issue I was having which you helped me solve in another thread.
My cloning process was a service that returned the Id of the cloned entity. That cloned entity had relations to other entities that were cloned as well. In the calling controller I was querying for that top cloned entity by the returned ID, but I was getting strange results on the related entities due to Doctrines efficiency management. So to solve the cache-type issue, I was creating the entity then redirecting to another page (but the redirect needed to be done from the service) to finish things up. The redirect would create a new session on the parent entity, clearing the cache-type issue.
So the resolution was to do a more detailed cloning.
Details in this discussion for anyone that comes across the same issue:
https://disqus.com/home/dis...
Thanks again!
Hi Ryan,
You have a nice tutorial about the Symfony! But i'm stuck on this chapter.
I use PHPstorm and i created the new folder in AppBundle named 'Service'.
I also created the file 'MarkdownTransformer' with the right code.
Now if i go back to the controller and try to typ '$markdownParser = new $MarkdownTransformer' there is one auto complication but not the one in the Service map... I have tried to fix it manually and add in the top 'use AppBundle\Service\MarkdownTransformer' but then the Service part become red. It looks like he don't recognised the folder.
Can you please help my!
Thank you
Hey Patrick Van Krugten!
Thanks man - glad you're enjoying the tutorial! :)
Ok, about your issue: open your MarkdownTransformer class and make sure the namespace
at the top is set correctly (should be AppBundle\Service
). The clue is that PhpStorm is auto-completing your class, but is not auto-completing with the correct use
statement. When PhpStorm autocompletes, it simply reads the namespace
from that target class (e.g. MarkdownTransformer
) and uses that for the use
statement. If the namespace is wrong or missing, you'll get the wrong auto-complete! You did a good job at recognizing that this incorrect auto-complete was a sign of a problem!
Let me know if that fixes the issue!
Cheers!
Thx weaverryan
The problem was i had not a namespace.
i had created a new project and forgot to set the source root in PHPStorm
Thank you again!
---
Can i also send you a another question? but it's not about this course but about building a small application with symfony to practice. I'm already started, but i'm not nothing what the do next and how to.
I have recently downloaded the course files. While using the start folder, every time I try to access localhost:8000/genus I get a 404 error displaying "No route found for "GET /genus/" ". I can however, access the homepage and localhost:8000/genus/new. Is there an issue with the files or is it an issue on my end? Perhaps I missed a step?
Worked it out. Above list action, the *@Route("/genus") needs a forward slash like so: *@Route("/genus/")
Hey Stc
As you just figured it out, Symfony routing system has that defect when working with paths that does not end with a slash, but that's about to change in Symfony 4.1. Check this out: https://symfony.com/blog/ne...
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.1.*", // v3.1.4
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.4
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
"doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0", // v3.1.3
"nelmio/alice": "^2.1", // 2.1.4
"doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
}
}
I know it gets old but there you sure are good at teaching and keeping ppl engaged in your lectures! kudos to you!
for real you are the first caster{-that-teaches-something} that I've ever seen (outside a really few good starcraft2 casters) that really keeps me interested in what he/she is saying.