Symfony: service.yaml

In symfony package, you could find this file under : config directory

This file job is to figure out all of the services that should be in the container when symfony load

Services comes from external bundles, and also inject your own custom service,

Let’s look at service.yaml file:

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        public: false       # Allows optimizing the container by removing unused services; this also means
                            # fetching services directly from the container via $container->get() won't work.
                            # The best practice is to be explicit about your dependencies anyway.

the point here is autowire: true, which means that any services registered in this file should have the autowiring behavior turned on

And look the key point follow:

App\:
    resource: '../src/*'
    exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'

App\Controller\:
    resource: '../src/Controller'
    tags: ['controller.service_arguments']

look at App: resource, that’s means:
Make all classes inside src/ available as services in the container.

that’s why you could put any class inside the src and make it called by symfony

to verify, go to terminal:

$ php bin/console debug:autowiring

as you could see what we had add the controller class before, it has put in autowiring lists

and don’t worry, for better performance:

Services are only Instantiated Once

App\:
    resource: '../src/*'
    exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'

exclude key: if you know that some classes don’t need to be in the container, you can exclude them for a small performance boost in the dev environment only.

And what happend if you need to configure one service ?

Let’s do it:

Step 1 found out your services Id

Services Ids = Class Name

go to your terminal:
$ php bin/console debug:container –show-private

you could find your service Id, for example, for markdown:

App\Service\MarkdownHelper App\Service\MarkdownHelper

Step 2 inject monolog logger service
and in order to inject this sevice and make it autowiring by service.yaml

track back $ php bin/console debug:autowiring

Psr\Log\LoggerInterface
alias to monolog.logger

go to src/ Service/ MarkdownHelper.php

add use Psr\Log\LoggerInterface;

and inject argument LoggerInterface

public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $logger)
{
    $this->cache = $cache;
    $this->markdown = $markdown;
    $this->logger = $logger;
}

add this logger info in parse function :

if (stripos($source, 'Spicy') !== false) {
    $this->logger->info('They are talking about spicy again!');
}

Step 3 find your service Information

go to terminal

$ ./bin/console debug:container monolog.logger

here you find Service ID monolog.logger

Step 4 Creating a new Logger Channel

Go to config/ packages, add monolog.yaml:

monolog:
    channels: ['markdown']

once add, go to terminal to clean catch:

$ ./bin/console cache:clear

$ ./bin/console debug:container log

you could see:
[44] monolog.logger.markdown

then this is said you had a new logger service – monolog.logger.markdown!

Step 5 Fetching this non-standard service to service.yaml

go to service.yaml file

add follow

# setup special, global autowiring rules
bind:
    $markdownLogger: '@monolog.logger.markdown'

 

Step 5 Logger object to your controller

go to src/Service/MarkdownHelper.php

public function __construct(AdapterInterface $cache, MarkdownInterface $markdown, LoggerInterface $markdownLogger)
{
    $this->cache = $cache;
    $this->markdown = $markdown;
    $this->logger = $markdownLogger;
}

notice you made changed LoggerInterface $logger to LoggerInterface $markdownLogger

because you had add bind logger object in your service.yaml

because we added it to _defaults, it applies to all our services.

github commit here

*** Thanks to bind, we can define what values are passed to specific argument names. But, we can go further and control what value should be passed for a specific type hint ***

Symfony: Security Fundamentals

Ok, you might want continue the login and register part
but hold on a second, here what you need to do first is understand the security fundamental in symfony

Actually security is like before you enter a president house, you need to have guardian give a pass card
so in programming, this pass card is an token

the token could assign you : allow go inside, not allow, or where you should go

so that’s hold this basic concept and dig into symfony security fundamental:
Security configuration: security.yml

go to config/packages, open security.yml

line 5

firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false

make security: true

then go to your browser:

so you could see the page is not work, cause we set firewalls

move back:

security: false

then you could see the anonymous token from this code:

anonymous: ~

and from debugbar:

profiler:
anonymous: ~

so this is allowed anyone to access page cause so far there’s no any login system yet

but if we comment out this key?

#anonymous: ~

then you should see the page try to redirect to: http://127.0.0.1:8000/login

this is because you required to login to pass the firewall now

