I am trying to make a messaging system and I'm facing a small problem. I have a bigger template that displays my menu and my content. The menu includes the number of new messages, and the content can be any page(compose a new message, inbox, sent).
The problem is that I have to render each of the small templates by passing the number of new received messages to each of them, calling the doctrine each time and repeating code. Is there any way to send the number only to the parent template?
Here are my templates:
This is the parent containing the newmsg variable that gives me problems.
{% extends "::base.html.twig" %}
{% block body %}
inbox : {{ newmsg }}
sent
compose
{% endblock body %}
Here is an example of child template:
{% block body %}
{{ parent() }}
{% if messageList %}
{% for message in messageList %}
<li>title = {{ message.title|e }}</li>
<li>cont= {{ message.content|e }}</li>
<li>data= {{ message.date|date('d-m-Y H:m:s') }}</li>
<li>sender= {{ message.sender|e }}</li>
<hr>
{% endfor %}
{% else %}
<div>no messages</div>
{% endif %}
{% endblock body %}
The problem is that each child template is asking me for the newmsg variable
$messages = $this->getDoctrine()->getRepository('MedAppCrudBundle:Message');
$newMessagesNo = count($messages->findBy(array('seen' => '0', 'receiver' => $this->getUser())));
return $this->render(
'MedAppCrudBundle:UserBackend\Message:new.html.twig',
array(
'form' => $form->createView(),
'newmsg' => $newMessagesNo,
)
);
And I have to write this in every single controller. Any way I can shorten this problem?
You could implement a service that gives back the newmsg value and call it on your parent template. Then it would not be necessary to pass the variable.
You can add a service in your bundle's services.yml with something like:
services:
newmessages:
class: Full\Class\Name\NewMessagesService
arguments: ["#doctrine.orm.entity_manager"]
Then, implement the Full\Class\Name\NewMessagesService class. Keep in mind that this class will need a constructor that receives an EntityManager argument. Something like:
<?php
namespace Full\Class\Name;
class NewMessagesService{
private $entityManager;
public function __construct($entityManager){
$this->entityManager = $entityManager;
}
public function methodToCalculate(){
//Perform calculation and return result
}
}
Then, in your parent template, replace {{newmsg} with:
{{ newmessages.methodToCalculate() }}
Related
TL;DR: I am currently struggling with the idea how to elegantly generate CRUDs/GRID using Twig.
Long story:
I have a Phalcon app with AngularJs, templating system is Twig.
PHP is suppose to prepare a template for CRUD that contains:
List od entities, table that contains proper headers, columns, row items, action buttons
Simple and advanced search
Mass actions block
Main buttons - Add/Import/Export etc.
AngularJs is responsible for:
Paginating results in 2 modes: Ajax Loaded entities, All entities at once
Changing number of entities per page
Displaying various forms in modals - Add/Edit/Import
Filtering entities - simple and advanced search
Everything is working right now but I am not particularly fond of how it works.
For example a simple entity with few fields needs a following template:
{% extends 'index.twig' %}
{% block containerAttr %}ng-controller="InventoryController as ctrl"{% endblock %}
{% set ctrlName = 'ctrl' %}
{% set columnWidth = 'col-xs-12' %}
{% set tableClass = 'table table-bordered table-striped table-hover' %}
{% block header %}
{% include '#crud/crud/header.twig' %}
{% endblock %}
{% block content %}
{% embed '#crud/crud/main-box.twig' %}
{% block tableHeader %}
{% autoescape false %}
{{ crud.tableHeader('Id', 'id') }}
{{ crud.tableHeader('Nazwa', 'name') }}
<th class="col-xs-1">Actions</th>
{% endautoescape %}
{% endblock %}
{% block tableRow %}
<td ng-bind="e.id"></td>
<td ng-bind="e.name"></td>
{% endblock %}
{% endembed %}
{% embed '#crud/crud/form.twig' %}
{% block modalBody %}
{% autoescape false %}
<div class="row">
{{ crud.input('ctrl.entity.name', 'Name', 12) }}
</div>
{% endautoescape %}
{% endblock %}
{% endembed %}
{% embed '#crud/crud/upload.twig' %}{% endembed %}
{% embed '#crud/crud/advancedSearch.twig' %}{% endembed %}
<script>
window.xdata = {{ xdata | json_encode | raw }};
</script>
{% endblock %}
crud in this template is responsible for generating form fields compatible with AngularJs.
Idea is: To create new CRUDs/Grids with the least amount of work required. While still giving a room for some flexibility.
Right now to make a CRUD/Grid inside my application there is a really little work required:
PHP Controller needs to contain only one line:
class SubCategoryController extends CrudBase
{
protected $entityClass = InventoryCategory::class;
}
Thanks to extending it is really easy to customize every part of it.
Model needs to have just a few fields:
class InventoryCategory extends ModelBase
{
/** #var integer */
public $id;
/** #var string */
public $name;
public function initialize()
{
$this->setSource('inventory_subcategory');
$this::setup(['castOnHydrate' => true]);
}
}
Even the AngularJs controller contains only URLs and one line:
class InventoryCategoryController extends CrudBase {
constructor($injector, $scope) {
super($injector, $scope);
this.urlSave = '/inventory/category/save';
this.urlImport = '/inventory/category/import';
this.urlExport = '/inventory/category/export';
this.urlDelete = '/inventory/category/delete';
this.init({
advancedSearch: false
});
}
}
angular.module('App').controller('InventoryCategoryController', InventoryCategoryController);
Yes I use ES6, application is not compatible with older browsers anyways and it is admin panel so I do not care about older browsers.
But now only templates remains a tedious work to copy it over and change it. Not to mention they do look pretty ugly.
So my idea was to use Annotation Reader (Phalcon has one) to read fields from Model, then transform it into a base configuration, check if there is custom configuration in json and merge it. Then based on configuration, generate this whole template on the fly.
So the grand question is:
I was wondering if I can create some class that extends Twig_Template to generate this template on the fly?
I have seen Twig cache and it should be pretty easy but then how can i use it? How can i render it? Will extend still work?
I am trying to add pagination to my current project. I am pretty new to Symfony so I am not sure if there is something out there that can help me build such. My current code looks like this:
Controller class:
class MovieDisplayController extends Controller
{
public function showAction()
{
$movies = $this->getDoctrine()->getEntityManager()->getRepository('AppBundle:Movie')->FindAll();
return $this->render('movies/index.html.twig', array(
'movies' => $movies
));
}
}
Twig template:
{% block body %}
{% if movies|length == 0 %}
There are no movie items available. Add a movie here to get started.
{% elseif movies|length != 0 %}
These are the results: <br />
<ul>
{% for x in movies %}
<li>Title: {{ x.title }} - Price: {{ x.price }} - Edit - Details - Delete</li>
{% endfor %}
</ul>
Add more movie entries
{% endif %}
{% endblock %}
This will return all results within the database. I would like to only show 5 results (rows per page) and add paging buttons below the list and I wonder how/if this is possible?
The findAll() function will not work if you want to set limit.
You can try KnpPaginatorBundle to add pagination in symfony. It will work with fine to add pagination.
https://github.com/KnpLabs/KnpPaginatorBundle
I want show the list of multiple attributes Name => value in a table overriding single field of only for PortsAdmin in ShowMapper
Ports Entity mapped with PortsAttributes Entity.
Relation of entity is OneToMany Ports with multiple attributes.
Admin View (Edit Action)
Show Action
I want change attribute view same as edit Action.
You can create a custom template for the PostAttributes:
Example:
/* ShowMapper in admin */
$showMapper->add('attributes', null, array(
'template' => 'YOUR_TEMPLATE.html.twig' // <-- This is the trick
));
In your template, you can extend the base show field (SonataAdminBundle:CRUD:base_show_field.html.twig or #SonataAdmin/CRUD/base_show_field.html.twig for symfony > 4.0), and override the field block. The variable named value stores the data in twig.
Example:
YOUR_TEMPLATE.html.twig
{% extends 'SonataAdminBundle:CRUD:base_show_field.html.twig' %}
{# for sf > 4.0 #}
{# {% extends '#SonataAdmin/CRUD/base_show_field.html.twig' %} #}
{% block field %}
{% for val in value %}
{{ val.name }} - {{ val.value }} {# I'm just guessing the object properties #}
<br/>
{% endfor %}
{% endblock %}
#SlimenTN you can try changing this line in template file:
{% extends 'SonataAdminBundle:CRUD:base_show_field.html.twig' %}
with this:
{% extends '#SonataAdmin/CRUD/base_show_field.html.twig' %}
The rest of code seems ok (I have the same in a SF4 project)
I want to show the one-to-many relationshop entity in the sonata admin's show action. I found answer to my problem at ("Sonata admin bundle, manipulate objects"). I try to implement #M Khalid Junaid's solution but I get an error "An exception has been thrown during the rendering of a template ("Warning: nl2br() expects parameter 1 to be string, object given") in SonataAdminBundle:CRUD:base_show_field.html.twig at line 13."
Did anyone here face this problem before?
GroupParticipant.php
class GroupRepresentive {
...
/**
* #ORM\OneToMany(targetEntity="GroupParticipant", mappedBy="representive", cascade={"persist", "remove"}, orphanRemoval=true)
*/
public $participant;
public function __construct() {
$this->participant = new ArrayCollection();
}
...}
GroupRepresentativeAdmin.php
protected function configureShowFields(ShowMapper $showMapper)
{
$showMapper
->add('name')
->add('eventTitle')
->add('email')
->add('person')
->add('payment.paymentType')
->add('payment.bank')
->add('payment.userAccountNumber')
->add('payment.referenceNumber')
->add('payment.paymentAt')
->end()
->with('Participant')
->add('participant', 'null', array(
'template' => 'AppBundle::Admin/groupParticipant.html.twig'
))
;
}
groupParticipant.html.twig
{% extends 'SonataAdminBundle:CRUD:base_show_field.html.twig' %}
{% block field %}
{% spaceless %}
{% endspaceless %}
{% endblock %}
I put the custom template at
Because you didn't really extend the
SonataAdminBundle:CRUD:base_show_field.html.twig
try this
{% block field %}
{# show a field of your entity for example the name #}
{{value.name}}
{% endblock %}
Though the previous answer have been accepted, I'm providing solution for users who are using Symfony 4 and Symfony 3.4 (Symfony Flex).
Field in Admin class must be like:
GroupRepresentativeAdmin.php
->add('participant', 'null', array(
'template' => 'folderName/fileName.html.twig'
));
Note that the folder must be in your templates directory. The path to your twig file must be templates/folderName/fileName.html.twig
The content in twig file must be like:
fileName.html.twig
{% extends '#SonataAdmin/CRUD/base_show_field.html.twig' %}
{% block field %}
{% spaceless %}
//Your custom operation
{% endspaceless %}
{% endblock %}
What's the best way to create breadcrumbs using knpmenu bundle in symfony 2.1.x ? Aside from using 3-rd party bundles.
UPDATE:
Hi, theunraveler, sorry for late answer. Now I've been following your example and I'm stuck at one moment. Here, code below throws an exception, that
Missing argument 2 for Acme\DemoBundle\Menu\MenuBuilder::getBreadCrumbs()
{% set item = knp_menu_get('main') %}
{{ knp_menu_render(item) }}
{% block breadcrumbs %}
{% set breadcrumbs = knp_menu_get('breadcrumbs', [], {'request': app.request, 'menu': item }) %}
{{ dump(breadcrumbs) }}
{% endblock %}
Why it doesn't accepts "item" variable?
Since version 2.0, getBreadcrumbsArray has been moved to Knp\Menu\Util\MenuManipulator.
Possible workout to this solution is to create a twig extension:
<?php
namespace Kimwild\CommonBundle\Twig;
use Knp\Menu\Util\MenuManipulator;
use Knp\Menu\ItemInterface;
class MenuManipulatorExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('menu_manipulator', array($this, 'menuManipulator')),
);
}
public function menuManipulator(ItemInterface $item)
{
$manipulator = new MenuManipulator();
return $manipulator->getBreadcrumbsArray($item);
}
public function getName()
{
return 'menu_manipulator';
}
}
Register twig extension:
kimwild_common.menu_manipulator_extension:
class: Kimwild\CommonBundle\Twig\MenuManipulatorExtension
public: false
tags:
- { name: twig.extension }
In breadcrumb.html.twig:
{% block root %}
{%- for link in menu_manipulator(item) %}
/* whatever you want to do ... */
{%- endfor %}
{% endblock %}
The Knp\Menu\MenuItem class has a getBreadcrumbsArray() method. It should return an array of items in the current active menu trail. If you are on an earlier version of KnpMenu (<= 1.1.2, I think), the returned array will be in the form of label => uri. Otherwise, it will be an array with each item having keys label, uri, and item.
To find the current menu item, you'll probably want to create a method in your controller (or somewhere else, if it makes more sense for your project) that looks something like this:
public function getCurrentMenuItem($menu)
{
foreach ($menu as $item) {
if ($item->isCurrent()) {
return $item;
}
if ($item->getChildren() && $current_child = $this->getCurrentMenuItem($item)) {
return $current_child;
}
}
return null;
}
From there, you can call getBreadcrumbsArray() on the returned value:
$this->getCurrentMenuItem($your_menu)->getBreadcrumbsArray();
I guess what I would do ultimately is create a Twig extension that registers a breadcrumbs global, and put the getCurrentMenuItem() method in there. That way, you can have the breadcrumb variable in all of your templates without having to manually render it in each controller.
Source: https://github.com/KnpLabs/KnpMenu/blob/master/src/Knp/Menu/MenuItem.php#L544.
Since KnpMenu 2.1, there is a new twig function: knp_menu_get_breadcrumbs_array
You can take a look at my gist: https://gist.github.com/fsevestre/b378606c4fd23814278a
I added a new twig function knp_menu_get_current_item, which retrieve the current menu item and work fine with the knp_menu_get_breadcrumbs_array function.
--
Edit:
With KnpMenu 2.2, you can now do:
<ol class="breadcrumb">
{% for breadcrumb_item in knp_menu_get_breadcrumbs_array(knp_menu_get_current_item('main')) %}
{% if not loop.last %}
<li>{{ breadcrumb_item.label }}</li>
{% else %}
<li class="active">{{ breadcrumb_item.label }}</li>
{% endif %}
{% endfor %}
</ol>
https://github.com/KnpLabs/KnpMenu/blob/master/doc/02-Twig-Integration.markdown#functions
The knp_menu_get_current_item('main') Twig function will retrieve the current menu item, for the main menu.