Using Symfony2 and PdfBundle to generate dynamically PDF files, I don't get to generate the files indeed.
Following documentation instructions, I have set up all the bundle thing:
autoload.php:
'Ps' => __DIR__.'/../vendor/bundles',
'PHPPdf' => __DIR__.'/../vendor/PHPPdf/lib',
'Imagine' => array(__DIR__.'/../vendor/PHPPdf/lib', __DIR__.'/../vendor/PHPPdf/lib/vendor/Imagine/lib'),
'Zend' => __DIR__.'/../vendor/PHPPdf/lib/vendor/Zend/library',
'ZendPdf' => __DIR__.'/../vendor/PHPPdf/lib/vendor/ZendPdf/library',
AppKernel.php:
...
new Ps\PdfBundle\PsPdfBundle(),
...
I guess all the setting up is correctly configured, as I am not getting any "library not found" nor anything on that way...
So, after all that, I am doing this in the controller:
...
use Ps\PdfBundle\Annotation\Pdf;
...
/**
* #Pdf()
* #Route ("/pdf", name="_pdf")
* #Template()
*/
public function generateInvoicePDFAction($name = 'Pedro')
{
return $this->render('AcmeStoreBundle:Shop:generateInvoice.pdf.twig', array(
'name' => $name,
));
}
And having this twig file:
<pdf>
<dynamic-page>
Hello {{ name }}!
</dynamic-page>
</pdf>
Well. Somehow, what I just get in my page is just the normal html generated as if it was a normal Response rendering.
The Pdf() annotation is supposed to give the "special" behavior of creating the PDF file instead of rendering normal HTML.
So, having the above code, when I request the route http://www.mysite.com/*...*/pdf, all what I get is the following HTML rendered:
<pdf>
<dynamic-page>
Hello Pedro!
</dynamic-page>
</pdf>
(so a blank HTML page with just the words Hello Pedro! on it.
Any clue? Am I doing anything wrong? Is it mandatory to have the alternative *.html.twig apart from the *.pdf.twig version? I don't think so... :(
Ok I got it.
For some reason, the example that comes in the bundle documentation didn't work for me. Nevertheless, there is this class in de bundle: http://github.com/psliwa/PdfBundle/blob/master/Controller/ExampleController.php, where I could find an example that did work for me. This is the code that I finally used:
/**
* #Route ("/generateInvoice", name="_generate_invoice")
*/
public function generateInvoiceAction($name = 'Pedro')
{
$facade = $this->get('ps_pdf.facade');
$response = new Response();
$this->render('AcmeStoreBundle:Shop:generateInvoiceAction.pdf.twig', array("name" => $name), $response);
$xml = $response->getContent();
$content = $facade->render($xml);
return new Response($content, 200, array('content-type' => 'application/pdf'));
}
Next challenge: store that PDF into disk.
It's because you've missed the "_format" option in the URL.
$this->render() shouldn't be used with the #Template annotation. The #Template will serve the correct template's format depending of the _format parameter.
...
use Ps\PdfBundle\Annotation\Pdf;
...
/**
* #Pdf()
* #Route ("/pdf.{_format}", name="_pdf")
* #Template()
*/
public function generateInvoicePDFAction($name = 'Pedro')
{
return array('name' => $name);
}
Should work fine.
Related
I'm looking for a PHP / Symfony code to redirect the user to another link according to his place of connection www.mondomain.fr/fr and www.mondomaine.fr/pt etc ... How can I do this ? I have included in my roads this already:
/**
* #Route("/{_locale}", name="homepage")
*
*/
public function indexAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$boutton = $em->getRepository('AppBundle:Boutton')->findAll();
$image = $em->getRepository('AppBundle:Images')->findAll();
// replace this example code with whatever you need
return $this->render('default/index.html.twig', array(
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..').DIRECTORY_SEPARATOR,
'boutton' => $boutton,
'images' => $image
));
}
Thank you
One of the solutions (not a PHP one) is to redirect based on browser language, you can find answer here.
You can also detect IP and redirect based on that, you can use this library to get the country/locale of the IP address and then redirect based on that.
I recently dove into the world of laravel (version 5.4). While initially confused, the concept of MVC makes a lot of sense in writing large applications. Applications that you want to be easily understood by outside developers.
Using laravel for this has greatly simplified coding in PHP and has made the language fun again. However, beyond dividing code into its respective models, views, and controllers, what happens if we need to divide controllers to prevent them from growing too large?
A solution that I have found to this is to define one controller each folder and then fill that controller with traits that further add functionalities to the controller. (All-caps = folder):
CONTROLLER
HOME
Controller.php
TRAITS
additionalFunctionality1.php
additionalFunctionality2.php
additionalFunctionality3.php
...
ADMIN
Controller.php
TRAITS
additionalFunctionality1.php
additionalFunctionality2.php
additionalFunctionality3.php
...
Within routes/web.php I woud initialize everything as so:
Route::namespace('Home')->group(function () {
Route::get('home', 'Controller.php#loadPage');
Route::post('test', 'Controller.php#fun1');
Route::post('test2', 'Controller.php#fun2');
Route::post('test3', 'Controller.php#fun3');
});
Route::namespace('Admin')->group(function () {
Route::get('Admin', 'Controller.php#loadPage');
Route::post('test', 'Controller.php#fun1');
Route::post('test2', 'Controller.php#fun2');
Route::post('test3', 'Controller.php#fun3');
});
With me being new to laravel, this seems like a simple and elegant way to organize my logic. It is however something I do not see while researching laravel controller organization.
The Question
Is there an issue, both in the short-run and in the long-run, of organizing my data like this? What is a better alternative?
Example Controller:
<?php
namespace App\Http\Controllers\Message;
use DB;
use Auth;
use Request;
use FileHelper;
use App\Http\Controllers\Message\Traits\MessageTypes;
use App\Http\Controllers\Controller;
class MessageController extends Controller
{
// Traits that are used within the message controller
use FileHelper, MessageTypes;
/**
* #var array $data Everything about the message is stored here
*/
protected $data = []; // everything about the message
/**
* #var booloean/array $sendableData Additional data that is registered through the send function
*/
protected $sendableData = false;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('access');
}
/**
* Enable sendableData by passing data to the variable
*
* #param array $data Addition data that needs to registrered
* #return MessageController
*/
protected function send ($data = []) {
// enable sendableData by passing data to the variable
$this->sendableData = $data;
return $this;
}
/**
* Enable sendableData by passing data to the variable
*
* #param string $type The type of message that we will serve to the view
* #return MessageController
*/
protected function serve ($type = "message") {
$this->ss();
$this->setData(array_merge($this->sendableData, $this->status[$type]));
$this->data->id = DB::table('messages')->insertGetId((array) $this->data);
}
/**
* Set the data of the message to be used to send or construct a message
* Note that this function turns "(array) $data" into "(object) $data"
*
* #param array $extend Override default settings
* #return MessageController
*/
protected function setData(array $extend = []) {
$defaults = [
"lobby" => Request::get('lobbyid'),
"type" => "text",
"subtype" => null,
"body" => null,
"time" => date("g:ia"),
"user" => Auth::User()->username,
"userid" => Auth::User()->id,
"day" => date("j"),
"month" => date("M"),
"timestamp" => time(),
"private" => Request::get('isPrivate') ? "1" : "0",
"name" => Request::get('displayname'),
"kicker" => null
];
$this->data = (object) array_merge($defaults, $extend);
// because a closure can not be saved in the database we will remove it after we need it
unset($this->data->message);
return $this;
}
/**
* Send out a response for PHP
*
* #return string
*/
public function build() {
if($this->data->type == "file") {
$filesize = #filesize("uploads/" . $this->data->lobby . "/" . $this->data->body);
$this->data->filesize = $this->human_filesize($filesize, 2);
}
// do not send unneccessary data
unset($this->data->body, $this->data->time, $this->data->kicker, $this->data->name, $this->data->timestamp);
return $this->data;
}
/**
* Send out a usable response for an AJAX request
*
* #return object
*/
public function json() {
return json_encode($this->build());
}
}
?>
Laravel architecture is simple enough for any size of the application.
Laravel provides several mechanisms for developers to tackle the fatty controllers in your Application.
Use Middlewares for authentications.
Use Requests for validations and manipulating data.
Use Policy for your aplication roles.
Use Repository for writing your database queries.
Use Transformers for your APIs to transform data.
It depends on your application. if it is too large and have different Modules or functionalities then you should use a modular approach.
A nice package is available for making independent modules here
Hope this helps.
I think you should do a little differently ! First you should use your traits at the same levels as the controllers since traits are not controllers, your tree should look more like :
Http
Controller
Controller.php
Home
YourControllers
Admin
Your admin controllers
Traits
Your Traits
Next your routes need to be more like that :
Route::group(['prefix' => 'home'], function()
{
Route::get('/', 'Home\YourController#index')->name('home.index');
}
Route::group(['prefix' => 'admin', 'middleware' => ['admin']], function()
{
Route::get('/', 'Admin\DashboardController#index')->name('dashboard.index');
}
You can use many kink or routes like :
Route::post('/action', 'yourControllers#store')->name('controller.store');
Route::patch('/action', 'yourControllers#update')->name('controller.update');
Route::resource('/action', 'yourController');
The Resource route creates automatically the most used your, like post, patch, edit, index.. You just need to write the action and the controller called with its action. You can check out your toutes with this command : php artisan route:list
Laravel also has many automated features, like the creation of a controller with this command : php artisan make:controller YourController.
For the routes the prefix creates portions of url, for example all the routes inside the route group with the prefix 'admin' will lool like : www.yourwebsite.com/admin/theroute, and can also be blocked for some users with a middleware.
To get familiar with laravel i suggest you follow the laravel 5.4 tutorial from scratch by Jeffrey Way on Laracasts, he's awesome at explaining and showing how laravel works. Here is a link : https://laracasts.com/series/laravel-from-scratch-2017
Hope it helps, ask me if you want to know anything else or have some precisions, i'll try to answer you !
My route cannot be found if I add a / at the end as you see below.
class QuizController extends Controller {
/**
* #Route("/quiz/{name}")
*/
public function showAction($name = '') {
$templating = $this->container->get('templating');
$html = $templating->render('quiz/show.html.twig', [
'name' => $name,
'title' => 'Hello World'
]);
return new Response($html);
}
}
The problem is:
myurl/quiz/whatever works
myurl/quiz works
myurl/quiz/ doesn't work
Whenever there is a slash but no value behind, I get the message
No route found for "GET /"
I'm new to Symfony. How can I fix this?
Using myurl/quiz indicates the {name} parameter is not used, but when you use myurl/quiz/ it needs the {name} parameter to be passed in.
Suggests you add defaults and a name for the route like so:
/**
* #Route("/quiz/{name}")
* defaults={"name" = 0},
* name="quiz")
*/
Then if you use myurl/quiz/ it will send myurl/quiz/0. This is an example
Add the ending slash in the route declaration, the route without / will be automatically redirected to the one with the /
class QuizController extends Controller
{
/**
* #Route("/quiz/{name}/")
*/
public function showAction($name = '')
{
$templating = $this->container->get('templating');
$html = $templating->render('quiz/show.html.twig', [
'name' => $name,
'title' => 'Hello World'
]);
return new Response($html);
}
}
Edit (via phone) After OP comments Below:
put 2 #Route declarations before controller action, one with slash one without
#Route("/quiz/")
#Route("/quiz/{name}")
Since OP wants the same route controller to handle both trailing slash and non-trailing slash, the solution is the allow Symfony to accept a slash as part of the variable (which is doesn't by default).
Docs Here : http://symfony.com/doc/current/routing/slash_in_parameter.html
So your route becomes
class QuizController extends Controller {
/**
* #Route("/quiz{name}", name="quiz_name", requirements={"name"=".+"})
*/
public function showAction($name = '') {
$templating = $this->container->get('templating');
$html = $templating->render('quiz/show.html.twig', [
'name' => $name,
'title' => 'Hello World'
]);
return new Response($html);
}
}
Please Note: While this answers the OP's question, it's a dangerour practice. For anyone reading this, it might suit the OP for his logic but note that it will also match any route
/quiz*
So while the OP wants this, it will also match
/quiz/name
/quiz/
/quiz
/quiz/name/edit
/quiz/name/delete
For OP, a work-around is to include edit, delete routes before this custom one as Symfony matches routes by first match wins basis.
I have to render a template of an action as a simple .txt file.
How can I do this? Is there a way other than using the Response object?
Using a Response object:
$content = $this->get('templating')->render(
'AppBundle:Company:accountBillingInvoice.txt.twig',
[
'invoice' => 'This is the invoice'
]
);
$response = new Response($content , 200);
$response->headers->set('Content-Type', 'text/plain');
I can't see what's wrong with using the Response object - it's pretty simple!
If you want to render text responses from many controller actions and you don't want to repeat yourself a lot, you can define some service class that builds up the response for you, like:
class TextResponseRenderer
{
/** #var EngineInterface */
private $engine;
// constructor...
/**
* #param string $template The name of the twig template to be rendered.
* #param array $parameters The view parameters for the template.
* #return Response The text response object with the content and headers set.
*/
public function renderResponse(string $template, array $parameters): Response
{
$content = $this->engine->render($template, $parameters);
$textResponse = new Response($content , 200);
$textResponse->headers->set('Content-Type', 'text/plain');
return $textResponse;
}
}
Other option may be writing a listener for the kernel.response that modifies the response headers, but this might be over-complicating things. See more info here.
I am using this Bundle to convert HTML to PDF files.
The actual conversion works, but I have a problem understanding the routing.
Here is my code:
/**
* #Route("/formulare/selbstauskunft/{keycode}", name="saPrint")
*/
public function saPrintAction(Request $request, $keycode)
{
$em = $this->getDoctrine()->getManager();
$sa = $em->getRepository('AppBundle:Selfinfo')->findOneBy(array(
'keycode' => $keycode,
));
if(count($sa) > 0){
$response = new Response(
$this->get('padam87_rasterize.rasterizer')->rasterize(
$this->renderView('default/formSAPrint.html.twig', array(
'selfinfo' => $sa,
))
),
200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="my.pdf"'
]
);
return $response;
}else{
return new Response("fail");
}
}
The bundle creates 2 files, rasterize-UNIQUEID.html and rasterize-UNIQUEID.pdf. The html file contains the correct output.
After the creation of the html file in /bundles/padam87rasterize/temp/ the second part of the script opens this file via an url call here.
Unfortunately the actual rendered page is a symfony error page, saying:
No route found for GET /bundles/padam87rasterize/temp/rasterize-UNIQUEID.html
What do I have to set in order to render the html file?
I think you actually have to create a separare route to render the html. As far as I can tell the rasterize function generates a pdf from the temporary html file (The key word being temporary).