How can I use paginate function from Eloquent in Slim 3 project using twig ?
This is in my controller :
$posts = Sound::paginate(2);
$this->container->view->render($response, 'admin/sounds/index.twig', [
'posts' => $posts
]);
This is the view :
{{ posts.links() }}
But it doesn't work as well as I expected :
Warning: call_user_func() expects parameter 1 to be a valid callback, no array or string given in **PATH_TO_PROJECT**\vendor\illuminate\pagination\AbstractPaginator.php on line 412
Fatal error: Call to a member function make() on null in **PATH_TO_PROJECT**\vendor\illuminate\pagination\LengthAwarePaginator.php on line 90
What I have to do to make it work ?
Can you try this:
{{ posts.links }}
I presume that links is a getter that returns links. If not, this won't work like you expect.
First, you need to include illuminate/pagination in your project (it's not included with illuminate/database):
composer require illuminate/pagination
Now paginator needs to know how to resolve current page. You should make sure this is done before using paginator, I personally put it where I'm setting up dependencies:
// $container is application's DIC container.
// Setup Paginator resolvers
Illuminate\Pagination\Paginator::currentPageResolver(function ($pageName = 'page') use ($container) {
$page = $container->request->getParam($pageName);
if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) {
return $page;
}
return 1;
});
Then in your twig template you can output pagination links. But please you should notice that paginator generates some HTML code which needs to be written to output as is so you'll need to tell twig to ignore escaping for links:
{{ posts.links | raw }}
Sorry for the late :
I didn't keep the project, I don't remember exactly how I did, but this : https://github.com/romanzipp/PHP-Slim-Pagination looks like what I did.
$app->get('/posts', function(Request $req, Response $res, $args = []) use ($cache) {
$page = ($req->getParam('page', 0) > 0) ? $req->getParam('page') : 1;
$limit = 5; // Number of posts on one page
$skip = ($page - 1) * $limit;
$count = Post::getCount([]); // Count of all available posts
return $this->view->render($res, 'post-list.twig', [
'pagination' => [
'needed' => $count > $limit,
'count' => $count,
'page' => $page,
'lastpage' => (ceil($count / $limit) == 0 ? 1 : ceil($count / $limit)),
'limit' => $limit,
],
// return list of Posts with Limit and Skip arguments
'posts' => Post::getList([
'limit' => $limit,
'skip' => $skip,
])
]);
});
In template :
{% if pagination.needed %}
<div class="ui pagination menu">
{% for i in 1..pagination.lastpage %}
<a class="{% if i == pagination.page %}active{% endif %} item" href="?page={{ i }}">{{ i }}</a>
{% endfor %}
</div>
{% endif %}
<div class="ui container">
{% for post in posts %}
<a class="item">
{# Post contents (title, url, ...) #}
</a>
{% endfor %}
</div>
Related
I'm sure this is very simple but it's proving just a bit beyond me at the moment.
I have made a plugin that I would like to use for displaying galleries which is working fine. However, trying to add the options of the galleries that I have created in my component is proving to be difficult.
When I add the component to a page, I have now got the option to choose all the galleries that I created but displaying the gallery based upon which one I selected is what I have been unsuccessful in doing.
Any help would be greatly appreciated!
I'm sure this is very simple but it's proving just a bit beyond me at the moment.
I have made a plugin that I would like to use for displaying galleries which is working fine. However, trying to add the options of the galleries that I have created in my component is proving to be difficult.
When I add the component to a page, I have now got the option to choose all the galleries that I created but displaying the gallery based upon which one I selected is what I have been unsuccessful in doing.
Any help would be greatly appreciated!
Components/Gallery.php:
use Cms\Classes\ComponentBase;
use MartinSmith\Gallerys\Models\Gallery as GalleryModel;
class gallerys extends ComponentBase
{
public $gallery;
public function componentDetails(){
return [
'name' => 'Frontend Gallery',
'description' => 'A gallery for you webpage'
];
}
public function defineProperties() {
$lists = $this->getLists();
return [
'galleryName' => [
'title' => 'Gallery',
'type' => 'dropdown',
'placeholder' => 'Select Gallery',
'options' => $lists
]
];
}
public function getLists() {
$agreements = GalleryModel::all()->pluck('name', 'id');
return $agreements->toArray();
}
public function getList() {
$agreement = GalleryModel::where('id', $this->property('galleryName'))->get();
return $agreement->first();
}
}
Components/gallery/default.htm:
{% set gallerys = __SELF__.gallery %}
{% for gallery in gallerys %}
<div class="container-fluid px-0">
<div class="gallery">
<div class="row">
{% for image in gallery.fullImage %}
<div class="col-md-4 px-0 home-galleryImg">
<a href="{{ image.path }}">
<div class="gallery-imgOverlay">
<p>{{ image.title }}</p>
<h5>{{ image.description }}</h5>
</div>
<img class="img-fluid" src="{{ image.thumb(650,auto) }}" alt="{{ thumbnail.description }}">
</a>
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
See screenshot
I solved this for myself by creating a function that returns the "name" and indexed by the 'id' using the laravel pluck method. pluck('name', 'id') The first argument selects the column to use as the value and the second argument selects the column to use as a key. Note* the toArray() method I don't think the options field can take collections.
public function getLists() {
$agreements = Agreements::all()->pluck('agrnum', 'id');
return $agreements->toArray();
}
//returns
array:3 [▼
2 => "DLE-2"
4 => "DLE-1"
5 => "DLE-3"
]
Now in my properties area I call the function $list = $this->getList();
public function defineProperties() {
$lists = $this->getLists();
return [
'getList' => [
'title' => 'List',
'type' => 'dropdown',
'placeholder' => 'Select List',
'options' => $lists
]
];
}
After that you can proceed to do a Lists::where('id', $this->property('getList')); or something of that sort in a function to show the selected list or in your case gallery.
My results:
The CMS Page Backend from component
public function defineProperties() {
$lists = $this->getLists();
return [
'getList' => [
'title' => 'List',
'type' => 'dropdown',
'placeholder' => 'Select List',
'options' => $lists
]
];
}
public function getLists() {
$agreements = Agreements::all()->pluck('agrnum', 'id');
return $agreements->toArray();
}
public function getList() {
$agreement = Agreements::where('id', $this->property('getList'))->get();
return $agreement->first();
}
The Webpage from default.htm in the component template folder
{{ d(__SELF__.getList) }}
Also if I do {{ d(__SELF__.property('getList')) }} it shows me the value is "5".
I need to know when an argument for a twig macro is defined vs when a null was passed as the value. If I use "is defined" then that accounts for both conditions, as twig seems to set all undefined arguments to null.
For example, here are two calls, the first calling the macro without the argument, and the second with a null value for the argument:
{% import 'macros.twig' as macros %}
{{ macros.method() }}
{{ macros.method(null) }}
And this would be the macro definition:
{% macro method(value) %}
{# condition to determine if value is undefined or null? #}
{% endmacro %}
To have a closer look into what Twig does with the definition of macro's I've added the compiled source. It as you say, twig sets all the variables default to null, so testing whether a variable was passed to a macro will be hard
twig
{% macro vars(foo, bar, foobar) %}
{% endmacro %}
{% import _self as forms %}
{{ forms.vars(null, false) }}
compiled source
// line 1
public function macro_vars($__foo__ = null, $__bar__ = null, $__foobar__ = null, ...$__varargs__)
{
$context = $this->env->mergeGlobals(array(
"foo" => $__foo__,
"bar" => $__bar__,
"foobar" => $__foobar__,
"varargs" => $__varargs__,
));
$blocks = array();
ob_start();
try {
return ('' === $tmp = ob_get_contents()) ? '' : new Twig_Markup($tmp, $this->env->getCharset());
} finally {
ob_end_clean();
}
}
I'm trying to implement some sort of macro autoloading.
The idea is to define a bunch of macros and use them on all the next template files.
Here's how I'm trying to do it:
<?php
define('ROOT_FRONT', '/path/to/files/');
define('LAYOUT_DIR', ROOT_FRONT . 'layout/');
include(ROOT_FRONT . 'lib/Twig/Autoloader.php');
Twig_Autoloader::register();
$twig_loader = new Twig_Loader_Filesystem(array(LAYOUT_DIR, ROOT_FRONT));
$twig = new Twig_Environment($twig_loader, array(
'charset' => 'ISO-8859-15',
'debug' => !!preg_match('#\.int$#', $_SERVER['SERVER_NAME']),
'cache' => $_SERVER['DOCUMENT_ROOT'] . '/cache/twig/'
));
$macro_code = '';
foreach(array_filter(
array_diff(
scandir(LAYOUT_DIR . 'macros/'),
array('..','.')
),
function($file)
{
return strtolower(pathinfo($file, PATHINFO_EXTENSION)) == 'twig'
&& is_file(LAYOUT_DIR . 'macros/' . $file);
}
) as $file)
{
$info = pathinfo($file);
$macro_code .= '{% import \'macros/' . $info['basename'] . '\' as macros_' . $info['filename'] . ' %}';
}
$twig
->createTemplate($macro_code)
->render(array());
$twig->display('index.twig', array());
If I have a file, say, macro/clearfix.twig, it will generate this template code, inside $macro_code:
{% import 'macros/clearfix' as macros_clearfix %}
The code inside macro/clearfix.twig is something like this:
{% macro clearfix(index, columns) %}
{% if index is divisible by(columns) %}
<div class="clearfix visible-md-block visible-lg-block"></div>
{% endif %}
{% if index is even %}
<div class="clearfix visible-sm-block"></div>
{% endif %}
{% endmacro %}
And then, inside the index.twig, I have this:
{{ macros_clearfix.clearfix(index=2, columns=6) }}
But nothing is displayed.
However, the following code works:
{% set index = 2 %}
{% set columns = 6 %}
{% if index is divisible by(columns) %}
<div class="clearfix visible-md-block visible-lg-block"></div>
{% endif %}
{% if index is even %}
<div class="clearfix visible-sm-block"></div>
{% endif %}
What could I possibly be doing wrong?
Am I misunderstanding something or applying this incorrectly?
TL;DR:
Twig requires you to load the macros inside the file where they will be used.
Just create custom functions to do what you want.
Twig (at least v1.30) doesn't implement macro inheritance.
This requires that you load every single macro you want to use on every single file.
The only way to do this is with functions, entirelly written in PHP.
This is what I've settled with:
index.php:
<?php
define('ROOT_FRONT', '/path/to/files/');
define('LAYOUT_DIR', ROOT_FRONT . 'layout/');
include(ROOT_FRONT . 'lib/Twig/Autoloader.php');
Twig_Autoloader::register();
$twig_loader = new Twig_Loader_Filesystem(array(LAYOUT_DIR, ROOT_FRONT));
$twig = new Twig_Environment($twig_loader, array(
'charset' => 'ISO-8859-15',
'debug' => !!preg_match('#\.int$#', $_SERVER['SERVER_NAME']),
'cache' => $_SERVER['DOCUMENT_ROOT'] . '/cache/twig/'
));
// ~ magic happens here ~
foreach(include(LAYOUT_DIR . 'fn.php') as $k => $fn)
{
$twig->addFunction(new Twig_SimpleFunction("fn_$k", $fn));
}
$twig->display('index.twig', array());
fn.php:
<?php
return array(
'clearfix' => function($index, $columns){
$html = '';
if(!($index % $columns))
{
$html .= '<div class="clearfix visible-md-block visible-lg-block"></div>';
}
if(!($index & 1))
{
$html .= '<div class="clearfix visible-sm-block"></div>';
}
return $html;
}
);
index.twig:
{{ fn_clearfix(index=2, columns=6) }}
This way, all your code is neatly indexed, new functions are created automatically and it is pretty easy to extend it to your liking.
Probably this is the worst idea, but it does the job.
Macros
As of Twig 2.0, macros imported in a file are not available in child templates anymore (via an include call for instance). You need to import macros explicitly in each file where you are using them.
From https://twig.symfony.com/doc/1.x/deprecated.html
I'd like to do something quite simple, but I can't figure out how to manage it. I have a form:
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
There are several text field in it. I'd like to "insert" some text (like <p>my Text</p>) between two text fields (let's say between name text field and description text field). Form are generated with form builder tool. I've tried something like:
$builder
->add('stuffName') // works well (text field 1)
->add('addedText', 'text', array('label' => 'my Text')) // Trouble here!
->add('stuffDescription'); // works well (text field 2)
But it generates a text field (and not a simple text). I don't care if the text is set in the form builder or directly in twig template... As long as it is between my two text fields. Any idea?
Thanks a lot!
Symfony forms contain only form fields. Any additional content you want has to be added by the template.
This means you'll have to output the form field-by-field. Your form, for example might look like this:
{{ form_start(form) }}
{{ form_row(form.stuffName) }}
<p>Your Text</p>
{{ form_row(form.stuffDescription) }}
{{ form_end(form) }}
For more more information on how you can customize form rendering, please see the forms chapter in the Symfony documentation.
The keyword in this question is generated.
Let's assume, that you build a form generator in Symfony. You have entities like Form, Fields and Fields Items (it's options for select box or buttons for radio button field).
So you have this entities and you create a service to create a form from the data. In the service you build the form ($this->buildedForm - generated form, $page->getFormData() - put the data to the constructed form):
$this->buildedForm = $this->formFactory->create(
'form',
$page->getFormData(),
['action' => '/page/formview/' . $task->getId()]
);
foreach($fields as $field) {
$fieldBuilderMethod = 'construct' . ucfirst($field->getType()) . 'Field';
if (method_exists($this, $fieldBuilderMethod)) {
$this->$fieldBuilderMethod($field);
}
}
return $this->buildedForm;
And you have methods for each type like (examples for Symfony 2):
private function constructInputField(FormField $field)
{
$this->buildedForm->add(
$field->getFieldName(),
'text',
[
'label' => $field->getName(),
]
);
}
private function constructTextareaField(FormField $field)
{
$this->buildedForm->add(
$field->getFieldName(),
'textarea',
[
'label' => $field->getName(),
]
);
}
You can now create your custom form type to paste a text in the generated form (it could be placed in the form folder of your bundle and retrieved with namespace "use"):
private function constructSimpletextField(FormField $field)
{
$this->buildedForm->add(
$field->getFieldName(),
new SimpletextType(),
[
'label' => $field->getName(),
'data' => $field->getPlaceholder(),
]
);
}
What in this custom field?
namespace Myproject\MyBundle\Form\TaskTypes;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SimpletextType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'disabled' => true,
'required' => false,
'mapped' => false,
]);
}
public function getParent()
{
return 'text';
}
public function getName()
{
return 'simpletext';
}
}
And the whole magic comes out in the template. For your custom form type you need to make a custom theme (see https://symfony.com/doc/2.7/form/form_customization.html#form-customization-form-themes). And there:
{% block simpletext_label %}{% endblock %}
{% block simpletext_widget %}
<p>{{ form.vars.data }}</p>
{% endblock %}
{% block simpletext_errors %}{% endblock %}
See, no label, no errors (it just a text) and only text in the field widget. Very handy for generated forms with dynamic template.
EDIT - Symfony 5
In Symfony 5, this solution became simplier. The form customization doesn't changes, and the php code became like this:
namespace Myproject\MyBundle\Form\TaskTypes;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SimpletextType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'disabled' => true,
'required' => false,
'mapped' => false,
]);
}
public function getBlockPrefix(): string
{
return 'simpletext';
}
}
It's used like this :
public function buildForm(FormBuilderInterface $builder, array $options): void {
/* … */
$builder->add('anykey', SimpleTextType::class, [
'data' => "Type your text here",
]);
/* … */
}
Here a sample code which would be self explain
{{ form_start(form, { 'attr': { 'class': 'form-horizontal form-bordered'} }) }}
<div class="form-group">
<div class="col-md-3 ">
{{ form_label(form.User, 'Label text', { 'attr': {'class': 'control-label'} }) }}
</div>
<p>You are free to add whatever you want here</p>
<div class="col-md-9">
{{ form_widget(form.User, { 'attr': {'class': 'form-control'} }) }}
</div>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
In any case, the symfony documentation is pretty clear and well-explain about this point.
I'm currently working with Symfony2 and I'm testing my project with PHPUnit.
I want to test an exception when a form is submitted with the wrong parameters or the URL isn't complete.
I went trough the documentation of Symfony2 and PHPUnit but didn't find any class/method to do so.
How can I change the value of a form's action? I want to use PHPUnit so the report created is up to date and I can see the coverage of my code.
EDIT:
To clarify my question, some new content.
How do I test the line starting with '>' in my controller? (throw $this->createNotFoundException('Unable to find ParkingZone entity.');)
When the user modifies the action link, in the controller the process will go trough the exception (or error message, if this action is chosen). How can I test this case?
Controller
/**
* Edits an existing ParkingZone entity.
*
* #Route("/{id}/update", name="parkingzone_update")
* #Method("post")
* #Template("RatpGarageL1Bundle:ParkingZone:edit.html.twig")
*/
public function updateAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('RatpGarageL1Bundle:ParkingZone')->find($id);
if (!$entity) {
> throw $this->createNotFoundException('Unable to find ParkingZone entity.');
}
$editForm = $this->createForm(new ParkingZoneType(), $entity);
$deleteForm = $this->createDeleteForm($id);
$request = $this->getRequest();
$editForm->bindRequest($request);
if ($editForm->isValid()) {
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('parkingzone_edit', array('id' => $id)));
}
return array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
);
}
View:
<form action="{{ path('parkingzone_update', { 'id': entity.id }) }}" method="post" {{ form_enctype(form) }}>
<div class="control-group">
{{ form_label(form.name, 'Name', { 'attr': {'class': 'control-label'} } ) }}
<div class="controls error">
{{ form_widget(form.name, { 'attr': {'class': ''} } ) }}
<span class="help-inline">{{ form_errors(form.name) }}</span>
</div>
</div>
<div class="control-group">
{{ form_label(form.orderSequence, 'Rang', { 'attr': {'class': 'control-label'} } ) }}
<div class="controls error">
{{ form_widget(form.orderSequence, { 'attr': {'class': ''} } ) }}
<span class="help-inline">{{ form_errors(form.orderSequence) }}</span>
</div>
</div>
<div class="control-group">
{{ form_label(form.image, 'Image', { 'attr': {'class': 'control-label'} } ) }}
<div class="controls error">
{{ form_widget(form.image, { 'attr': {'class': ''} } ) }}
<span class="help-inline">{{ form_errors(form.image) }}</span>
</div>
</div>
{{ form_rest(form) }}
<div class="form-actions">
<button type="submit" class="btn btn-primary">Enregistrer</button>
Annuler
</div>
</form>
Symfony itself does not have any objects through which it is possible to manipulate the form's action as it is set in the html (twig files). However, twig provides the capability to dynamically change the form's action in the twig file.
The basic approach is for the controller to pass a parameter into the twig file via the render call. Then the twig file can use this parameter to set the form action dynamically. If the controller uses a session variable to determine the value of this parameter then by setting the value of this session variable in the test program it is possible to set the form action specifically for the test.
For example in the controller:
public function indexAction()
{
$session = $this->get('session');
$formAction = $session->get('formAction');
if (empty($formAction)) $formAction = '/my/normal/route';
...
return $this->render('MyBundle:XXX:index.html.twig', array(
'form' => $form->createView(), 'formAction' => $formAction)
);
}
And then, in the twig file:
<form id="myForm" name="myForm" action="{{ formAction }}" method="post">
...
</form>
And then, in the test program:
$client = static::createClient();
$session = $client->getContainer()->get('session');
$session->set('formAction', '/test/route');
$session->save();
// do the test
This isn't the only way, there are various possibilities. For example, the session variable could be $testMode and if this variable is set the form passes $testMode = true into the render call. Then the twig file could set the form action to one of two values depending on the value of the testMode variable.
Symfony2 makes a distinction between unit testing of individual classes and functional testing of application behaviour. Unit testing is carried out by directly instantiating a class and calling methods on it. Functional testing is carried out by simulating requests and testing responses. See symfony testing for further detail.
Form submission can only be tested functionally as it is handled by a Symfony controller which always operates in the context of a container. Symfony functional tests must extend the WebTestCase class. This class provides access to a client which is used to request URLs, click links, select buttons and submit forms. These actions return a crawler instance representing the HTML response which is used to verify that the response contains the expected content.
It is only appropriate to test that exceptions are thrown when carrying out unit tests as functional tests cover interaction with the user. The user should never be aware that an exception has been thrown. Therefore the worst case scenario is that the exception is caught by Symfony and in production the user is presented with the catch-all response "Oops, an error has occurred" (or similar customised message). However, this should really only occur when the application is broken and not because the user has used the application incorrectly. Therefore it is not something that would typically be tested for in a functional test.
Regarding the first scenario mentioned in the question - submitting a form with the wrong parameters. In this case the user should be presented with an error message(s) telling them what was wrong with their input. Ideally the controller should not be throwing an exception but symfony validation should be used to automatically generate error messages next to each field as appropriate. Regardless of how the errors are displayed this can be tested by checking that the response to submitting the form contains the expected error(s). For example:
class MyControllerTest extends WebTestCase
{
public function testCreateUserValidation()
{
$client = static::createClient();
$crawler = $client->request('GET', '/new-user');
$form = $crawler->selectButton('submit')->form();
$crawler = $client->submit($form, array('name' => '', 'email' => 'xxx'));
$this->assertTrue($crawler->filter('html:contains("Name must not be blank")')->count() > 0,
"Page contains 'Name must not be blank'");
$this->assertTrue($crawler->filter('html:contains("Invalid email address")')->count() > 0,
"Page contains 'Invalid email address'");
}
}
Regarding the second scenario - where the URL isn't complete. With Symfony any URL which does not match a defined route will result in a NotFoundHttpException. In development this will result in a message such as 'No route found for "GET /xxx"'. In production it will result in the catch-all 'Oops, an error has occurred'. It would be possible to test in development that the response contains 'No route found'. However, in practice it doesn't really make sense to test this as it's handled by the Symfony framework and is therefore a given.
EDIT:
Regarding the scenario where the URL contains invalid data which identifies an object. This could be tested (in development) like this in the unit test program:
$client = static::createClient();
$page = $client->request('GET', '/update/XXX');
$exceptionThrown = ($page->filter('html:contains("NotFoundException")')->count() > 0) && ($page->filter('html:contains("Unable to find ParkingZone entity")')->count() > 0);
$this->assertTrue($exceptionThrown, "Exception thrown 'Unable to find ParkingZone entity'");
If you just want to test that an exception has been thrown rather than a specific type / message you can just filter the html for 'Exception'. Remember that in production the user will only see "Oops, an error has occurred", the word 'Exception' will not be present.
Thanks to #redbirdo with his last answer, I found a solution without messing out with the controllers.
I only changed few lines in the templates.
ControllerTest
public function testUpdate()
{
$client = static::createClient();
$session = $client->getContainer()->get('session');
$session->set('testActionForm', 'abc');
$session->save(); // This line is important or you template won't see the variable
// ... tests
}
View
{% if app.session.has('testActionForm') %}
{% set actionForm = path('parkingzone_update', { 'id': app.session.get('testActionForm') }) %}
{% else %}
{% set actionForm = path('parkingzone_update', { 'id': entity.id }) %}
{% endif %}
<form action="{{ actionForm }}" {{ form_enctype(form) }} method="POST" class="form-horizontal">
// ... rest of the form