uncomment out, the page show up again

so this is very fundamental security part

And one more security part is access_control:

access_control:
    #- { path: ^/admin, roles: ROLE_ADMIN }
    #- { path: ^/profile, roles: ROLE_USER }

this is as you are role admin, you will lead to any /admin path, and if you are user, you will lead to any /profile path

or if you defined as follow:

access_control:
        - { path: ^/new, roles: ROLE_USER }
        - { path: ^/register, roles: ROLE_USER }

this means if you are user, you will go to /new path, or if you are not login yet, you will direct to /register path

with this access_control, you could define lots event and action ( like restric to admin page, blog page allow user to give comments) should go to, the most important thing to know is that only one access_control entry is matched on a request

if your request is not make clear, it will show ou this error:

Here is the doc, if you like to learn more

Symfony: create login, register pages

Wow, this is core chapter, I means for myself before precedent chapter is actually prepare for this

Before go through all the step, we should download some core bundles–>service, tools

go to termernal, and install follow :

$ composer require symfony/form

$ composer require doctrine form security validator

composer require security

this is for user entity part

$ ./bin/console debug:autowiring

we could see in terminal had download those security alias

Oh there’s might be more complete package you could just install once for the register form, and you could read reference doc here

The first concept is before create register page, you need to have the register list in order to config weather user has been registered or not ( then login), so we go to the first step to create list users page

So let’s start do it

Opps, before start, create your userType and User entity, cause later in Register controller we need to call those to bind the actions

Go to src/ create Form directory and create UserType.php :

<?php
namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class)
            ->add('email', EmailType::class)
            ->add('plainPassword', RepeatedType::class, [
                'type' => PasswordType::class,
                'first_options' => ['label' => 'Password'],
                'second_options' => ['label' => 'Confirm Password'],
            ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'App\Entity\User',
        ]);
    }
}

those you could find the symfony register doc here,

And also create Entity directory then create User.php file:

<?php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * @ORM\Entity
 * @UniqueEntity(fields="email", message="This email address is already in use")
 */
class User implements UserInterface
{
    /**
     * @ORM\Id;
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="string", length=255, unique=true)
     */
    protected $email;

    /**
     * @ORM\Column(type="string", length=40)
     */
    protected $name;

    /**
     * @ORM\Column(type="string", length=50)
     */
    protected $role;

    /**
     * @Assert\Length(max=4096)
     */
    protected $plainPassword;

    /**
     * @ORM\Column(type="string", length=64)
     */
    protected $password;

    public function eraseCredentials()
    {
        return null;
    }

    public function getRole()
    {
        return $this->role;
    }

    public function setRole($role = null)
    {
        $this->role = $role;
    }

    public function getRoles()
    {
        return [$this->getRole()];
    }

