In my bundle I have a Resources/public/images/image.jpg file.
This image is accessible via http://localhost/bundles/mybundle/images/image.jpg
How can I get this /bundles/mybundle prefix from a controller?
I want to be able to generate the path to the public file without hardcoding /bundles/mybundle prefix.
I'd create a service which would do this
Create the service
The main responsibility of this class, is to get the default web path of any bundle for any resource.
As defined in assets:install command, relative paths for each bundle are expected to be /bundles/foobar/ for a given FooBarBundle
Acme\FooBundle\WebPathResolver
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
class WebPathResolver
{
/**
* Gets the prefix of the asset with the given bundle
*
* #param BundleInterface $bundle Bundle to fetch in
*
* #throws \InvalidArgumentException
* #return string Prefix
*/
public function getPrefix(BundleInterface $bundle)
{
if (!is_dir($bundle->getPath().'/Resources/public')) {
throw new \InvalidArgumentException(sprintf(
'Bundle %s does not have Resources/public folder',
$bundle->getName()
));
}
return sprintf(
'/bundles/%s',
preg_replace('/bundle$/', '', strtolower($bundle->getName()))
);
}
/**
* Get path
*
* #param BundleInterface $bundle Bundle to fetch in
* #param string $type Which folder to fetch in (image, css..)
* #param string $resource Resource (image1.png)
*
* #return string Resolved path
*/
public function getPath(BundleInterface $bundle, $type, $resource)
{
$prefix = $this->getPrefix($bundle);
return sprintf('%s/%s/%s', $prefix, $type, $resource);
}
}
Declare it in your service.yml
Nothing special, but an usual service
#AcmeFooBundle/Resources/config/services.yml
services:
acme_foo.webpath_resolver:
class: Acme\FooBundle\WebPathResolver
Usage
You can then use it in your controller like this
Acme\FooBundle\Controller\BarController::bazAction
$bundle = $this->get('http_kernel')->getBundle('AcmeFooBundle');
$path = $this->get('acme.webpath_resolver')->getPath($bundle, 'image', 'foo.png');
echo $path; // Outputs /bundles/acmefoo/image/foo.png
You use assets in templates, like this:
{% image '#AcmeFooBundle/Resources/public/images/example.jpg' %}
<img src="{{ asset_url }}" alt="Example" />
{% endimage %}
or directly in src:
<img src="{{ asset('#AcmeFooBundle/Resources/public/images/example.jpg') }}" alt="Example" />
In css files you need to use relative paths.
From controller you can get full path in the same way by:
$this->container->get('templating.helper.assets')->getUrl('#AcmeFooBundle/Resources/public/images/example.jpg');
You could use something like this, but is assumes the path is the lowercased bundle name.
$controller = $request->attributes->get('_controller');
$regexp = '/(.*)\\\Bundle\\\(.*)\\\Controller\\\(.*)Controller::(.*)Action/';
preg_match($regexp, $controller, $matches);
$imagePath = '/bundles/'. strtolower($matches[2]). '/images/image.jpg';
Related
I have Symfony 6.2 application in which logged user may upload images to server. Because images are non public, I upload this images to /var/images directory.
In config/services.yaml I have
parameters:
card_directory: '%kernel.project_dir%/var/images'
and for upload service I use
App\Service\ImageUploader:
arguments:
$targetDirectory: '%card_directory%'
upload images is function.
But now I need serve this images to twig template for generating 4 column thumnail gallery. How I should generate path to this images in twig templates?
Finally I use this code for server private images with Controller for Card entity, which has image
<?php
namespace App\Controller;
use App\Entity\Card;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\Routing\Annotation\Route;
class ServePrivateImageController extends AbstractController
{
/**
* Returns a private image for card file for display.
*
* #Route("/serve-private-image/{id}", name="serve_private_image", methods="GET")
* #param Card $card
* #return BinaryFileResponse
*/
public function privateImageServe(Card $card): BinaryFileResponse
{
return $this->fileServe($card->getImage());
}
/**
* Returns a private file for display.
*
* #param string $path
* #return BinaryFileResponse
*/
private function fileServe(string $path): BinaryFileResponse
{
$absolutePath = $this->getParameter('card_directory') . '/' . $path;
return new BinaryFileResponse($absolutePath);
}
}
To answer your question, you would simply refer to your route within twig.
<div class="row">
{% for file in files %}
<div class="col-md-3">
<img src="{{ path('serve_private_image', {id: file.id}) }}" />
</div>
{% endfor %}
But as far as serving the resized images, LiipImageBundle does what you're looking for, and much more:
https://github.com/liip/LiipImagineBundle
Although it takes a few minutes to set up (because of the integration with flysystem), once it's working you can even keep your (secure) images on s3 and it handles all the downloading and local caching, rotating the resized issues, etc.
Let's say I use Webpack, and builded a Css file properly at "build/theme/mail.css".
I wants to include the CONTENT of this File into my twig.
{% include "https://mysite.io/build/theme/mail.css" %} doesn't work saying it can't find the file ( but it exist).
I don't want that because im using an inliner, and absolutly need the #CONTENT in my twig.
Tried the File get Content, didn't works. Found a solution:
twig:
paths:
'%kernel.project_dir%/public': public
Add a twig path to public directory
and then use :
{% apply inline_css(source(theme_asset('#public/build/theme/email.css'))) %}
and add a webpackconfig to build the scss file into public/build
Thanks you all.
You could create custom twig extension and use file_get_contents.
Extension:
<?php
namespace App\Twig;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class GetPublicFileContentExtension extends AbstractExtension
{
/** #var string */
private $publicPath;
public function __construct(ParameterBagInterface $parameterBag)
{
$this->publicPath = $parameterBag->get('kernel.project_dir') . '/public/';
}
public function getFunctions(): array
{
return [
new TwigFunction('get_public_file_content', [$this, 'getPublicFileContent']),
];
}
public function getPublicFileContent(string $filepath)
{
return file_get_contents($this->publicPath . $filepath);
}
}
Twig:
{{ get_public_file_content('build/theme/mail.css') }}
I am try to use OneupUploaderBundle for uploading files. I read the documentation of this bundle many times but I did not manage to find any simple example of an entity for the file to be uploaded. My expectation is a class definition similar to the VichUploaderBundle:
<?php
namespace Minn\AdsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity
* #Vich\Uploadable
*/
class MotorsAdsFile {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #Assert\File(
* maxSize="5M",
* mimeTypes={"image/png", "image/jpeg"}
* )
* #Vich\UploadableField(mapping="motors_files", fileNameProperty="filename")
* note: This is not a mapped field of entity metadata, just a simple property.
* #var File $file
*/
protected $file;
/**
* #ORM\Column(type="string", length=255, name="filename")
* #var string $filename
*/
protected $filename;
/**
* #ORM\Column(type="string", length=255, name="name")
* #var string $name
*/
protected $name;
// ...
}
Hope my question is clear...
I am really interested by this bundle since it supports jQuery.
Thanks,
There is no such thing as a predefined entity or ORM-handling. This has several reasons. This bundle can not foresee the projects need in terms of upload logic. If someone wants to store files to the database or not is entirely their own choice and should not be forced by a third party bundle. The thing the OneupUploaderBundle provides is a backend for the most common frontend uploaders there are.
Disclaimer: I've shorty copied and extended an answer that was already present in the GitHub issue tracker of this bundle. There you'll find quite a lot of insights on how and why this bundle is what it is now.
Given that you have already installed a working Symfony2 project, follow the installation instructions of the this bundle. I think step 1 and 2 should not be a problem, so lets hook directly to step 3, the configuration.
You said, you tried to integrate the jQuery File Uploader, so lets create a mapping for it.
Open the file app/config/config.yml and add the following lines to the end of it.
oneup_uploader:
mappings:
gallery:
frontend: blueimp
And of course, don't forget to add the following lines to app/config/routing.yml.
oneup_uploader:
resource: .
type: uploader
So much for the configuration. For the sake of simplicity we will alter the AcmeDemoBundle.
Open the file src/Acme/DemoBundle/Resources/views/Welcome/index.html.twig and delete everything between {% block content %} and {% endblock %}. We don't need this anymore.
Now insert the following:
<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="https://rawgithub.com/blueimp/jQuery-File-Upload/master/js/vendor/jquery.ui.widget.js"></script>
<script type="text/javascript" src="https://rawgithub.com/blueimp/jQuery-File-Upload/master/js/jquery.iframe-transport.js"></script>
<script type="text/javascript" src="https://rawgithub.com/blueimp/jQuery-File-Upload/master/js/jquery.fileupload.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#fileupload').fileupload({});
});
</script>
<input id="fileupload" type="file" name="files[]" data-url="{{ oneup_uploader_endpoint('gallery') }}" multiple />
Point your browser to the root directory of this application (app_dev.php). You'll see an input field just like expected and you can now upload some images (for example). The files will be stored in web/uploads/gallery every single one with a unique filename. Note that we used some CDNs to serve the JavaScript files needed for this.
At this point, you already have a working upload form. But besides uploading files to the uploads directory, it does nothing.
This is where the Next step section in the documentation comes in handy.
As I understand your question, you want to create an entity which stores a file path of a related file in it.
To do so, create your Entity class including all required fields.
<?php
namespace Minn\AdsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class MotorsAdsFile {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #ORM\Column(type="string", length=255, name="filename")
* #var string $filename
*/
protected $filename;
// ...
}
Then create an EventListener as described in the documentation of this bundle.
<?php
namespace Acme\HelloBundle\EventListener;
use Oneup\UploaderBundle\Event\PostPersistEvent;
use Minn\AdsBundle\Entity\MotorsAdsFile;
class UploadListener
{
protected $manager;
public function __construct(EntityManager $manager)
{
$this->manager = $manager;
}
public function onUpload(PostPersistEvent $event)
{
$file = $event->getFile();
$object = new MotorsAdsFile();
$object->setFilename($file->getPathName());
$this->manager->persist($object);
$this->manager->flush();
}
}
And of course register your event listener.
<services>
<service id="acme_hello.upload_listener" class="Acme\HelloBundle\EventListener\UploadListener">
<argument type="service" id="doctrine.orm.entity_manager" />
<tag name="kernel.event_listener" event="oneup_uploader.post_persist" method="onUpload" />
</service>
</services>
At this point the EventListener will be called as soon as a new file was uploaded through the configured mapping.
It takes this file, creates a new object of MotorsAdsFile and stores the file path to the filename property, persists and flushes it to the underlying database.
As I can't predict your actual logic, this is the most basic example I can think of. You're of course free to do whatever is needed in the event listener. (In case you need to store this object to another entity or the like.)
You'll find a bunch of other topics in the Next steps section of the documentation. For example how you'd change the naming strategy of the uploaded files or how to enable chunked uploads in case you need to upload big files.
Ok so i have a controller with an action and 2 routes associated with it:
/**
* #Route("/index/preview/", name="mybundle.preview_index")
* #Route("/", name="mybundle.index")
* #Template
*/
public function indexAction(Request $request)
{
$preview = ($request->get('_route') === 'mybundle.preview_index');
$host = $request->getHttpHost(); //domain.com
if(!$preivew){
$host = 'domain2.com';
}
return array(
'preivew' => $preview,
'host' => $host,
'basePath' => $preview?'mybundle.preview_':'mybundle.',
);
}
Then I want to generate a route inside the twig template depending on the host:
{{ path(basePath~'index') }}
//Then somehow pass the host to this so that i get the intended domain
If I was accessing this route using the preview route then i would get:
domain.com/index/preview/
But if i wasnt it would give me:
domain2.com/
What I Have Tried
Setting the router context from within the controller, but that doesnt change routes generated in twig
I figured it out. Instead of using path() i have to use url() and set the host in the context of the router:
if(!$preview){
$context = $this->get('router')->getContext();
$context->setHost($host);
}
Then twig:
{{ url(basePath~'index') }}
I'm dynamically loading different form classes in my Controller and displaying them in my template. This works fine, except that the Symfony2 docs show adding the route for the form to POST to in the template by hand.
<form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>
I need to set that form action in the FormBuilder class-- the POST routes (e.g. 'task_new') are different depending on the form class I'm using. Is there a way to set the form action url in the FormBuilder class? How can we get {{ form_widget(form) }} to render the complete form, and not just the rows? Thanks!
It is possible out of the box -- http://symfony.com/doc/current/book/forms.html#changing-the-action-and-http-method
$form = $this->createFormBuilder($task)
->setAction($this->generateUrl('target_route'))
->setMethod('GET')
->add('task', 'text')
->add('dueDate', 'date')
->add('save', 'submit')
->getForm();
I had the same problem. I was using a simple FormType class and wanted to set the action url in buildForm function. I tried different things, but couldn't do it that way.
Eventually I used a Form option called 'action'. I don't think it's documented in the Symfony Reference, I have found it by accident while reading some error report :).
You can set the option when creating the form within your controller like this:
$form = $this->createForm(new FormType(), $obj, array( 'action' => 'whatever you want'));
It's not as pretty as having it encapsulated in the form class, but it works..
I hope this helps.
It's bad practice to change submit route in form type. It not form type responsibility. If you added form from not handle form route, you can just change action url in template:
{{ form_start(yourForm,{action:path('yourSubmitRoute')}) }}
I solved this problem by injecting the router into my form type. In my application I have created a zip code search form called ZipCodeSearchType:
Form Class
use Symfony\Component\Form\AbstractType;
/*
* I'm using version 2.6. At this time 2.7 has introduced a
* new method for the Option Resolver. Refer to the documentation
* if you are using a newer version of symfony.
*/
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Routing\Router;
/**
* Class ZipCodeSearchType is the form type used to search for plans. This form type
* is injected with the container service
*
* #package TA\PublicBundle\Form
*/
class ZipCodeSearchType extends AbstractType
{
/**
* #var Router
*/
private $router;
public function __construct(Router $router)
{
//Above I have a variable just waiting to be populated with the router service...
$this->router = $router;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('zipCode', 'text', [
'required' => true,
])
/*
* Here is where leverage the router's url generator
*/
//This form should always submit to the ****** page via GET
->setAction($this->router->generate('route_name'))
->setMethod("GET")
;
}
...
}
The next step is to configure your form as a service, and let symfony know that you need the router service injected into your class:
Define Form as Service
/*
* My service is defined in app/config/services.yml and you can also add this configuration
* to your /src/BundleDir/config/services.yml
*/
services:
############
#Form Types
############
vendor_namespace.zip_search_form:
class: VENDOR\BundleNameBundle\Form\ZipCodeSearchType
arguments: [#router]
tags:
- { name: form.type, alias: zip_code_search }
Use It In Your Controller
/**
* #param Request $request
* #return Form
*/
private function searchByZipAction(Request $request)
{
...
$zipForm = $this
->createForm('zip_code_search', $dataModel)
;
...
}
I don't think it's possible out-of-box today (Mar 18 '12). You could, however, do something like this:
in your controller:
....
....
$post_route = null;
if ( $something ){
$post_route = "some_post_route";
}else if ( $something_else ){
$post_route = "some_other_post_route"
}else{
$post_route = "my_default_route";
}
....
....
return array(
'post_route' => $post_route
);
... and in you template:
<form action="{ path(post_route) }" method="post" {{ form_enctype(form) }}>
Similar approach would be to generate URL (not just route name) within your controller and pass it to template, in which case you don't need path function there.