What is the better solution to get the Entity I want in a biderectional OneToMany relation? - symfony

I have one entity Article and an other entity Image with a bidrectional relation OneToMany and ManyToOne :
class Article
{
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Image", mappedBy="article")
*/
private $images;
}
class Image
{
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Article", inversedBy="images")
* #ORM\JoinColumn(nullable=true)
*/
private $article;
}
In my controller I use #paramconverter to get the article I want :
/**
* #Route("/blog/{slug}", name="article")
* #ParamConverter("article", class="AppBundle:Article")
*/
public function articleAction(Article $article)
{
return $this->render('default/article.html.twig', array(
'article' => $article,
));
}
Now my problem is that I want to identify the ONLY image with the attributes "main = true" in all the "article.images" I have.
What is the best solution?
In my wiew I can do somehting like this but it's not the best I think :
{% for image in article.images %}
{% if image.main %}
<img src="{{ asset( image.src ) }}" alt="{{ image.alt }}" title="{{ image.title }}">
{% endif %}
{% endfor %}
I'd like to use something like :
{{ article.mainImg }}
How can I achieve this please? And is this the best solution?

Doctrine provides a collection filter mechanism you could use to get the "main image":
public function articleAction(Article $article)
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq("main", true))
->setMaxResults(1);
$mainImg = $article->getImages()->matching($criteria)->first();
return $this->render('default/article.html.twig', array(
'article' => $article,
'mainImg' => $mainImg
));
}
More information on filtering doctrine collections: Filtering collections
I did not test the code myself, but it should convey the idea of how it can be done.

Related

Overriding SensioGeneratorBundle's twig template: trying to translate the delete button

I'm trying to override Resources/crud/actions/delete.php.twig from SensioGeneratorBundle.
I created this file located at app/Resources/SensioGeneratorBundle/skeleton/crud/actions/delete.php.twig:
{% extends "#SensioGenerator/Resources/crud/actions/delete.php.twig" %}
{% block form %}
/**
* Creates a form to delete a {{ entity }} entity by id.
*
* #param mixed $id The entity id
*
* #return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm($id)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('{{ route_name_prefix }}_delete', array('id' => $id)))
->setMethod('DELETE')
->add('submit', 'submit', array('label' => 'Supprimer'))
->getForm()
;
}
{% endblock form %}
Problem is that #SensioGenerator is not recognized:
There are no registered paths for namespace "SensioGenerator" in "crud/controller.php.twig" at line 58.
I tried to manually register the namespace in app/config/config.yml:
twig:
[...]
paths:
"%kernel.root_dir%/../vendor/sensio/generator-bundle/Sensio/Bundle/GeneratorBundle/": SensioGenerator
But still not working. Idea?
Why you didn't do it like in docs ? SensioGeneratorDocs
{% extends "skeleton/crud/actions/delete.php.twig" %}
{% block form %}
/**
* Creates a form to delete a {{ entity }} entity by id.
*
* #param mixed $id The entity id
*
* #return \Symfony\Component\Form\Form The form
*/
private function createDeleteForm($id)
{
return $this->createFormBuilder()
->setAction($this->generateUrl('{{ route_name_prefix }}_delete', array('id' => $id)))
->add('submit', 'submit', array('label' => 'Supprimer'))
->getForm()
;
}
{% endblock form %}
If you're using a project that's based-on or is similar-to the Symfony Standard Edition, then the SensioGeneratorBundle is only loaded for the dev environment.
This is configured in both the composer.json and the AppKernel.
So, make sure your execution is using the dev environment or alter these configurations so that the bundle is always loaded.

Display image stored in BLOB database in symfony

