Twig Slimframework base template data - symfony

so I've been working on a simple website and have a simple question, I guess. I have my base template named template.html and I'm using it on every html page I have. My question is, can a base template have a data from a database? Because it isn't being rendered right? For example I have here my 404 page (I'm using slim framework btw).
$app->notFound(function () use ($app) {
$app->render('404.html');
});
And what is inside looks like this:
As you can see in the image, there is the part <!-- Loaded data from the base template.html -->. Can a base template have a data from a database? If so, how? Thank you!

You can use inheritance.
Simple method
Your route definition:
<?php
/**
* Not found.
*/
$app->notFound(function () use ($app) {
//Your amazing business to get data
$books = array(
"The Hobbit",
"Leaf by Niggle",
"The Lay of Aotrou and Itroun",
"Farmer Giles of Ham",
"The Homecoming of Beorhtnoth Beorhthelm's Son"
);
$app->render('404.html', array(
'books' => $books
));
});
Your template.html:
<!DOCTYPE html>
<html>
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">
{% block content %}
{% block books %}
<h3>My magic books</h3>
<ul class="books">
{% for book in books %}
<li>{{ book }}</li>
{% endfor %}
</ul>
{% endblock %}
{% endblock %}
</div>
</body>
</html>
Your 404.html:
{% extends "template.html" %}
{% block title %}404{% endblock %}
{% block content %}
{% block books %}
<div id="">
{{ parent() }}
</div>
{% endblock %}
{% endblock %}
Hook method
If you need it everywhere, you could use a hook.
$app->hook('slim.before.router', function() use ($app) {
//Your amazing business to get data
$books = array(
"The Hobbit",
"Leaf by Niggle",
"The Lay of Aotrou and Itroun",
"Farmer Giles of Ham",
"The Homecoming of Beorhtnoth Beorhthelm's Son"
);
$app->view()->setData('books', $books);
});
//And let your notFound handler be light
$app->notFound(function () use ($app) {
$app->render('404.html');
});

Yes, a base template can display a DB content:
you have to retrieve the data you want, render it to specific template to display and then, in your base template, you use the render(controller) method, something like
public function xyzAction(){
$data=$this->getDoctrine .... ;
return $this->render('XYZYourBundle:test.html.twig',array('data'=>$data));
}
in test.html.twig:
{℅ for d in data %}
// do your stuff
{% endfor ℅}
and in where you want to add this to your base template:
{{ render(controller('XYZYourBundle:Controller_name:xyz'))}}
you can pass arguments to the controller action too, this would be helpful.