    public function getId()
    {
        return $this->id;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getUsername()
    {
        return $this->email;
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function setPassword($password)
    {
        $this->password = $password;
    }

    public function getPlainPassword()
    {
        return $this->plainPassword;
    }

    public function setPlainPassword($plainPassword)
    {
        $this->plainPassword = $plainPassword;
    }

    public function getSalt()
    {
        return null;
    }
}

This is also from symfony doc, which allow the data validate

All right, the user entity is build, this could allow you get through register and login binding data methods, start now:

Step 1 create your user list controllers

go to src/Controller, and create this controllers: ListController.php :

<?php
namespace App\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;


class ListController extends Controller
{
    /**
     * @Route("/welcome", name="welcome")
     */
    public function showAction(Request $request)
    {
        $characters = [
            'Burger lover' => 'Tess Hsu',
            'Pasta lover'           => 'Anais siah',
            'Arya Stark'         => 'Maisie Williams',
            'Melisandre'         => 'Carice van Houten',
            'Khal Drogo'         => 'Jason Momoa',
            'Tyrion Lannister'   => 'Peter Dinklage',
            'Ramsay Bolton'      => 'Iwan Rheon',
            'Petyr Baelish'      => 'Aidan Gillen',
            'Brienne of Tarth'   => 'Gwendoline Christie',
            'Lord Varys'         => 'Conleth Hill'
        ];

        return $this->render('default/index.html.twig', array('character' => $characters));
    }
}

here set the route to show user list by this path: http://127.0.0.1:8000/welcome

defined user’s character and real name ( as key value array)
then ready to render to this view template: index.html.twig

Step 2 your list view page

go to template/, create directory defalut, and create a file called: index.html.twig

{% extends 'base.html.twig' %}

{% block body %}
    <div class="container">
        <div class="row">
            <div class="col-md-10 col-md-offset-1">
                <div class="panel panel-success">
                    <div class="panel-heading">List of Game of Thrones Characters</div>

                    {% if app.user != null %}
                        <table class="table">
                            <tr>
                                <th>Character</th>
                                <th>Real Name</th>
                            </tr>

                            {% for key, item in character %}
                                <tr>
                                    <td>{{ key }}</td><td>{{ item }}</td>
                                </tr>
                            {% endfor %}
                        </table>
                    {% endif %}


                </div>

                {% if app.user == null %}
                    <a href="/login" class="btn btn-info"> You need to login to see the list 😜😜 >></a>
                {% endif %}
            </div>
        </div>
    </div>
{% endblock %}

Here load the list from listController, and make href to login page:

{% if app.user == null %}
    <a href="/login" class="btn btn-info"> You need to login to see the list 😜😜 >></a>
{% endif %}

Yes!!! great, last step is get the burger menu Account link to this page:

go to template/base.html.twig

<a class="dropdown-item" href="{{ path('welcome') }}">Account</a>

add the path name: welcome as we had defined in listController.php route notation

go back to browser, and click the burger menu: Account

then you should link to this page

Right on!!!!! you had seen the list page as above just created

we want to the link: You need to login to see the list work, so next let’s create login page

Step 3 Create security login controller

go to src/Controller, create file called SecurityController.php

<?php

namespace App\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class SecurityController extends Controller
{
    /**
     * @Route("/login", name="login")
     */
    public function loginAction(Request $request)
    {
        $helper = $this->get('security.authentication_utils');

        return $this->render(
            'auth/login.html.twig',
            array(
                'last_username' => $helper->getLastUsername(),
                'error'         => $helper->getLastAuthenticationError(),
            )
        );
    }

    /**
     * @Route("/login_check", name="security_login_check")
     */
    public function loginCheckAction()
    {

    }

    /**
     * @Route("/logout", name="logout")
     */
    public function logoutAction()
    {

    }
}

here define the route to login page in loginAction ( ),

when you see this :

$helper = $this->get('security.authentication_utils');

this is actually Added a security error helper, the origin above code is doing follow:

$session = $request->getSession();

    if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
        $error = $request->attributes->get(
            SecurityContextInterface::AUTHENTICATION_ERROR
        );
    } elseif (null !== $session && $session->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
        $error = $session->get(SecurityContextInterface::AUTHENTICATION_ERROR);
        $session->remove(SecurityContextInterface::AUTHENTICATION_ERROR);
    } else {
        $error = '';
    }

from symfony 2.6 you could reduced code simply by get sercurity.authentication_utils

then render the view and then the login_check route will be hit once a user clicks the login button. A user will be logged out once the logout route has been hit.

Step 4 Create login template view page

go to template/ create auth directory and create a file login.html.twig:

{% extends 'base.html.twig' %}

{% block body %}
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Login</div>
                    <div class="panel-body">
                        <form class="form-horizontal" role="form" method="POST" action="{{ path('security_login_check') }}">
                            {% if error %}
                                <div class="alert alert-danger">
                                    {{ error.messageKey|trans(error.messageData) }}
                                </div>
                            {% endif %}

                            <div class="form-group">
                                <label for="email" class="col-md-4 control-label">E-Mail Address</label>

                                <div class="col-md-6">
                                    <input id="email" type="email" class="form-control" name="_email" value="{{ last_username }}">
                                </div>
                            </div>

                            <div class="form-group">
                                <label for="password" class="col-md-4 control-label">Password</label>

                                <div class="col-md-6">
                                    <input id="password" type="password" class="form-control" name="_password">
                                </div>
                            </div>

