Api pagination in symfony 3 - symfony

I am new to symfony I need to create api and paginate the result
I used to do that easily in Laravel
Is there anything that would help me do the same with symfony 3.6?

symfony doesnt ship that out of the box.
there is pagerfanta and KnpPaginatorBundle. I read a bunch of subjective opinions that pagerfanta is better here https://github.com/symfony/demo/issues/305
depending on the size/amount of the APIs you need to create, you should maybe look into ApiPlatform. There is as well good videos about ApiPlatform on symfonycasts for 25$ for one month (am not affiliated)
composer require babdev/pagerfanta-bundle
composer require pagerfanta/doctrine-orm-adapter
composer require pagerfanta/twig
in controller
$pagerfanta = new Pagerfanta(new QueryAdapter($queryBuilder));
$pagerfanta->setMaxPerPage(5);
$pagerfanta->setCurrentPage($request->query->get('page', 1));
return $this->render('yourpage/index.html.twig', [
'pager' => $pagerfanta,
]);
in view
{% for yourEntity in pager %}
...
{% endfor %}
{{ pagerfanta(pager) }}
see as well
https://symfonycasts.com/screencast/doctrine-relations/pager-links#play

Related

Grouped view for datasets in sonata admin list view

I am wondering if there is a easy way to implement a group view for datasets grouped by its "root" element.
Lets say i have a "person" entity. Many persons can have a "root" person they belong to. So i would add a reference to the person entity itself.
Now i don't want to show every person in the flat list view. Instead i want to display only every root-person and with clicking this dataset a accordion opens with the subordinated person entities ... how is that possible?
It would also be fine without a accordion, it can be enough if the subordinated entities are indented a bit ...
Can somebody give me a clue which approach i should follow? I would be the if i can reuse the most of the sonata admin functionality, especially the templates ...
Thanks
Was solving something similar in past. Got that working in few steps:
Override first field in your Datagrid
With this code you say that you want use my_custom_template.html.twig to render this field (in your admin class).
protected function configureListFields(ListMapper $list)
{
$list->add('yourFirstField', null, ['template' => 'my_custom_template.html.twig'])
}
Enable filtering for yourFirstField
Prepare filtering for your parent field (in your admin class)
protected function configureDatagridFilters(DatagridMapper $filter)
{
$filter->add('parent');
}
Write custom template for first field
Then in your custom template you use to set up filter value on click on link.
{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
{% block field %}
{% if object.isRoot %}
{{ object.name }}
{% else %}
{{ object.name }}
{% endif %}
{% endblock %}
Later on you can override break breadcrumbs builder service (https://sonata-project.org/bundles/admin/master/doc/reference/breadcrumbs.html) and create nice path in breadcrumbs, so user can go navigate.

Service to get/set current entity

I have a Project entity, which has a controller defining many routes:
projects/1
projects/1/foo
projects/1/bar
I need a service to provide the current project. The use case is that I have dependencies in my base twig templates which need to know the current project. i.e. a dropdown project selector that is outside the context of the template the current controller is serving.
I've tried creating a service getting route info with $container->get('router.request_context');, however, that only provides a path. I don't want to have to parse the path string if I don't have to.
What is the most proper approach?
If I understood you correctly the solution for your problem is rendering/embedding controller. Of course it's simplest, yet somehow acceptable solution for rendering parts of html with some custom logic apart from current template.
You can read about rendering/embedding controllers.
Some snippets...
Define controller:action (obviously the logic in my example is pretty straight forward):
/**
* Generate select input field.
*
* #Route("/widget", name="project_widget")
* #Method("GET")
*/
public function widgetAction()
{
$repo = $this->getDoctrine()
->getManager()
->getRepository('AppBundle:Project');
// #NOTICE: Wee need master request info, because our widget
// is rendered and called as subrequest.
$masterRequest = $this->get('request_stack')->getMasterRequest();
// #NOTICE: You need second parameter as default value in case there is no id in masterRequest route.
$currentProjectId = $masterRequest->get('id', 0);
$currentProject = $repo->find($currentProjectId);
$projects = $repo->findAll();
return $this->render('project/widget.html.twig', [
'currentProject' => $currentProject,
'projects' => $projects,
]);
}
Then you need to create the template project/widget.html.twig for it:
<div class="widget_project_selection">
{% if projects is not empty %}
<select name="widget_project_selection" id="widget_project_selection">
<option value="">None</option>
{% for project in projects %}
<option value="{{ project.id }}"
{# #NOTICE: If this is current project, select it #}
{{- currentProject and project.id == currentProject.id
? 'selected="selected"'
: '' -}}
>
{{- project.title -}}
</option>
{% endfor %}
</select>
{% else %}
<span>{{ 'Sadly, no projects yet :('|trans }}</span>
{% endif %}
</div>
and at last (but not least) render it somewhere like in base.html.twig:
{{- render(controller('AppBundle:Project:widget')) -}}
I've created for you a Github repo as reference. It's a small Symfony app with similar setup. You can even run it if you like, don't forget about dependencies and database update thou. Entry point is /app_dev.php/project/
Take a look at widgetAction, widget template and example usage in base.html.twig.
EDIT: But that's not everything. You said you need a service. If for some reason rendering/embedding controller is not an option for you or you really whant to use a Service (as in Dependency Container) you can extend Twig and use the full power of services.
I've also implemented a Twig Filter as example to show you the real power of Twig Extensions in here and here (usage in templates).
Check out Twig Extension and Extending Twig for more info about Twig Extensions.
Also check out service.yml for service and extension definitions - if you are not using Symfony3.3+, there will be some additional work to do - defining service and extension directly.
In your controller, you can use type hinting to load the "current" entity via the route.
For example:
#myrouter.yml
current_project:
path: /projects/{project}
Separately, your controller...
//mycontroller.php
public function myControllerAction(Request $request, Project $project)
{
//$project is entity (assuming valid) loaded via the route above
return $this->render('mytemplate.twig', ['project' => $project]);
}

Cannot render text filtered with trans in twig template using Sandbox Security Policy

I am a little bit confused. First of all, look at my code, I guess.
public function renderTemplate($templateType, $data)
{
$layoutName = "$templateType.layout.html.twig";
$policy = new \Twig_Sandbox_SecurityPolicy(
['if', 'for', 'block', 'set', 'extends'],
['escape', 'format', 'dateformat', 'trans', 'raw', 'striptags'],
self::$allowedMethods,
self::$allowedProperties,
['gettext']
);
$sandboxExt = new \Twig_Extension_Sandbox($policy);
$intlExt = new \Twig_Extensions_Extension_Intl();
$i18nExt = new \Twig_Extensions_Extension_I18n();
$twig = new \Twig_Environment(new \Twig_Loader_Filesystem(__DIR__ . "/../Resources/views/Something/", "__main__"));
$sandboxExt->enableSandbox();
$twig->addExtension($sandboxExt);
$twig->addExtension($intlExt);
$twig->addExtension($i18nExt);
try {
$result = $twig->render($layoutName, $data);
} catch (\Exception $e) {
\Doctrine\Common\Util\Debug::dump($e);die();
}
return $result;
}
Here it is the template I want to render
{% extends 'layout.html.twig' %}
{% block title %}{{ entity.id }}{% endblock %}
{% block bodyTitle %}
{{ entity.id }} {{ 'translation_key.created_at'|trans({}, 'entities', locale) }} {{ entity.createdAt|dateformat(null, locale) }}
{% endblock %}
Here, as you can see, I want to render a template according to its type. The problem is: half of the template renders just fine, and then, when it tries to render the translated string, it throws an error.
Fatal error: Call to undefined function gettext() in /home/dev/vhosts/my-project/vendor/twig/twig/lib/Twig/Environment.php(403) : eval()'d code on line 69
I checked if this function existed right before trying to call render method, and it was really undefined. Basically, I have 2 questions here:
Question 1
How does it work in other parts of my project, but not in this specific handler? See "Important update" below.
Question 2
Can I solve my problem in another way? For example, without using Sandbox or using Sandbox with some kind of a flag "everythingAllowed=true"?
ATTENTION! IMPORTANT UPDATE
Previously, I misunderstood my own question. I thought the error was thrown while rendering the variable, but I re-checked the situation (When Alain Tiemblo asked me for twig template code here in comments) and now 100% sure it's thrown while trying to translate smth. Also, I have translations all over my project, and it works just fine, but in this specific situation it's not. I think it worth to mention, that I also tried to render the template without using Sandbox. I tried to render it directly from Twig Engine like this
return $this->templating->render($layoutName, $data);
//$this->templating is injected in the constructor via services.yml like this
//arguments:
// - "#templating"
The result - not properly translated text. When I dumped "locale" - I got one specific language, but the text was translated in another. But at least using this method - I didn't get any errors.. Can anybody clarify this for me? Because I really don't understand how the Intl/i18n extension works and why it doesn't want to work in Sandbox or not in Sanbox?
P.S. My guess, why it doesn't work directly from Twig Engine - probably I should inject not like "#templating" or it injects just right, but the Intl or I18n extensions are not enabled? How to enable. And I have no clues why it doesn't work with Sandbox

Time picker (no date) in a Sonata-admin form

I'm upgrading a Symfony project's Sonata Admin usage, from Sonata-Admin 2.2 to 2.3, as part of upgrading the overall project to Symfony 2.7.
We have a number of 'time' fields (that is, defined in Doctrine as "time", with no meaningful date component.)
In Sonata-Admin 2.2 the FormMapper definition was simple:
$formMapper
->tab('tab.general')
->add('start', null, array('label' => 'label.start')
->end()
and that gave the layout of "half-form-width" Hour and Minute selection boxes, side by side, in the form:
But Sonata-Admin 2.3 is showing them over two full lines:
which is not so nice or useable.
So, what should I be setting to get the same rendering?
I've tried using the 'sonata_type_datetime_picker' but that is really insistent on displaying a date. These fields do not have a date. There seems to be no equivalent for just picking the time.
Had the same issue and solved it by adding my own CSS file for overrides.
1) Add class to your form field
->add('start', 'time', array(
'attr' => array('class' => 'fixed-time')
))
2) Create a new CSS file in your bundle under Resources/public/css/override.css with overrides by fixed-time class
For example:
.fixed-time .select2-container {
width: auto !important;
}
3) Then somewhere in your bundle (Resources/views) create a new template called standard_layout.html.twig and override Sonata template + add newly created CSS file (remember to call parent() first, to load sonata code first).
{% extends 'SonataAdminBundle::standard_layout.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{asset('bundles/yourbundle/css/override.css')}}" type="text/css" media="all"/>
{% endblock %}
4) Then register your standard_layout twig template into Sonata admin (in your config.yml).
sonata_admin:
templates:
layout: YourBundle::standard_layout.html.twig
Do not forget to install new assets (assets:install) and clear cache (remove selected folder from app/cache), otherwise you will not see any change.

Using repository class methods inside twig

In a Symfony2.1 project, how to call custom entity functions inside template? To elaborate, think the following scenario; there are two entities with Many-To-Many relation: User and Category.
Doctrine2 have generated such methods:
$user->getCategories();
$category->getUsers();
Therefore, I can use these in twig such as:
{% for category in categories %}
<h2>{{ category.name }}</h2>
{% for user in category.users %}
{{ user.name }}
{% endfor %}
{% endfor %}
But how can I get users with custom functions? For example, I want to list users with some options and sorted by date like this:
{% for user in category.verifiedUsersSortedByDate %}
I wrote custom function for this inside UserRepository.php class and tried to add it into Category.php class to make it work. However I got the following error:
An exception has been thrown during the rendering of
a template ("Warning: Missing argument 1 for
Doctrine\ORM\EntityRepository::__construct(),
It's much cleaner to retrieve your verifiedUsersSortedByDate within the controller directly and then pass it to your template.
//...
$verifiedUsersSortedByDate = $this->getYourManager()->getVerifiedUsersSortedByDate();
return $this->render(
'Xxx:xxx.xxx.html.twig',
array('verifiedUsersSortedByDate' => $verifiedUsersSortedByDate)
);
You should be very carefull not to do extra work in your entities. As quoted in the doc, "An entity is a basic class that holds the data". Keep the work in your entities as basic as possible and apply all the "logic" within the entityManagers.
If you don't want get lost in your code, it's best to follow this kind of format, in order (from Entity to Template)
1 - Entity. (Holds the data)
2 - Entity Repositories. (Retrieve data from database, queries, etc...)
3 - Entity Managers (Perform crucial operations where you can use some functions from your repositories as well as other services.....All the logic is in here! So that's how we can judge if an application id good or not)
4 - Controller(takes request, return responses by most of the time rendering a template)
5 - Template (render only!)
You need to get the users inside your controller via repository
$em = $this->getDoctrine()->getEntityManager();
$verifiedusers = $em->getRepository('MYBundle:User')->getVerifiedUsers();
return array(
'verifiedusers' => $verifiedusers,
);
}

Resources