In Slim 3 you can use Middleware instead hooks.
BooksMiddleware.php
class BooksMiddleware{
protected $container;
public function __construct($container) {
$this->container = $container;
}
public function __invoke($request, $response, $next){
$sql = "SELECT * FROM books";
$stmt = $this->container->db->prepare($sql);
$stmt->execute();
$books= $stmt->fetchAll(PDO::FETCH_OBJ);
$request = $request->withAttribute('books', $books);
$response = $next($request, $response);
return $response;
}
}
routes.php
$app->get('/home', '\HomeController:home')->add(BooksMiddleware::class);
$app->get('/about', '\AboutController:about')->add(BooksMiddleware::class);
HomeController.php
class HomeController{
protected $container;
public function __construct($container) {
$this->container = $container;
}
public function home($request, $response) {
return $this->container->view->render($response, 'home.html', [
'books' => $request->getAttribute('books')
]);
}
AboutController.php
class AboutController{
protected $container;
public function __construct($container) {
$this->container = $container;
}
public function about($request, $response) {
return $this->container->view->render($response, 'about.html', [
'books' => $request->getAttribute('books')
]);
}
}

Related

twig inheritance and symfony2 controller variables

Im trying my first project using symfony2 + twig. I created basic twig template with defined blocks. It basically looks like this
{% block content %}
some content...
{% endblock %}
{% block footer %}
{{ footer.content}}
{% endblock %}
I want footer to be same for all pages. Footer is loaded from DB and its set in controller. I wanted inherit from template described above to other pages but I have to always set footer in controller otherwise variable is not defined.
My questions is if exists any 'nice' way how to set footer variable for multiple templates inherited from parent template?
The solution : Embedding Controllers
In some cases, you need to do more than include a simple template.
Suppose you have a sidebar in your layout that contains the three most
recent articles. Retrieving the three articles may include querying
the database or performing other heavy logic that can't be done from
within a template.
The solution is to simply embed the result of an entire controller
from your template. First, create a controller that renders a certain
number of recent articles:
Controller
// src/AppBundle/Controller/ArticleController.php
namespace AppBundle\Controller;
// ...
class ArticleController extends Controller
{
public function recentArticlesAction($max = 3)
{
// make a database call or other logic
// to get the "$max" most recent articles
$articles = ...;
return $this->render(
'article/recent_list.html.twig',
array('articles' => $articles)
);
}
}
View
{# app/Resources/views/article/recent_list.html.twig #}
{% for article in articles %}
<a href="/article/{{ article.slug }}">
{{ article.title }}
</a>
{% endfor %}
Layout
{# app/Resources/views/base.html.twig #}
{# ... #}
<div id="sidebar">
{{ render(controller(
'AppBundle:Article:recentArticles',
{ 'max': 3 }
)) }}
</div>
you can do with one of the following, 'write a custom Twig Extension'
<?php
namespace AppBundle\Extension;
class MyTwigExtension extends \Twig_Extension
{
private $em;
private $conn;
public function __construct(\Doctrine\ORM\EntityManager $em) {
$this->em = $em;
$this->conn = $em->getConnection();
}
public function getFunctions()
{
return array(
'users' => new \Twig_Function_Method($this, 'getUsers'),
);
}
public function getUsers()
{
$sql = "SELECT * FROM users ORDER BY accountname";
return $this->conn->fetchAll($sql);
}
public function getName()
{
return 'smproc4_twig_extension';
}
}
Register an Extension as a Service
services:
my.twig.extension:
class: AppBundle\Extension\MyTwigExtension
tags:
- { name: twig.extension }
arguments:
em: "#doctrine.orm.entity_manager"
Using the custom Extension
Hello {{ name }}!
<ul>
{% for user in users() %}
<li>{{ user.accountname }}</li>
{% endfor %}
</ul>
have a look at the
How to Write a custom Twig Extension
Creating an Extension

Symfony controller tutorial error

I'm following the walk through for learning how to use Symfony (http://symfony.com/doc/current/quick_tour/the_controller.html) however after following through the "Route Parameters" section and going to localhost/hello/fabien I receive:
"404 Not Found The requested URL /hello/fabien was not found on this server.".
However, I can't see what I'm doing wrong - below is the code I have:
config.yml
# ...
framework:
templating:
{engines: ['twig','php']}
DefaultController:
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
/**
* #Route("/", name="homepage")
*/
public function indexAction()
{
/** return new Response('Welcome to Symfony!');*/
return $this->render('default/index.html.twig');
}
/**
* #Route("/hello/{name}", name="hello")
*/
public function helloAction($name)
{
return $this->render('default/hello.html.twig', array(
'name' => $name
));
}
}
base.html.twig
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>
hello.html.twig
{# app/Resources/views/default/hello.html.twig #}
{% extends 'base.html.twig' %}
{% block body %}
<h1>Hi {{ name }}! Welcome to Symfony!</h1>
{% endblock %}
The error message is self explanatory. The code should be like this:
/**
* #Route("/hello/{name}", name="hello")
*/
public function helloAction($name)
{
return $this->render('AppBundle:default:hello.html.twig', array(
'name' => $name
));
}
or simplified using annotations:
/**
* #Route("/hello/{name}", name="hello")
* #Template()
*/
public function helloAction($name)
{
return array(
'name' => $name
);
}
In my case I just removed {% extends 'mybase.html.twig' %} and run the code it started working and then I again put it at same place and it is working. it seems might be some cache issue. so I request you to clear cache as well.
Thanks
Rikkin

Set Twig layout in controller

I have multiple subdomains, each with its own layout. Some controllers are shared across subdomains (e.g login), some not. What I'd like to do is to set layout according to domain, so that I would not need to write in each template:
{% if app.request.domain == 'one' %}
{% set layout = '::layout-one.html.twig' %}
{% elseif app.request.domain == 'two' %}
{% set layout = '::layout-two.html.twig' %}
...
{% endif %}
{% extends layout %}
Is it possible to set default layout in controller (or somewhere)? E.g:
class FooController
{
function fooAction()
{
...
$templating = $this->get('templating');
$templating->setLayout($layout);
return $templating->renderResponse($view, $parameters, $response);
}
}
If you have a separate config file for each of the domains, you can put the layout config in there and have it available in twig as a global variable:
config_one.yml
twig:
globals:
base_layout: '::layout-one.html.twig'
Then in twig you can just do:
{% extends base_layout %}
You can set layout variable in your FooController:
class FooController
{
function fooAction()
{
...
return $this->render($template, array(
'layout' => $layout
));
}
}
And then use it in your template:
{% extends layout %}

FormComponent - Pass Entity with Form to Template

Lets say i have a one to many relation:
A company can own several pictures
I have company entity and image entity
Now, I want to display all pictures of a company within a template. And i also want to make theese pictures directly editable. I thought of adding a form to each Entity of the DoctrineArrayCollection and pass them to the template. In Template if somebody clicks on a picture the corresponding should be fade in, the should be able to edit the pictures description and pass it through ajax to a controller.
In my entity I added a field without annotations:
private $form;
public function setForm(MyPictureForm $form)
{
$this->form = $form;
}
public function getForm()
{
return $this->form;
}
Now in my controller I add a form instance to every picture of the company:
//office images with forms
$officeImages = array();
foreach($company->getOfficeImages() as $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
$image->setForm($form->createView());
array_push($officeImages, $image);
}
return $this->render('CompanyBundle:Company:Show/show.html.twig', array(
'company' => $company,
'officeImages' => $officeImages
));
}
And in my template i render it like this way:
{% for image in officeImages %}
<a href="#" title="{% if image.description %}{{ company.description }}{% else %}CLICK HERE FOR EDIT{% endif %}">
{% if image.image %}
<img src="{{ vich_uploader_asset(image, 'image') | imagine_filter('company_office_image_thumb') }}"
alt="{% if image.description %}{{ company.description }}{% endif %}"/>
{% else %}
{% image '#UserBundle/Resources/public/img/nopic_logo.jpg' output='/images/nopic_logo.jpg' %}
<img src="{{ asset_url }}" alt="Joblogo"/>
{% endimage %}
{% endif %}
</a>
{{ form(image.form) }}
{% else %}
<p>Es sind noch keine Images vorhanden</p>
{% endfor %}
At the end there is a lot if javascript stuff which handels the fade in / fade out of the form and their submitting.
Is this the correct way to handle my case? I think not cause passing a form for every picture seems like overhead?
The reason why I am working with forms instead of simply passing data out of a manual added input field is csrf protection and the smart usage of the form component.
As you said keeping form object in entity isn't a good idea. It should represents model data.
I've got two solutions for this:
I. Pass array of forms with image id as a key
$officeImages = array();
$imageForms = array();
foreach($company->getOfficeImages() as $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
$imageForms[$image->getId()] = $form->createView();
}
return $this->render('CompanyBundle:Company:Show/show.html.twig', array(
'company' => $company,
'officeImages' => $officeImages,
'imageForms' => imageForms
));
and in show.html.twig
{% for image in officeImages %}
{# your image display code #}
{{ form(imageForms[image.id]) }}
{% endfor %}
II. render partial for single image
in controller
public function showAndEditImageAction(Image $image)
{
$form = $this->get('companybundle.imagedescription.form.factory')->createForm();
$form->setData($image);
return $this->render(
'CompanyBundle:Company:Show/showAndEditImage.html.twig', array(
'image' => $image,
'imageForm' => $form->createView()
));
}
in twigs
{# CompanyBundle:Company:Show/showAndEditImage.html.twig #}
{# your image display code #}
{{ form(imageform) }}
{# CompanyBundle:Company:Show/show.html.twig #}
{% for image in officeImages %}
{{ render(controller('CompanyBundle:Company:showAndEditImage',
{ 'image': image })) }}
{% endfor %}

Symfony2 FOSUserBundle Overriding Forms

I am trying to change the template for the registration form in my application so that I can add some other HTML to it. Here is the /My/UserBundle/Resources/views/Registration/register.html.twig file:
{% extends "MyUserBundle::layout.html.twig" %}
{% block fos_user_content %}
<section class="register site-content">
<header>
<h1>{{ 'layout.register'|trans({}, 'FOSUserBundle') }}</h1>
</header>
<div class="block">
{% include "FOSUserBundle:Registration:register_content.html.twig" %}
</div>
</section>
{% endblock fos_user_content %}
And I have successfully overridden the layout.html.twig:
{% extends 'MyMainBundle::layout.html.twig' %}
{% block title %}{{ site_name }}{% endblock %}
{% block content %}
{% for key, message in app.session.getFlashes() %}
<div class="{{ key }}">
{{ message|trans({}, 'FOSUserBundle') }}
</div>
{% endfor %}
{% block fos_user_content %}{% endblock %}
{% endblock %}
as well as form.html.twig:
{% extends 'FOSUserBundle::form.html.twig' %}
{% block field_row %}
<li class="form_row">
{{ form_label(form) }}
{{ form_errors(form) }}
{{ form_widget(form) }}
</li>
{% endblock field_row %}
{% block form_widget %}
<ul {{ block('container_attributes') }}>
{{ block('field_rows') }}
{{ form_rest(form) }}
</ul>
{% endblock form_widget %}
config parts:
# FOS User Configuration
fos_user:
db_driver: orm
firewall_name: main
user_class: My\UserBundle\Entity\User
from_email:
address: %admin_email%
sender_name: %site_name%
template:
engine: twig
theme: MyUserBundle::form.html.twig
I have cleared my cache.
Whenever I go to
http://localhost/register/
apache just hangs until it times out.
The best I can figure out, is the PHP maximum execution message says it crashes on a twig template in the cache on line 16. That line is function doGetParent(...) The file is:
<?php
/* FOSUserBundle::form.html.twig */
class __TwigTemplate_9cf68a2af1db50466c556a735bcdeba0 extends Twig_Template
{
public function __construct(Twig_Environment $env)
{
parent::__construct($env);
$this->blocks = array(
'field_row' => array($this, 'block_field_row'),
'form_widget' => array($this, 'block_form_widget'),
);
}
protected function doGetParent(array $context)
{
return "FOSUserBundle::form.html.twig";
}
protected function doDisplay(array $context, array $blocks = array())
{
$this->getParent($context)->display($context, array_merge($this->blocks, $blocks));
}
// line 3
public function block_field_row($context, array $blocks = array())
{
// line 4
echo " <li class=\"form_row\">
";
// line 5
echo $this->env->getExtension('form')->renderLabel($this->getContext($context, "form"));
echo "
";
// line 6
echo $this->env->getExtension('form')->renderErrors($this->getContext($context, "form"));
echo "
";
// line 7
echo $this->env->getExtension('form')->renderWidget($this->getContext($context, "form"));
echo "
</li>
";
}
// line 11
public function block_form_widget($context, array $blocks = array())
{
// line 12
echo " <ul ";
$this->displayBlock("container_attributes", $context, $blocks);
echo ">
";
// line 13
$this->displayBlock("field_rows", $context, $blocks);
echo "
";
// line 14
echo $this->env->getExtension('form')->renderRest($this->getContext($context, "form"));
echo "
</ul>
";
}
public function getTemplateName()
{
return "FOSUserBundle::form.html.twig";
}
public function isTraitable()
{
return false;
}
}
It has also timed out on \vendor\twig\lib\Twig\Template.php on line 65 Which is public function getParent(array $context)
So clearly there is some problem with getParent but I don't know what that means or how to fix it.
According to the FOSUserBundle documentation:
The easiest way to override a bundle's template is to simply place a
new one in your app/Resources folder. To override the layout template
located at Resources/views/layout.html.twig in the FOSUserBundle
directory, you would place you new layout template at
app/Resources/FOSUserBundle/views/layout.html.twig.
As you can see the pattern for overriding templates in this way is to
create a folder with the name of the bundle class in the app/Resources
directory. Then add your new template to this folder, preserving the
directory structure from the original bundle.
In my project I override FOSUserBundle's layout as they said and it's work like a charm.
So doing it at the same way you will need to create app/Resources/FOSUserBundle/views/Registration/register.html.twig. (or the form you want to override)
EDIT
Ok, I just realize that you've chosen to extend the FOSUserBundle. In that case instead of app/Resources/ you need to do it inside your bundle. But you don't need to put
{% extends 'FOSUserBundle::form.html.twig' %}
The FOSUserBundle will detect that you are overriding the bundle and will be extended automatically.
And you also need to tell your bundle that FOSUserBundle is its parent.
class YourBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}
Copy the file:
{% include "FOSUserBundle:Registration:register_content.html.twig" %}
to the: app/Resources/FOSUserBundle/views/ catalogue. Then it will overwrite the base FOS form. You can edit the copied file.
Otherwise you must override the FOS RegisterController.
I feel stupid, just removed the {% extends ... %} line in form.html.twig and it worked. I guess I don't understand enough about what is inherited in bundles. (I did want to inherit from the FOSUserBundle form.html.twig, but I don't know how to access that from a bundle which inherits from FOSUserBundle)

Resources