                            <div class="form-group">
                                <div class="col-md-6 col-md-offset-4">
                                    <button type="submit" class="btn btn-primary">
                                        <i class="fa fa-btn fa-sign-in"></i> Login
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

Right here you first create your login page, go to browser and check:

Step5 create RegisterController

go to src/Controller and create file called RegisterController.php

<?php
namespace App\Controller;

use App\Entity\User;
use App\Form\UserType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class RegistrationController extends Controller
{
    /**
     * @Route("/register", name="register")
     */
    public function registerAction(Request $request)
    {
        // Create a new blank user and process the form
        $user = new User();
        $form = $this->createForm(UserType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            // Encode the new users password
            $encoder = $this->get('security.password_encoder');
            $password = $encoder->encodePassword($user, $user->getPlainPassword());
            $user->setPassword($password);

            // Set their role
            $user->setRole('ROLE_USER');

            // Save
            $em = $this->getDoctrine()->getManager();
            $em->persist($user);
            $em->flush();

            return $this->redirectToRoute('login');
        }

        return $this->render('auth/register.html.twig', [
            'form' => $form->createView(),
        ]);
    }

In this controller we will use User and UserType class:

use App\Entity\User;
use App\Form\UserType;

we will add those later on

inside registerAction ( ) function, it is to handle the form rendering and submission. If the form is submitted, the controller performs the validation and saves the data into the database, those code from symfony register form doc
Handling the Form Submission part

the next step is to render this form. This is done by passing a special form “view” object to your template : ( thank for Form bundles we had download before)

$form->createView()

Step6 create Register page view

go to template/auth and create file called register.html.twig:

{% extends 'base.html.twig' %}

{% block body %}
    <div class="container">
        <div class="row">
            <div class="col-md-8 col-md-offset-2">
                <div class="panel panel-default">
                    <div class="panel-heading">Register</div>
                    <div class="panel-body">
                        {{ form_start(form) }}
                        <div class="form_group">
                            <div class="col-md-8 col-md-offset-2">
                                {{ form_row(form.name, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <div class="form_group">
                            <div class="col-md-8 col-md-offset-2">
                                {{ form_row(form.email, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <div class="form_group">
                            <div class="col-md-8 col-md-offset-2">
                                {{ form_row(form.plainPassword.first, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <div class="form_group">
                            <div class="col-md-8 col-md-offset-2">
                                {{ form_row(form.plainPassword.second, {'attr': {'class': 'form-control'}}) }}
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-8 col-md-offset-4" style="margin-top:5px;">
                                <button type="submit" class="btn btn-primary">
                                    <i class="fa fa-btn fa-user"></i> Register
                                </button>
                            </div>
                        </div>
                        {{ form_end(form) }}
                    </div>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

here you could see use helper form to defined from the form build class:

{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}

And in order to get the link in burger menu, do the same add login name path in base.html.twig
Create account

Yes, go to your browser:

Right on, so here we had create Account, create Account and login pages

Take a break

github commit code

Symfony: cache service to use

This is back to the last chapter : we had inject cache service as well, so what does this service for and how to use it?

when you run this command:

$ ./bin/console debug:autowiring

you should be able to see there’s 2 same alias cache.app:

Psr\Cache\CacheItemPoolInterface                                          

      alias to cache.app

Symfony\Component\Cache\Adapter\AdapterInterface                          

      alias to cache.app

you could actually either use one of those and it turn out the same thing

Let’s use the AdapterInterface

so according last chapter, in src/Service/MarkdownHelper.php

in MarkdownHelper class construct function, had inject AdapterInterface $catch

public function __construct(AdapterInterface $cache, MarkdownInterface $markdown)

this is where you get your $catch object by cache service

And down to parse function:

$item = $this->cache->getItem('markdown_'.md5($source));
if (!$item->isHit()) {
    $item->set($this->markdown->transform($source));
    $this->cache->save($item);
}

this is to get cache item and insert the key as articelContent we had defined before

here has more this catch method doc if you will like to dig more

and to check the item if get cache, use isHit() method which returns true for cache hits, otherwise return false:

if (!$item->isHit())

if is not catch, use the set() method to set the data stored in the cache item ( which the item should be markdown), and use save() method to store it

so to check if it working, go to src/Controller/ArticleController.php

change articleContent variable:

**in dolore**

then go back your browser, YES, you do had changed the article:

and then go to profiler: http://127.0.0.1:8000/_profiler/1fe8d0?panel=cache

Yes, you did just write and read catch which has 2 calls

voila this is basic usage of catch tool, hope this help

Symfony: add your custom service

Oh, this is the core part of doing symfony project

Add your own custom service

Since we could use as much different service from installing bundles

but what happend if we need the complex method to combien those service into one method?

so this case could be solved by customise your own service

and in this service you could inject dependencies which simply use after called one method

so that’s do it!!

For this case we want the article content part has markdown formatted html, what is this? you could check the good doc here

that’s the service need to add here: MarkdownBundle

Step 1 Installing MarkdownBundle

go to terminal, type follow command:
$ composer require knplabs/knp-markdown-bundle

check in bundle.php file, you should be able see the service is been add:

Knp\Bundle\MarkdownBundle\KnpMarkdownBundle::class => ['all' => true],

then run this:

$ ./bin/console debug:autowiring

you should be see the alias has been add:

Step 2 changed twig template content variable

find the article text you want to called by twig variable:

<div class="article-text">
    <p>Spicy jalapeno bacon ipsum dolor amet veniam shank in dolore. Ham hock nisi landjaeger cow,
        lorem proident beef ribs aute enim veniam ut cillum pork chuck picanha. Dolore reprehenderit
        labore minim pork belly spare ribs cupim short loin in. Elit exercitation eiusmod dolore cow
        turkey shank eu pork belly meatball non cupim.</p>

    <p>Laboris beef ribs fatback fugiat eiusmod jowl kielbasa alcatra dolore velit ea ball tip. Pariatur
        laboris sunt venison, et laborum dolore minim non meatball. Shankle eu flank aliqua shoulder,
        capicola biltong frankfurter boudin cupim officia. Exercitation fugiat consectetur ham. Adipisicing
        picanha shank et filet mignon pork belly ut ullamco. Irure velit turducken ground round doner incididunt
        occaecat lorem meatball prosciutto quis strip steak.</p>

    <p>Meatball adipisicing ribeye bacon strip steak eu. Consectetur ham hock pork hamburger enim strip steak
        mollit quis officia meatloaf tri-tip swine. Cow ut reprehenderit, buffalo incididunt in filet mignon
        strip steak pork belly aliquip capicola officia. Labore deserunt esse chicken lorem shoulder tail consectetur
        cow est ribeye adipisicing. Pig hamburger pork belly enim. Do porchetta minim capicola irure pancetta chuck
        fugiat.</p>

    <p>Sausage tenderloin officia jerky nostrud. Laborum elit pastrami non, pig kevin buffalo minim ex quis. Pork belly
        pork chop officia anim. Irure tempor leberkas kevin adipisicing cupidatat qui buffalo ham aliqua pork belly
        exercitation eiusmod. Exercitation incididunt rump laborum, t-bone short ribs buffalo ut shankle pork chop
        bresaola shoulder burgdoggen fugiat. Adipisicing nostrud chicken consequat beef ribs, quis filet mignon do.
        Prosciutto capicola mollit shankle aliquip do dolore hamburger brisket turducken eu.</p>

    <p>Do mollit deserunt prosciutto laborum. Duis sint tongue quis nisi. Capicola qui beef ribs dolore pariatur.
        Minim strip steak fugiat nisi est, meatloaf pig aute. Swine rump turducken nulla sausage. Reprehenderit pork
        belly tongue alcatra, shoulder excepteur in beef bresaola duis ham bacon eiusmod. Doner drumstick short loin,
        adipisicing cow cillum tenderloin.</p>
</div>

we could first change this part as follow:

<div class="article-text">
    {{ articleContent }}
</div>

wow, this is just cut all those long text into one variable called

Step 3 Inject the articleContent variable in Controller

That’s put this variable in src/Controller/ArticleController.php, function show():

$articleContent = <<<EOF
    Spicy **jalapeno bacon** ipsum dolor amet veniam shank in dolore. Ham hock nisi landjaeger cow,
    lorem proident [beef ribs](https://baconipsum.com/) aute enim veniam ut cillum pork chuck picanha. Dolore reprehenderit
    labore minim pork belly spare ribs cupim short loin in. Elit exercitation eiusmod dolore cow
    turkey shank eu pork belly meatball non cupim.
    Laboris beef ribs fatback fugiat eiusmod jowl kielbasa alcatra dolore velit ea ball tip. Pariatur
    laboris sunt venison, et laborum dolore minim non meatball. Shankle eu flank aliqua shoulder,
    capicola biltong frankfurter boudin cupim officia. Exercitation fugiat consectetur ham. Adipisicing
    picanha shank et filet mignon pork belly ut ullamco. Irure velit turducken ground round doner incididunt
    occaecat lorem meatball prosciutto quis strip steak.
    Meatball adipisicing ribeye bacon strip steak eu. Consectetur ham hock pork hamburger enim strip steak
    mollit quis officia meatloaf tri-tip swine. Cow ut reprehenderit, buffalo incididunt in filet mignon
    strip steak pork belly aliquip capicola officia. Labore deserunt esse chicken lorem shoulder tail consectetur
    cow est ribeye adipisicing. Pig hamburger pork belly enim. Do porchetta minim capicola irure pancetta chuck
    fugiat.
 EOF;

and we need to call the service Markdown to make this content show out correctly with html format

Step 4 Call the service

here is the key point, we could either call the service inside the function show(), like this:

public function show($slug, MarkdownInterface $markdown)
{
  $item = $cache->getItem('markdown_'.md5($articleContent));
        if (!$item->isHit()) {
            $item->set($markdown->transform($articleContent));
            $cache->save($item);
        }
        $articleContent = $item->get();
}

( here the code is also called the catch service, all the markdown item has to transform first then save after cache, for catch service I will try to finger out later….)

But the goal is make the code more easy to extend and more readable,, reuseful, so that’s put this code in another service file, and create a file called MarkdownHelper.php

src/Service/MarkdownHelper.php

put a function called parse and use a string argument called $source which will return a string, also since inside the function has called the catch service and markdown service, it has to add in arguments:

<?php
namespace App\Service;
class MarkdownHelper
{
    public function parse(string $source, AdapterInterface $cache, MarkdownInterface $markdown): string
    {
        $item = $cache->getItem('markdown_'.md5($source));
        if (!$item->isHit()) {
            $item->set($markdown->transform($source));
            $cache->save($item);
        }
        return $item->get();
    }
}

Step 5 Inject your service to another Controller

go to src/Controller/ArticleController.php

here you could inject your markdownHelper class you just had created:

 $articleContent = $markdownHelper->parse(
            $articleContent,
            $cache,
            $markdown
        );

and remember to add this variable if need to show in show.html.twig:

return $this->render('article/show.html.twig', [
    'title' => ucwords(str_replace('-', ' ', $slug)),
    'comments' => $comments,
    'slug' => $slug,
    'articleContent' => $articleContent,
]);

Step 6 proper dependency injection

Whenever you have a service that depends on other services, like $cache or $markdown, instead of passing those in as arguments to the individual method, you should pass them via a constructor:

private $cache;
private $markdown;
public function __construct(AdapterInterface $cache, MarkdownInterface $markdown)
{
    $this->cache = $cache;
    $this->markdown = $markdown;
}

public function parse(string $source): string
{
    $item = $this->cache->getItem('markdown_'.md5($source));
    if (!$item->isHit()) {
        $item->set($this->markdown->transform($source));
        $this->cache->save($item);
    }
    return $item->get();
}

once done, you might notice there might be error shown:

this is because we had cache and markdown services in this controller, that’s add those alias on the header:

use Michelf\MarkdownInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;

then go back to articleController.php
you could just pass one argument instead since add the construct:

$articleContent = $markdownHelper->parse($articleContent);

Step 7 Final decor your twig template
Final, run your browser:

That’s what we want to do, those markdown syntag, here you could add twig filtre raw to make it safe:

{{ articleContent|raw }}

then refresh:

 

voila, this chapter is long, but it is very useful in the future if we need to add customise service

github commit code here

Symfony: config to prod

This chapter is the suite of Symfony: config file secret

since the default is in dev env, how could we change to prod env?

That’s dig out:

Step 1 Chang env file

go to the root of your project, directly open the

.env file, and change 

APP_ENV=dev

to

APP_ENV=prod

Step 2 clear cache of Symfony

you need to clear cache in Symfony, go to terminal and type:

$ ./bin/console cache:clear

you should be able see follow screen:

in this step you reload back your browser, you should be not see the debug bar as showed in dev env:


Step 3 Change the content

go to template/article/homepage.html

that’s change the French Fries :

line 65, changed to French Fries to Prod:

<p><span class="advertisement-text">New:</span> French Fries to Prod!</p>

and reload, you will see this is not changed at all, this is special in symfony which you had to clear catch each time once change env,

so go to terminal : $ ./bin/console cache:clear

then back your browser, yes!!!! you will able to see it does change:

Key point: in symfony 4, recommand to use: ./bin/console cache:warmup, here is the doc

voila, and once you want to go back dev env,
follow the same procedure: env:dev-> $ ./bin/console cache:warmup -> refresh your browser then you could see your debug_bar is back :

Symfony twig template for usage 2

well, this is just the extend this chapter
This is basic the same logical except we had the menu link and related page data load

That’s add the menu part: about

so basic what like to do here:

1 we like to click this menu and go to about page by this path: http://127.0.0.1:8000/about

2 Inside about page, we like the list the heading and content part

output result:

so that’s start:

Step 1 Add MenuController
go to src/Controller add this file: MenuController.php

the logic is the same, we need the route has /about, so add this part in symfony notation:

/**
 * @Route("/about", name="app_aboutpage")
 */

and write a aboutpage function, this function has define variable called $headings, which has title and content
then we rendre the twig template and $heading variable :

<?php
namespace App\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;


class MenuController extends AbstractController
{
    /**
     * @Route("/about", name="app_aboutpage")
     */
    public function aboutpage()
    {
        $headings = [
            'burger1' => 'I ate a normal rock once. It did NOT taste like bacon!',
            'burger2' => 'Woohoo! I\'m going on an all-asteroid diet!',
            'burger3' => 'I like bacon too! Buy some from my site! bakinsomebacon.com',
        ];
        return $this->render('article/aboutpage.html.twig', array(
            'headings' => $headings,
        ));
    }
}

Step 2 Add twig template

go to template/article
add a twig template called: aboutpage.html.twig

we merge the extends base and write the layout between body block:
{% extends ‘base.html.twig’ %}
{% block body %}
// YOUR LAYOUT HERE
{% endblock %}

Step 3 Called the data from MenuController

use twig for filtre, and us your variable $headings(key, value) to show the heading and content part:

<div class="row">
    {% for key, heading in headings %}
    <div class="col-lg-4">
        <img class="rounded-circle" src="{{ asset('images/asteroid.jpeg') }}" alt="Generic placeholder image" width="140" height="140">
        <h2>{{ key }}</h2>
        <p>{{ heading }}</p>
        <p><a class="btn btn-secondary" href="#" role="button">View details &raquo;</a></p>
    </div><!-- /.col-lg-4 -->
    {% endfor %}
</div><!-- /.row -->

Step 4 update the link path
go to templates/base.html.twig
change about link href part:

<a style="color: #fff;" class="nav-link" href="{{ path('app_aboutpage') }}">About</a>

Go to your page, then you should be able the see the output as show above

here is the github commit link

Symfony: config file secret

That’s explore more Symfony config directory doing here

Go to config file, you will see there’s basic 2 files: packages, routes, and others files:

as symfony is a set of routes & services , config file is actually configure those services

and before this, that’s dig first Symfony environment

there’s 2 env by default: dev and prod , and you could have test too if you like to add other env

And, how env work?

Go to see public/ index.php

line 18

$env = $_SERVER['APP_ENV'] ?? 'dev';

This env variable is set to either have APP_ENV or dev

And it pass to Kernel class:

$kernel = new Kernel($env, $debug);

so find out Kernel class, which is in src/Kernel.php

look 3 methods:
registerBundles(), configureContainer() and configureRoutes()

registerBundles
this method is once you had installed the new bundles, it will loads to config/bundles.php file:

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true],
    Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
    Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
    Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
    Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
];

and some are load to all, some load to dev, or test env:

Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true],

And 2 method: configureContainer()

this is package file loading, which load the into config/packages file or config/service.yaml or config/service_xxx.yaml:

with CONFIG_EXTS constant:

const CONFIG_EXTS = '.{php,xml,yaml,yml}';

it’s load any PHP, XML, YAML. YML files

if it has to load to packages file, it will load into dev, prod or test env by bundles.php definition

And 3rd method: configureRoutes()
basiclly do the same thing, except to the routes file:

protected function configureRoutes(RouteCollectionBuilder $routes)
{
    $confDir = $this->getProjectDir().'/config';

    $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob');
    $routes->import($confDir.'/{routes}/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob');
    $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob');
}

So here it is the core bundle: services ( tools ) where path to go and set to which env by Symfony

and why do we know about this?

well, it will be happend that you might need to change those bundles from dev env to prod env, so if you know those core concept it will get easier to change the env as it is required

Symfony: installed your first bundle

Here you will learn how to installed bundles

what is bundle in symfony?
bundle is like a plugin, which provide service, and remember: service == tools

so there’s lot buddle to use, mostly you could found in this github page

so which bundle you should install and in what circumstance?
Honestly I do not very clair ideal either, what you should do is go to the link and read the document

and try it!!

Step 1 install one bundle

I found the basic usage bundle called: Monolog – Logging for PHP

here is github link

Go to the document page
And go to your terminal and type :

$ composer require logger

once installed you should be see the changed in your composer.json file :

"symfony/monolog-bundle": "^3.2",

Step 2 check the autowiring

go back to terminal

./bin/console debug:autowiring

then you should be able see you had add alias to monolog.logger

great, basic we had installed successful

Step 3 Injection class to test

go to your src/Controller/ArticleController.php

tried in function toggleArticleStar to inject LoggerInterface, give the varialbel called $logger

public function toggleArticleStar($slug, LoggerInterface $logger)

then return the message:so show the loggerInterface

$logger->notice('so show the loggerInterface');

go to your endPoint star part and click, then go to this file:

var/log/dev.log

So yes!!! you you see the log is right here

This is just one of example bundle usage, and my first too, I hope you guys could get the point from this chapter and me too, way long to learn

 

Symfony twig template for usage

For iterate data there’s happend quiet often to use for each

Extra: how to use symfony twig for?

that’s take example in our previous chapter: http://blog.vacha-design.com/symfony-add-js-ajax/

Remember we had planter a article view page which has user’s comments below ?

what we like to do here, is make the output should be like follow:

to make load the names and comments load dynamic with for loop twig

that’s do it:

step 1 add data in controller
go to src/Controller/ArticleController.php

in public show function
modified the variable comments data with key value, which key == name, value == comment

 $comments = [
            'tess hsu' => 'I ate a normal rock once. It did NOT taste like bacon!',
            'Stephan' => 'Woohoo! I\'m going on an all-asteroid diet!',
            'Alexandre Frank' => 'I like bacon too! Buy some from my site! bakinsomebacon.com',
        ];

So here we had name to assign the comment with

step 2 Add for method in twig tag

go to template/article/show.html.twig

find the div has comment list:

{% for comment in comments %}
    <div class="row">
        <div class="col-sm-12">
            <img class="comment-img rounded-circle" src="{{ asset('images/alien-profile.png') }}">
            <div class="comment-container d-inline-block pl-3 align-top">
                <span class="commenter-name">Mike Ferengi</span>
                <br>
                <span class="comment"> {{ comment }}</span>
                <p><a href="#">Reply</a></p>
            </div>
        </div>
    </div>
{% endfor %}

And changed Iterating over Keys and Values

{% for key, comment in comments %}
    <div class="row">
        <div class="col-sm-12">
            <img class="comment-img rounded-circle" src="{{ asset('images/alien-profile.png') }}">
            <div class="comment-container d-inline-block pl-3 align-top">
                <span class="commenter-name"> {{ key }}</span>
                <br>
                <span class="comment"> {{ comment }}</span>
                <p><a href="#">Reply</a></p>
            </div>
        </div>
    </div>
{% endfor %}

and go to see your page, it should be show correctly

github commit here