I load my image (blob data ) in my GETer Entity
When I just return ($this->foto) in my GETer I see :Resource id #284 on the screen
When I change my GETer like this : return stream_get_contents($this->foto);
I see these : ���JFIF��� ( ,,,,,,,, ( and more )
In my Controller a call the index.html.twig to show all my entities
/**
* Lists all Producten entities.
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('CustomCMSBundle:Producten')->findAll();
return $this->render('CustomCMSBundle:Producten:index.html.twig', array(
'entities' => $entities,
));
}
Now in my views ( index.html.twig ) I like to show the picture
{% for entity in entities %}
<tr>
<td>
<img src="{{ entity.foto}}" alt="" width="80" height="80" />
</td>
<td>
{{ entity.foto }}
</td>
<td>
<ul>
<li>
show
</li>
<li>
edit
</li>
</ul>
</td>
</tr>
{% endfor %}
But I don't see the picture ?
Can anyone help me?
You are using <img src="(raw image)"> instead of <img src="(image's url)">
A quick solution is to encode your image in base64 and embed it.
Controller
$images = array();
foreach ($entities as $key => $entity) {
$images[$key] = base64_encode(stream_get_contents($entity->getFoto()));
}
// ...
return $this->render('CustomCMSBundle:Producten:index.html.twig', array(
'entities' => $entities,
'images' => $images,
));
View
{% for key, entity in entities %}
{# ... #}
<img alt="Embedded Image" src="data:image/png;base64,{{ images[key] }}" />
{# ... #}
{% endfor %}
in your entity write your image getter like this:
public function getFoto()
{
return imagecreatefromstring($this->foto);
}
and use it instead of the object "foto" property.
php doc for the function: http://php.net/manual/de/function.imagecreatefromstring.php
A more direct way, without extra work in the controller:
In the Entity Class
/**
* #ORM\Column(name="photo", type="blob", nullable=true)
*/
private $photo;
private $rawPhoto;
public function displayPhoto()
{
if(null === $this->rawPhoto) {
$this->rawPhoto = "data:image/png;base64," . base64_encode(stream_get_contents($this->getPhoto()));
}
return $this->rawPhoto;
}
In the view
<img src="{{ entity.displayPhoto }}">
EDIT
Thanks to #b.enoit.be answer to my question here, I could improve this code so the image can be displayed more than once.
As it is said before you must use base64 method, but for a better performance and usability, the correct option is creating a custom twig filter (Twig extension) as described here .
<?php
namespace Your\Namespace;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class TwigExtensions extends AbstractExtension
{
public function getFilters()
{
return [
new TwigFilter('base64', [$this, 'twig_base64_filter']),
];
}
function twig_base64_filter($source)
{ if($source!=null) {
return base64_encode(stream_get_contents($source));
}
return '';
}
}
In your template:
<img src="data:image/png;base64,{{ entity.photo | base64 }}">

Passing multiple arguments through twig path

I have this twig code:
<div style="padding-left: 5em" class="comment">
<p>{{ comment.author.name }} - {{ comment.created|date('j. n. Y H:i') }}</p>
<p>{{ comment.text }}</p>
<p>Odpovědět na komentář</p>
{% for child in comment.children %}
{% include 'BlogApplicationBundle:Post:_comment.html.twig' with {'comment' : child}%}
{% endfor %}
</div>
and this is function that processes the output from link in twig code:
/**
* #Route("/post/{id}/newcommentresponse", name="comment_response_new")
* #Template("BlogApplicationBundle:Post:form.html.twig")
*/
public function commentResponceAction($id,$idc)
{
$comment = new Comment();
$form = $this->createForm(new CommentType(), $comment);
return array(
'form' => $form->createView()
);
}
when i try to run code i get this error :
Controller "Cvut\Fit\BiWt1\Blog\ApplicationBundle\Controller\CommentController::commentResponceAction()"
requires that you provide a value for the "$idc" argument (because
there is no default value or because there is a non optional argument
after this one).
So it seems that second argument passsed through link is ignored and i have no idea what am i doing wrong.
You are missing the $idc definition in your #Route annotation. It should look something like this:
#Route("/post/{id}/newcommentresponse/{idc}", name="comment_response_new")
or this:
#Route("/post/{id}/{idc}/newcommentresponse", name="comment_response_new")
You can also leave it out of the route and function declaration and grab it directly from the Controller:
/**
* #Route("/post/{id}/newcommentresponse", name="comment_response_new")
* #Template("BlogApplicationBundle:Post:form.html.twig")
*/
public function commentResponceAction($id)
{
$idc = $request->query->get('idc');

Sonata admin tabs in page

I use Sonata admin Generator.
I'd like to create multiple lists from a symfony class.
For example I have a list of invoices and I would like to create a tab with paid bills, an other tab with pending bills and a last tab with invoices disabled.
This status is in the class.
I saw this page(admin/admin) in the Sonata demo who use context but I wouldn't like install mediabundle if it's possible.
It is possible without the mediabundle.
I had the same use-case as you with 'project-statuses'. These statuses are in my database.
A few steps are necessary:
Override the CRUDController. Maybe this step isn't needed, but I couldn't figure out how to without. I want to display the different statuses as tabs, so I inject a collection in the list-template.
Make your own CRUDController:
namespace Your\OwnBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
class ProjectCrudController extends Controller
{
/**
* {#inheritdoc}
*
* #param Request $request
*/
public function render($view, array $parameters = array(), Response $response = null, Request $request = null)
{
$projectStatusRepo = $this->getDoctrine()->getRepository('EvinceObjectsBundle:ProjectStatus');
// here inject the params you'll need
// you can do it only when $parameters['action'] == 'list' if you want
$parameters['projectStatuses'] = $projectStatusRepo->findAll();
$parameters['activeProjectStatus'] = $request->get('status', 1);
return parent::render($view, $parameters, $response);
}
}
Inject your own CRUDController in the services.yml (or xml)
sonata.admin.project:
class: Your\OwnBundle\Admin\ProjectAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "project", label: "Project" }
arguments:
- ~
- Your\OwnBundle\Entity\Project
- YourOwnBundle:ProjectCrud
calls:
- [ setLabelTranslatorStrategy, ["#sonata.admin.label.strategy.underscore"]]
- [ setTemplate, [list, YourOwnBundle:ProjectAdmin:list.html.twig]]
Notice the setTemplate-call, so lets create your template
Create your own list-template
{% extends 'SonataAdminBundle:CRUD:base_list.html.twig' %}
{% block preview %}
<ul class="nav nav-pills">
<li><a><strong>{{ "label.select_projectstatus"|trans({}, 'SonataProjectBundle') }}</strong></a></li>
{% for projectStatus in projectStatuses %}
{% set active = false %}
{% if projectStatus.id == activeProjectStatus %}
{% set active = true %}
{% endif %}
<li class="{% if active %}active{% endif %}" >{{ projectStatus.status }}</li>
{% endfor %}
</ul>
{% endblock %}
Override the Admin::getFilterParameters function in your own admin class. Here you want to set the filter based on your requestparam:
/**
* {#inheritdoc}
*/
public function getFilterParameters()
{
$parameters = parent::getFilterParameters();
return array_merge(array(
'status' => array(
'type' => '',
'value' => $this->getRequest()->get('status', 1),
)
), $parameters);
}

echoing variable gets "Resource id"

Using symfony2 I load some entities and then try to iterate over them in a twig template.
However, instead of the variable content I am getting the following:
Resource id #23
My twig template looks like this:
<ol>
{% for post in posts %}
<li>
<div>
{{ post.content }}
</div>
</li>
{% endfor %}
</ol>
My controller code is:
$repository = $this->getDoctrine()
->getRepository('AppPostBundle:Post');
$reviews = $repository->findBy(
array('title' => 'my title'))
;
Maybe is too late for this answer (definitely it is LOL) but I recently had the same issue and the problem is that blob datatypes are treated as a buffer, so you have to read the info using buffer functions.
In your entity Post code you may add this method:
public function readContent(){
$content = '';
while(!feof($this->getContent())){
$content.= fread($this->getContent(), 1024);
}
rewind($this->getContent());
return $content;
}
Then in your views you may call the readcontent method instead of content:
<ol>
{% for post in posts %}
<li>
<div>
{{ post.readcontent }}
</div>
</li>
{% endfor %}
</ol>
Is better late than never :)
My entity name is : 'Test'
Blob type field is : 'Details'
in '/src/Vendorname/Bundlename/Entity/entityname.php'
**
* Get details
*
* #return string
*/
public function getDetails()
{
return $this->details;
}
/**
* Reading the blob type of content.
* #return data
*/
public function readDetails(){
$details = '';
while(!feof($this->getDetails())){
$details.= fread($this->getDetails(), 1024);
}
rewind($this->getDetails());
return $details;
}
and in '*.html.twig' file
{% for match in matches %}
{{match.readdetails}}
{%endfor%}

Resources