I'm working on a way for users to be able to generate PDF copies of invoices and other tabular data. To do this, I've wrapped dompdf into a library that I can use with CI and created a method that will generate a PDF based on the return value of CI's output->get_output(). The wrapper is similar to this one on Github.
The problem is, I can't figure out a way to get the view (and HTML/CSS needed for the PDF) into CI's output class other than load->view(), which is going to write to the browser.
My only other choice would be to use curl to request the page, but that seems so silly to do since I can get it right from the output buffer. I just don't want the HTML sent to the browser, since I set headers telling the browser to expect a PDF.
To be clear, this is what I want to accomplish (in the order that I want to accomplish it):
Do everything I'd normally do to prepare the view for display
Load the view into the CI output class, but not display it
Pass the return value of output->get_output() to my dompdf library
Set the appropriate headers
Execute my dompdf method that will send the PDF to the browser
I don't see any way of doing step 2 based on the output class documentation.
Is it possible to get a view into the output class without displaying it? If so, how? I'm using CI 2.0.3.
Edit
The very helpful Anthony Sterling pointed out that I can just get what I want from the loader class by setting the third argument telling it to return a string rather than render the view to TRUE. E.g.:
$lotsaHtml = $this->load->view('fooview', $somearray, TRUE);
And that would be better in my particular instance since I don't need to load partials. However, this is still a valid and (I think) interesting question, it would also be handy to know if I could get the same from the OB, perhaps if I did have a bunch of partials. Those could be concatenated, but yuck.
It seems like I should be able to get the output class to not render anything (else, why does get_output() exist?) so I can do something else with everything it knows about. I just can't find a way to make that happen.
Edit 2
Some pseudo (but not far from reality) code illustrating what I hope to do, by showing what I did and then explaining what I actually wanted to do.
Let's say I have a public method genpdf($id) in a controller named invoice using a model named inv:
public function genpdf($invoiceId) {
$this->load->library('dompdflib');
$this->pagedata['invoice_data'] = $this->inv->getInvoice($invoiceId);
$html = $this->load->view('pdfgen', $this->pagedata, TRUE);
$this->dompdflib->sendPdf($html);
}
That is almost identical to code that I have that works right now. There, I ask the loader to parse and give me the results of the pdfgen view as a string, which I pass to the function in my dompdf wrapper that sets headers and sends the PDF to the browser.
It just seemed like this would be easy to do by just getting the output buffer itself (after setting headers correctly / etc).
Or do I just have to call the output class append_output() in succession with every partial I load?
Multiple methods loading a plethora of models need to work together to generate these (they're going in as an afterthought), so I was hoping to just collect it all and retrieve it directly from the output class. It could be that I just have to talk gradually to output->append_output() to make that happen.
...so - do I understand correctly - you want to get the whole final output (not just the view) as a string AND not display it to the user? Why dont you just overload the controllers _output() function?
class Your_controller extends CI_Controller
{
function stuff()
{
// do whatever - prep $data etc
$this->load->view('your_view', $data);
}
function _output($output)
{
// send $output to your library - get results blah blah
$result_pdf_file = $this->your_pdf_library_generator($output);
// Show something else to the user
echo "hi - I'm not what you expected - but here is your PDF";
echo $result_pdf_file; // or something like that
}
}
This means you can send ANYTHING you like to the output class - but nothing is displayed except what you want.
There are ways to improve this idea (i.e. hooks, variables to turn output on/off etc) - but the simplest would be to have this controller specifically for your pdf_generation command.
I don't see any way of doing step 2 based on the output class documentation. Is it possible to get a view into the output class without displaying it? If so, how? I'm using CI 2.0.3.
The controller _output() documentation is actually in the CI controller documentation, which is why it eluded you.
Related
I figure I'd better ask than make a fool of myself some time when I get my code reviewed.
I am making a website, trying to follow MVC design architecture, although it might not even be MVC but I figured it works nice.
Currently, I am doing something like this;
I have a render() function that takes a string $template and array of arguments,
render($template, $vars=[]){
extract($vars);
ob_start();
include($template.'.php');
ob_flush();
}
and outputs the view. In a controller, I then do this,
Class WhicheverController
public static function defaultAction(){
$article=Article::getOneByID(array('id'=>$_GET['id'], 'user'=>User::currentUser())); //returns database result as object
render('_header', array('title'=>$article->title));
render('header', array('user'=>User::currentUser()));
render('sidebar', array('user'=>User::currentUser()));
$content=Comment::getByArticleID(array('article'=>$article->id)); //returns object
//of root comments, highest level in comment-replies tree
$comments=function() use ($content){
foreach($content as $comment){
$threads[]=function() use ($comment){
render('comment', array('comment'=>$comment));
}
}
render('pagination', array('content'=>$threads)); //pagination paginates based on number of
//items in content, and echoes it or runs it if it is_callable
}
render('article', array('article'=>$article, 'comments'=>$comments));
}
And finally in article.php;
//some html with echoing $article->variablethis or variablethat
<div class="comments">
<?= $comments(); ?>
</div>
Now, the way I figured it out, ob_flush() sends to client prematurely, so I get headers with styles and header and sidebar printed as I am getting the neccessary variables. That is good (if that's how it works).
Then, I want to do the same with the main content - article and comments. First I get the comments and article data, and pass it to article.php. But I have to do some pagination logic first before sending it away, and that would delay rendering of article.php. So I create an anonymous function which does that, and pass that as a variable function $comments(); which does it's logic when it is called.
This is only one example. In one controller I first get top rated articles of today, yesterday and current week, and create anonymous function for each one, before passing it to frontpage.php. Frontpage.php does this, essentialy;
//some html markup
<?= $articles24h(); ?>
//more html
<?= $articles48h(); ?>
//and so on
Now, when I thought of this I thought it was brilliant but as I started to add more variable functions I noticed my code was starting to become longer and I presume anyone other than me illegible.
My question is: is this bad practice; this usage of anonymous functions, variable functions and ob_flush() (more importanntly, does it do what I think it does)?
EDIT: The code itself works. In here I might've made some logical mistakes, but the point is to display the usage of functions.
EDIT 2: Okay, so I realized I can have one anonymous function display $articles24h and $articles48h any paginating content just by passing each as $content, like so function($content) use ($something_else, $but_common){ //yada yada }, which helps a lot, but I still don't know if this is desirable. i.e. there is a better way.
EDIT 3: I created 3 View classes, one View class with static render() method that just takes a template name and array with variables and prints out a template, and two others, PaginationView and PartialView; both have their own render() method and don't extend View class (even thought if I was a better programmer yadda yadda I' do it differently). Pagination paginates partial views that PartialView stores in a class variable and creats when constructed. If I don't need to paginate data, I just call PartialView->render()
Neat (IMO), but it looks kinda ugly in code, like so:
$content=Article::getByAuthorID(array('author'=>$profile->id), array('order'=>$order,'column'=>'time_submitted'));
$user_articles=new PartialView('_article', $content, array('length'=>60));
$user_articles=new PaginationView($user_articles->views, 10, 1);
$user_articles is then sent to
echo View::render('user_page', array('articles'=>$user_articles, 'profile'=>$profile, 'comments'=>$user_comments));
As you can see, I also do this for comments so the code gets lengthy. I don't want to change anything it I am not breaking anything, although I feel like this is a violation of DRY in some way (?)
Is there a better way to do this, without sacrificing clarity but making it more compact?
I'm working with a PHP MVC Framework. Works really well. I like the separation of the business layer (model) with the business logic (controller). But i just stumbled upon a problem. Here's the thing:
Suppose i navigate to the following url:
http://localhost/user/showall/
In this case the userController.php is called and within that file there is a method showallAction() which gets executed.
In the showallAction() method i simply do a request to a model which gets all the users for me. Something like this:
public function showallAction()
{
// create userModel object
$users = new userModel();
// get all users and assign the data to a variable which can be accessed in the view
$this->view->users = $users->getAllUsers();
// render views
$this->view->render();
}
So this method gets all the users, assigns the data returned from the userModel to a variable and i can easily work with the returned data in my view. Just a typical MVC thing.
Now here comes the problem.
I also need to create a native iphone variant. Ofcourse the looks will be totally different. So all i actually want to do is to request this url:
http://localhost/user/showall/
And that it just gives me the array (in json format) back. So i can use that for the mobile development.
But this obviously can't be done right now because the showallAction() method assumes that it is for web browser display. It doesn't echo JSON formatted, instead it simply assings the array of users to a variable.
So that means i have to create another method "showallMobileAction()" in order to get the data, but specifically for the mobile device. But this is not an elegant solution. I'm sure that are better ways...
Anyone any idea how can i solve this problem??
In your situation i would modify the routing mechanism.
It would be useful, if you could add extension at the end of URL, which represents the format you expect, like :
http://foo.bar/news/latest >> HTML document
http://foo.bar/news/latest.html >> HTML document
http://foo.bar/news/latest.rss >> you RSS feed
http://foo.bar/news/latest.json >> data in JSON format
It's a simple pattern to recognize. And you can later expand this to add .. dunno .. pdf output, or Atom feeds.
Additionally , two comments :
Model is not a type of objects. Instead it is a layer, containing objects responsible for business logic, and objects responsible for data storage/retrieval.
View should be a full blown object, to which you bind the domain objects (objects responsible for business logic).
You could pass parameters to your url:
/user/showall/json
and get the third URL segment with a custom function or a built-in one. For instance, with CodeIgniter: $this->uri->segment(3).
Some frameworks will pass the additional parameters to your method. Just try this with the URL I wrote above:
public function showallAction()
{
print_r(func_get_args());
}
I'm not familiar with PHP MVC but in general terms I'd use the "accepts" HTML header field to request the response in either "text/html" or "text/json", the controller would check for the accepts type and return the response accordingly.
my code is far from DRY and it needs refactored but for the time being i need to split it into files- i have a 300 line controller method that does a lot of api work when called and i just want to put some code into another file so i can read it a bit better without going and re-writing it.
if($type == "like"){
$this->load->helper('posts/likes');
}else{
$this->load->helper('posts/pic');
}
i tried the above method but its treating it like a normal (would you believe it)-
basically i want to copy and paste the code into another file to clean it up slightly, but the new file should just be an extension of the current method. i dont what to to use php functions (require_once or similar).. can ci not do this?
to clarify
I had a very large controller method- and instead of having 400 lines of code in the one method i want to split it in two files and have the code reside in there. If the conditional passed one file would load into the controller instead of another-
answering my question on how to load files by posting about DRY class methods didnt help my situation- the code should be cleaned- refactored and sorted into a library, i know this but i need a temp fix
my answer
The fix was to to lump the code into a subfolder within the controller directory for my posts controller i put the two files in the posts subfolder then used require_once- which worked but i thought ci may have soemthing to load blocks of code.
General rule is to keep controller slim.
DRY is the thing you need to take seriously and get ride of it.
Usually I will prepare the controller to call a single model method,
and this model method WILL prepare, and return all the necessary data in correct format back to controller method
so, you can avoid result looping in controller
$result = $this->some_source->get_comments($limit=30);
// I don't do loop in controller
$this->load->view("display", $result);
class some_source extends xxx
{
function get_comments(...)
{
// get results
// loop
// do necessary massage / format
// and return
}
}
I have a CI page that will load to a div in view file, using jQuery. Using switch(page_parameter), I control what is showing from the page.
When I call the page for 3rd time, I set a value to the class array.
But when I call the 4th time, the array become empty.
I was wondering, is it actually possible to use the class property to store value that can be used after page re-access? Or something missing in my head?
I know that using session is not a good idea, since the real array is a big chunk of serialized xml.
Here's my code:
class MyClass extends MY_Controller
{
public static $pitems = array();
function Hotel(){
parent::MY_Controller();
}
function new_campaign(){
$params = $this->uri->uri_to_assoc();
switch($params['step']){
case '3' : self::$pitems = array("test","another"); //here the class array was set successfully
$this->load->view('viewer');
break;
case '4' : print_r(self::$pitems); //here the array is empty
break;
}
}
In the viewer page, there's a call to the page:
Next page
Same issue also with $this->
What am i missing here?
Thanks in advance~
edit:
I saw a script that has similar scenario. it successfully reused the variable set in the constructor, instead of treating it as a class variable. i'll look thorough to confirm this, but for now, i'll close this thread. Thanks Chris for sharing.
Im not really sure what your trying to do but I have users jQuery post()/get()/ajax() many of times in CI and have had no problems. So despite not knowing or understanding what your trying to do. I thought I'd at least say I know loading data without refresh in CI through something like jQuery isn't an issue. Example on system I built on CI had a twitter like feed of tweets where jQuery on a timer was polling for new data and coming back with it each time accordingly if something new was to be shown.
I have a CakePHP application that in some moment will show a view with product media (pictures or videos) I want to know if, there is someway to include another view that threats the video or threats the pictures, depending on a flag. I want to use those "small views" to several other purposes, so It should be "like" a cake component, for reutilization.
What you guys suggest to use to be in Cake conventions (and not using a raw include('') command)
In the interest of having the information here in case someone stumbles upon this, it is important to note that the solution varies depending on the CakePHP version.
For CakePHP 1.1
$this->renderElement('display', array('flag' => 'value'));
in your view, and then in /app/views/elements/ you can make a file called display.thtml, where $flag will have the value of whatever you pass to it.
For CakePHP 1.2
$this->element('display', array('flag' => 'value'));
in your view, and then in /app/views/elements/ you can make a file called display.ctp, where $flag will have the value of whatever you pass to it.
In both versions the element will have access to all the data the view has access to + any values you pass to it. Furthermore, as someone pointed out, requestAction() is also an option, but it can take a heavy toll in performance if done without using cache, since it has to go through all the steps a normal action would.
In your controller (in this example the posts controller).
function something() {
return $this->Post->find('all');
}
In your elements directory (app/views/element) create a file called posts.ctp.
In posts.ctp:
$posts = $this->requestAction('posts/something');
foreach($posts as $post):
echo $post['Post']['title'];
endforeach;
Then in your view:
<?php echo $this->element('posts'); ?>
This is mostly taken from the CakePHP book here:
Creating Reusable Elements with requestAction
I do believe that using requestAction is quite expensive, so you will want to look into caching.
Simply use:
<?php include('/<other_view>.ctp'); ?>
in the .ctp your action ends up in.
For example, build an archived function
function archived() {
// do some stuff
// you can even hook the index() function
$myscope = array("archived = 1");
$this->index($myscope);
// coming back, so the archived view will be launched
$this->set("is_archived", true); // e.g. use this in your index.ctp for customization
}
Possibly adjust your index action:
function index($scope = array()) {
// ...
$this->set(items, $this->paginate($scope));
}
Your archive.ctp will be:
<?php include('/index.ctp'); ?>
Ideal reuse of code of controller actions and views.
For CakePHP 2.x
New for Cake 2.x is the abilty to extend a given view. So while elements are great for having little bits of reusable code, extending a view allows you to reuse whole views.
See the manual for more/better information
http://book.cakephp.org/2.0/en/views.html#extending-views
Elements work if you want them to have access to the same data that the calling view has access to.
If you want your embedded view to have access to its own set of data, you might want to use something like requestAction(). This allows you to embed a full-fledged view that would otherwise be stand-alone.
I want to use those "small views" to
several other purposes, so It should
be "like" a cake component, for
reutilization.
This is done with "Helpers", as described here. But I'm not sure that's really what you want. The "Elements" suggestion seems correct too. It heavily depends of what you're trying to accomplish. My two cents...
In CakePHP 3.x you can simple use:
$this->render('view')
This will render the view from the same directory as parent view.