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
Related
I'm trying to render a tree of links using Doctrine's self referencing association. I have an entity like:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="link")
* #ORM\Entity(repositoryClass="App\Repository\LinkRepository")
*/
class Link
{
/**
* #ORM\Column(name="link_id", type="integer", nullable=false, options={"unsigned"=true})
* #ORM\Id()
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $linkId;
/**
* #ORM\Column(name="name", type="string", length=50)
*/
private $name;
/**
* #ORM\Column(name="url", type="string", length=500)
*/
private $url;
/**
* #ORM\Column(name="parent_id", type="integer")
*/
private $parentId;
/**
* #ORM\OneToMany(targetEntity="Link", mappedBy="parent")
*/
private $children;
/**
* #ORM\ManyToOne(targetEntity="Link", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="link_id")
*/
private $parent;
public function __construct()
{
$this->children = new ArrayCollection();
}
public function getLinkId(): ?int
{
return $this->linkId;
}
// Getters and setters ...
/**
* #return ArrayCollection
*/
public function getChildren()
{
return $this->children;
}
}
and in my controller I have it fetching the link and calling the twig template like:
public function link(int $linkId, LinkRepository $linkRepository)
{
$link = $linkRepository->findOneBy(['linkId' => $linkId]);
return $this->render('link.html.twig',
[
'link' => $link
]);
}
And the twig template is something like this:
{% extends 'base.html.twig' %}
{% block body %}
{{ link.name }}
<h2>Children:</h2>
{% import _self as macros %}
<ul>
<li>{{ link.name }}
{{ macros.link_tree(link) }}
</li>
</ul>
{% endblock %}
{% macro link_tree(link) %}
{% import _self as macros %}
<ul>
{% for linkChild in link.children %}
<li>
{{ link.name }}
{% if linkChild.children %}
<ul>
{{ macros.link_tree(linkChild.children) }}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}
Although when I call this controller it gives me this error:
Neither the property "children" nor one of the methods "children()",
"getchildren()"/"ischildren()"/"haschildren()" or "__call()" exist and
have public access in class "Doctrine\ORM\PersistentCollection".
This seems to be when I make reference to linkChild.children in the twig template.
How can I loop recursively over children() in twig?
First of all your Doctrine mapping is invalid: You should remove the parentId field, there is no need for it since you have already added the appropriate association with parent field.
Secondly, you should use Symfony's ParamConverter to get the link inside the controller like this:
public function link(Link $link)
Yes, it is as easy as it seems, you can get the link just by typehinting the link variable in your controller action, no need to use the LinkRepository there. You can find more about ParamConverter here.
Finally, it seems to be an issue in your data, because you got an instance of Doctrine Collection when you expect an instance of Link class. Try to debug, using {{ dump() }} inside your twig template, at this point there are not enough data to help you further with this one. But you definitely should first fix the mapping issue before trying again.
So it turns out I was sending the wrong thing to the macro, it was expecting linkChild and I was passing linkChild.children. Inside the macro it was trying to reference linkChild.children.children
This was fixed by using:
{{ link.name }}
{% if linkChild.children %}
<ul>
{{ macros.link_tree(linkChild) }}
</ul>
{% endif %}
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')
]);
}
}
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
I would like auto select value from session on radio button from twig. Unfortunately following code doesn't work for me.
In my twig template
{{ form_row(reg_form.sex, {'data' : 2}) }}
My form field type (User::SEX_MALE = 1, User::SEX_FEMALE = 2)
<?php
namespace FWM\CoreBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use FWM\CoreBundle\Entity\User;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* A form field for selecting user's sex
*/
class SexType extends AbstractType
{
/**
* {#inheritDoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'choices' => array(
User::SEX_MALE => 'label.form.male',
User::SEX_FEMALE => 'label.form.female'
),
'label' => 'label.form.sex',
'expanded' => true,
]);
}
/**
* {#inheritDoc}
*/
public function getParent()
{
return 'choice';
}
/**
* {#inheritDoc}
*/
public function getName()
{
return 'user_sex';
}
}
And my display customization:
{% block form_row %}
{% set error = false %}
{% if errors %}
{% set error = true %}
{% set attr = attr|merge({'class' : attr.class|default('') ~ ' error'}) %}
{% endif %}
{{ form_widget(form, {'attr' : attr, 'error': error}) }}
{% endblock form_row %}
{% block choice_widget %}
{% spaceless %}
{% if expanded %}
{% set attr = attr|merge({'class' : attr.class|default('') ~ ' radio-row'}) %}
<div {{ block('widget_container_attributes') }}>
{{dump(form)}}
{% for child in form %}
<span class="unbreakable">{{ form_widget(child) }} {{ form_label(child) }}</span>
{% endfor %}
</div>
{% else %}
...
If I add "data => 2" to setDefaults(...), field female becomes selected, but I can't find way to make it work by passing value from twig. Could someone help me please?
#qooplmao wrote in a comment:
By the time the form is available in the twig then everything that is or was going to happen to it should have happened. You could set your default in the object class $this->createFormBuilder(Object, {your options}) and then set it or you could hard code it in your entity.
When I've created a form:
$builder = $this->createFormBuilder();
$form = $builder->add( 'add', 'button')->getForm();
and render it:
<div><button type="button" id="form_add" name="form[add]">Add</button></div>
the attributes type, id and name are created.
I want to erase this attributes but I don't know how to do it. I've tried to do:
$builder = $this->createFormBuilder();
$form = $builder->add( 'add', 'button', array( 'attr' => array() ) )->getForm();
without any success.
How could I do it?
Greetings and thanks
I messed around with this for a while and the closest I could get was to have them render with empty attributes. However, that's because the project I tested with is Symfony 2.0, and in that version it's impossible to completely remove the attributes since Symfony\Component\Form\FormView::$vars is private.
However, in Symfony 2.1 and later, that same property is public so you should be able to modify (or delete) the attributes/vars directly without being constrained by the FormView api.
First, create your own type to represent this "naked button"
src/Your/Bundle/Form/NakedButtonType.php
<?php
namespace Your\Bundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
class NakedButtonType extends AbstractType
{
/**
* (non-PHPdoc)
* #see Symfony\Component\Form.FormTypeInterface::getName()
*/
public function getName()
{
return "naked_button";
}
/**
* (non-PHPdoc)
* #see Symfony\Component\Form.AbstractType::getParent()
*/
public function getParent(array $options)
{
return 'button';
}
/**
* (non-PHPdoc)
* #see Symfony\Component\Form.AbstractType::buildViewBottomUp()
*/
public function buildViewBottomUp(FormView $view, FormInterface $form)
{
// Symfony 2.0
// This will still render the attributes, but they will have no value
$view->set('id', null);
$view->setAttribute('type', null);
// Symfomy >= 2.1
// This *should* remove them completely
unset( $view->vars['id'] );
unset( $view->vars['attr']['type'] );
}
}
Now, tell the service container how to build your type
app/config/config.yml
services:
form.type.naked_button:
class: Your\Bundle\Form\NakedButtonType
tags:
- {name: form.type, alias: naked_button}
Then update your parent form to use your new type instead of the ootb "button" type.
$builder = $this->createFormBuilder();
$form = $builder->add( 'add', 'naked_button')->getForm();
All of that being said...
If you want these buttons w/o any attributes, why not just put them like that directly into your view?
<form>
{{ form_errors(form) }}
{{ form_rest(form) }}
<div>
<button>Add</button>
</div>
</form>
All of this custom type nonsense seems like alot of overhead to render something you clearly don't need Symfony to manage for you.
While not a good idea, I will help you load the gun and shoot yourself in the foot :-)
Create a new Resources/views/Form/fields.html.twig file, and put the following in it:
{% block widget_attributes %}
{% spaceless %}
id="{{ id }}" name="{{ full_name }}"{% if read_only %} readonly="readonly"{% endif %}{% if disabled %} disabled="disabled"{% endif %}{% if required %} required="required"{% endif %}{% if max_length %} maxlength="{{ max_length }}"{% endif %}{% if pattern %} pattern="{{ pattern }}"{% endif %}
{% for attrname, attrvalue in attr %}{% if attrname in ['placeholder', 'title'] %}{{ attrname }}="{{ attrvalue|trans({}, translation_domain) }}" {% else %}{{ attrname }}="{{ attrvalue }}" {% endif %}{% endfor %}
{% endspaceless %}
{% endblock widget_attributes %}
If you really, really want to remove all the attributes on there, you can remove the {% for attrname ... %} line. That will remove the attributes from all form fields. If you add some logic, you can have it apply to just specific fields.
Next step, you need to register your fields helpers. In your app/config/config.yml file, add the following line:
twig:
form:
resources:
- 'SomeBundle:Form:fields.html.twig'