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

Symfony service

Services is the fundamental part of Symfony

That’s make the definition much simple: service like an object ( class, method) that allow to access more functionalités like sending email, sending data or logger messages, so the basic concept for services == tools

So beside the basic Symfony elements: router, twig and logger object, you could always get more tricks to do more stuff by service

so that’s review back what is basic Symfony:

Allright, then we go to further to add service, but where could we find out those services available ?

go to terminal, type:

$  php bin/console debug:autowiring

how does those use in the symfony?

well, take example what we had done in last couple tutorials,

Step 1 find the service

go to src/Controller/ArticleController.php

in the top of page:

Yes, you will find :

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;

and this are actually in the list of Autowirable Services:

Symfony\Component\HttpFoundation

Symfony\Bundle\FrameworkBundle\Controller

Step 2 inject service in your class parametre

public function toggleArticleStar($slug, LoggerInterface $logger)

type logger it will autowriting LoggerInterface, and name an object called logger

as typing there’s already auto-writing lists of functions:

 

$logger->info('this is been rating');

Stop the server running, you should be able to see this message in terminal:

 

Voila, this is the first service usage!!

Here is the github commit part

 

 

Symfony add Js & Ajax

Now next, that’s make more reaction between page and users

Remember what is the article page looks like ?

look at the start icon, that’s allow user rating the article

so we need to make user click the start and add the numbers of clicks,

to make it work, it require :

1. Jquery onclick event

2 Send an Ajax to an API endpoint to count start

So that’s start:

Step 1 Add Jquery

go to templates/base.html.twig

Add the jquery library on the footer:

{% block javascripts %}
    <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
    <script>
        $('.dropdown-toggle').dropdown();
    </script>
{% endblock %}

then In the public/ directory, create a new js/ directory and a file inside called show_rating.js.

Add follow code:

$(document).ready(function() {
    $('.js-like-article').on('click', function(e) {
        e.preventDefault();
        var _self = this;
        $(this).toggleClass('fa-star-o').toggleClass('fa-star');
    });
});

use toggleClass to bind the icon start change, so far we finish the start change color binding event part,

and final remember since you just want this action use this js file, go to

templates/show.html.twig

in the footer add follow:

{% block javascripts %}
    {{ parent() }}
    <script src="{{ asset('js/show_rating.js') }}"></script>
{% endblock %}

we need to inhirent the parent js file too,

{{ parent() }} do this work

This will print the parent template’s block content first, and then it should be work:

click start:

Step 2 Get A Json encode Star object ( Serializer)

To do this we could add a function called toggleArticleHeart, and this funtion will return new json response

Go to src/Controller/ArticleController.php

/**
 * @Route("/news/{slug}/heart", name="article_toggle_star", methods={"POST"})
 */
public function toggleArticleStar($slug)
{
    return new JsonResponse(['hearts' => rand(5, 100)]);
}

what doing here is add Route url, and path name as article_toggle_star, method is poste

inside the toogleArticleStar, it has return the Json Response with starts data

To check if the send method is updated,

use this comment in terminal:

./bin/console debug:route

as you could see article_toggle_star function is POST and Path is :

/news/{slug}/heart

in order to check the json object is available, change GET methode instead, and add JsonResponse in the header of ArticleController.php

use Symfony\Component\HttpFoundation\JsonResponse;

And

/**
 * @Route("/news/{slug}/star", name="article_toggle_star", methods={"POST"})
 */
public function toggleArticleStar($slug)
{
    return new JsonResponse(['starts' => rand(5, 100)]);
}

Go to template/article/show.html.twig

find your star icon code and change href path:

 

Go back your browser re-run with this path: http://127.0.0.1:8000/news/{slug}/star

you should got follow page like:

Great!! you could now use this json object and send Ajax in Jquery

Step 3 Send Ajax endpoint

this step is you need to send ajax called

so first go to template/article/show.html.twig
and add this right after star icon class

<span class="js-like-article-count"></span>

give this number a class

this is for js binding with ajax done promise:

$.ajax({
    method: 'POST',
    url: $(this).attr('href')
}).done(function(data) {
    $('.js-like-article-count').html(data.stars);
})

here once click star icon, it will also called ajax called with POST method, once finished it will update stars value, which is randon number between 5-100

Voila,

once you had done, go to your browser you should be able see:

Voila

here is github code commit differ here

 

Symfony Assets function for css & Js

What next now, it’s decorate your web page, and some animation or dynamical binding events

so here we need inject css and javascript

that’s start

Step 1 put your front end file in public directory

well, front end file means: css, fonts, images

so here you could create any style you like, put under public directory

Step 2 find the stylesheets block and add a link tag

Normally you could add link with follow:

But it’s much better do this with symfony web pack, which called asset() function,
asset() function will give us more flexibility to version our assets or store them on a CDN
so you could easily replace with follow code:

<link rel="stylesheet" href="{{ asset('css/font-awesome.css') }}">
<link rel="stylesheet" href="{{ asset('css/styles.css') }}">

reload the page, and oppsssss, you will see the error:

don’t worry, that install asset

Step 3 Install asset
go to terminal and install:

$composer require asset

Viola, and personalise your page style, here is example:

Here is github code