I'm new to Object-Oriented PHP so I'm not entirely sure what my question really is, but here's the jam:
I created a WordPress plugin that adds a bunch of metaboxes on a certain custom post type created by another plugin. In my theme I need to get all the custom meta, but it's quite a lot that needs to be gotten, and usually with certain conditionals and so forth. So, I created a bunch of helper functions to simplify it. Problem is the names of all these functions are kind of a pain, so I put them inside a class in my plugin to simplify things, and my issue is that I'm having trouble combining them. Whether that's possible or not, hopefully you can help with...
So most of the helper functions (now class methods) look something like this:
public function metabox_wrapper( $code ) {
$info = get_post_meta( ID, 'blah', true);
if ( $info ) {
code( $info );
}
}
Then in my theme file I use:
global $Other_Plugins_Global // Created by the other plugin.
$class = new Class( $Other_Plugins_Global );
$class->metabox_wrapper( function( $info ) {
?>
<div>
<?php echo $info; ?>
</div>
<?php
});
The above is currently working - it displays the blah meta from the current post if the blah metadata exists. The other plugin's global variable contains the custom post type info (and therefore the meta), so I pass that through the class instance, use a method in the class to manage all the conditionals and other complicated stuff, and then use the method in the theme file, so that the conditional stuff is pre-arranged and able to be used in the same way elsewhere in the theme.
However, here's the rub: There are situations where I want something like
if (
Do this
if (
Also do this
)
)
But this (and everything like it that I've tried) does not work:
global $Other_Plugins_Global
$class = new Class( $Other_Plugins_Global );
$class->metabox_wrapper( function( $info ) {
?>
<div>
<?php echo $info; ?>
</div>
<?php
$class->other_metabox_wrapper( function( $stuff ) {
?>
<div>
<?php echo $stuff; ?>
</div>
<?php
});
});
So my question is... is there a way I can use these wrapper functions inside of each other? I've tried setting them as static and using :: in the theme, I've tried some fancy variable gymnastics that didn't work and would've been ugly af if it had, I almost raised a demon trying to call the global variable from inside the method... so at this point I'm not even sure what to Google.
Obviously for this particular example I could just use if(){}, but the conditional logic often has multiple conditions and other semi-complicated stuff, so it'd be helpful to keep that separate and able to be used in multiple places around the theme.
I will say that if this is a dumb question and you know of a completely different and more elegant way to accomplish what I'm describing, then I'm all ears.
Many thanks for the help.
The problem lies not in the realm of OOP, but rather in the realm of functions and function scopes. You see, when you create an anonymous function it has its own scope -- the scope of variables that are accessible. In most languages, anonymous function encloses the scope it was created in, creating a closure -- meaning it can access the copies of all variables from the parent scope. In PHP it is not the case, so in order to access $class variable from within the anonymous function you should use use: $class->metabox_wrapper(function($info) use ($class) {.
Another approach would be to bind the context to anonymous function with Closure::bind():
public function metabox_wrapper($code) {
$info = get_post_meta(ID, 'blah', true);
if ($info) {
$code = Closure::bind($code, $this);
$code($info);
}
}
Then you can use $this inside the anonymous function:
$class->metabox_wrapper(function($info) {
?>
<div>
<?php echo $info; ?>
</div>
<?php
$this->other_metabox_wrapper(function($stuff) {
?>
<div>
<?php echo $stuff; ?>
</div>
<?php
});
});
But to tell you the truth I agree with #CrisanLucian. Mixing the code with HTML in such a manner is not the best way to program.
First of all you have to read some Coding Standards articles
1. https://www.tutorialspoint.com/php/php_coding_standard.htm
Try to make separation between PHP code and HTML; Using Twig instead PHP make life much easy
https://aristath.github.io/blog/using-twig-in-wordpress
When you have a more help (hooks) functions better to group them into a class, or create a Helper dir structure and create a file for each scope. (e.g: Trim strings , Resize Images create different files)
After creating Helpers try to not put HTML inside; A helper suppose to return a value ( boolean ).
e.g showCampaign() => will return True;
HTML page/ TWIG page
{{ if(showCampaign()) }}
//HTML CODE IS HERE
{{ endif }}
When you have too many if/else statements is need refactor check the link above or another example: https://carlalexander.ca/mastering-php-conditionals/
happy codding!
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?
It's really convenient how MVC patterns allow you to define view a and then load variables into it via the controller. For the sake of argument let us take CodeIgniter as an example:
Controller:
Class Example extends CI_controller(){
function show_page(){
$data = array('msg'=>'Hello World');
echo $this->load->view('hello',$data);
}
}
View (hello.php):
<h1><?php echo $msg; ?></h1>
I have taken over an old project written years ago where there are redundant html code everywhere. It has no pattern whatsoever just straight up poorly structured code.
I wanted to create a class that has a function that will fetch all HTML code from a file in one folder, feed variables to them and show the result. Like so:
Folder structure:
View_folder
- hello.php
Class
- view_class.php`
Main:
<?php
$data['msg'] = 'Hello World!';
echo $view_class->get_view('hello.php',$data);
?>
Is it possible to achieve this? Can someone give an example function on how to do this. Thanks.
Sure thing, that's what the frameworks are doing. Here's a really basic overview of how I'd expect that to work:
function view($template, $data){
extract($data); // this pulls all of the first-level array keys out as their own separate variables
ob_start(); // this turns on **output buffering** which is the method we'll use to "capture" the contents of the view (there are surely other ways)
require $template; // you should prepend some sort of fixed path to this where all of your templates will reside
echo ob_get_flush(); // turns off output buffering, returning the buffered content as a string which we then send to the browser.
}
This has probably been asked before, but I couldn't find the answer to my question with some preliminary searches, so here it is:
A very simple example of my current method of injecting PHP variables into HTML is as follows:
HTML (file.php):
<tag><?php echo $variable; ?></tag>
PHP:
$variable = "value";
ob_start();
include "file.php";
$results = ob_get_clean();
This achieves the correct result just fine, but it annoys me every time that I have to copy and paste those three lines to get variables injected into my HTML files. There's a chance I might want to change the details of this injection functionality at a later date, and it's currently scattered in a couple hundred locations throughout my code.
In a perfect world, I would move those three lines to a separate function that takes the file path as an input, and returns the "compiled" result. However, the function would then no longer have access to the calling scope, and I would have to pass the variables in as another input. The only thing I'm able to think of for doing that is:
function injectVariables($filePath, array $variables)
{
//Built-in PHP function, extracts array key => value pairs into variable $key = value pairs in the current scope
extract($variables);
ob_start();
include $filePath;
return ob_get_clean();
}
That certainly gets the job done, and it will be what I implement if there aren't any better solutions, but it seems less than ideal to me. It involves creating an array every time this runs, just to loop through it and extract all the variables, which just feels a bit wrong to me.
Does anyone know of any other methods that might work?
Not really sure what you are asking, but here is my answer
I don't know the structure of your code, but I hope you are using a MVC approach (or at least something which deals with classes) so that you can do the following:
In your main controller, you create a class variable like viewData which will be an array. And you put in it everything you want
$this->viewData['username'] = $username;
$this->viewData['myArray'] = array('foo' => 'bar');
$this->viewData['menuSubview'] = 'path_to_menu_subview.php';
Then, you create a render function.
public function render()
{
extract($this->viewData);
ob_start();
include("myfile.php");
return ob_get_clean();
}
And in myfile.php (with the HTML) you can simply do what you used so far
<div id="menu"><?php include($menuSubview);?></div>
<p><?=$username;?></p>
<p><?=$myArray['foo'];?></p>
The whole code can be something like this.
class Something {
protected $viewData;
protected $viewFile;
public function logic()
{
$this->userProfile();
echo $this->render();
}
public function userProfile()
{
$this->viewData['username'] = 'John The Ripper';
$this->viewFile = 'myFile.php';
}
public function render()
{
extract($this->viewData);
ob_start();
include($this->viewFile);
return ob_get_clean();
}
}
Here's a simplified class that stores data and allows for recursive rendering of files that all have access to the save data form the initial instance.
class View {
// view data
protected $_data = [];
/**
* Set view data
*/
public function __set( $key, $value )
{
$this->_data[ $key ] = $value;
}
/**
* Get view data
*/
public function __get( $key )
{
if( array_key_exists( $key, $this->_data ) )
{
return $this->_data[ $key ];
}
return null;
}
/**
* Render view file
*/
public function render( $file )
{
if( is_file( $file ) )
{
$view = $this;
ob_start();
include( $file );
return ob_get_clean();
}
return 'File not found: '.$file;
}
}
Just use the variable $view inside your included files to access the data in the class, or render() another partial file that can do the same thing, and so on.
// Bootstrap a View instance and add some data
$view = new View;
$view->dataOne = 'valueOne';
$view->dataTwo = 'valueTwo';
// Render main template
echo $view->render( 'template.php' );
Inside template.php
<header>
<?= $view->render( 'header.php' ) ?>
</header>
<h1><?= $view->dataOne ?></h1>
<p><?= $view->dataTwo ?></p>
Nothing is wrong with your injectVariables() function, it is in fact a good idea.
You shouldn't have performance impact anyway (If this is your main concern your are doing premature optimization !)
Your view (HTML) shouldn't know about the internal of your application. (You split responsability - this is a huge subject that I won't talk deep)
You know if something end-up into $variables it has been build/formatted or is revelent to be display for $filePath.
In complex system you may end up with a variable with a pdf generator, why would the HTML want that? The only purpose of the HTML is to DISPLAY HTML data.
Some variables from your logic will end-up in $variables almost every time such as session informations (Who is currently logged), for your case this is normal.
In the perfect world if you put a class into $variables it should be designed only for purpose of your HTML even if it is almost the same object as your logic.
As an exemple I designed a Session class in my logic. It does have method such as Logout() and EnsureIsAlive() as well as field such as FullName. FullName is going to be use by my HTML because I want to display it in the header of my page.
I should have a Session class for the HTML that only contains a FullName field because the HTML has only one job and is it to display data. Login() and EnsureIsAlive() are specific for the logic and aren't related at all with displaying data.
In reallity time is always a constraint and because you are writing all by yurself from scratch you may end-up just sending the logic Session class into $variables.
Design note : I'm a C# programmer and we always use class over array to pass parameters as a good pratice and it does affect the exemple about how to pass FullName to your HTML. In your case, instead of creating a class dedicated for the HTML you could also create a simple array :
$variables['Session'] = array('FullName' => $mySession->fullName).
That way you still avoid your HTML to have access to the unrelated method specific for your logic. I'm not sure if it is a good pratice in php...
Keep context variables clean
If you do call injectVariables() more than once (for different $PathFile) you may not want the same variables.
One injectionVariables() could be for a widget, another for a complete complete page.
Why would a TimeWidget need your totalInvoice variable?
You also have the ability to prepare multiples $variables at the same time because each injectVariables() will have his own parameters.
I'm not up-to-date but I know phpBB template system use (back near 2000) the exact same pattern as your injectVariables()).
You wrote that each of your page do call injectVariables(), you could also (but it may be complex) do it only in one file - like php frameworks.
Framework handle HTTP requests into one specific file
(eg. http://yoursite.com/hello/world or http://yoursite.com/login would call (internaly) http://yoursite.com/index.php?page=hello&subpage=world and http://yoursite.com?page=login).
Then this page (index.php in my exemple) would include a specific file (controller) according with his GET parameters ($_GET['page'] and $_GET['subpage']).
The controller job would be to fetch data, apply logic then fill $variables.
Then index.php will call injectVariables() with the releated HTML.
Currently I am doing it like this:
function load_template($script, $args){
extract($args);
require __DIR__ . '/templates/' . $script;
}
In my controller code:
// if home page was requested
load_template('home.php', array(
'title' => get_title(),
'content' => get_content(),
...
));
The template is just a PHP script like
<!DOCTYPE html>
<html>
<head>
<title> <?php echo $title; ?> </titlee>
...
I was wondering if it's possible to lazy load these variables somehow, so I don't actually run get_title() or get_content() until the template specifically requests the variable.
Could this be possible, without creating a template parser thingy? I'd really like to stick with simple .php scripts and html as templates.
In short, what I'm asking is if it's possible to auto-assign a value to a variable only when it's first requested.
$var = func(); // this should not run
if($var){ // now the code above should run:)
echo $var; // <- the value that was just assigned (don't run func() again)
}
In my opinion, if you do not wish to change your template to extract the variables, you can create for example an array that would know which variables each template needs.
You may consider a function (let's name it caller) into which you pass all parameters and the template name. The caller could choose which variables are required. This idea is like a factory class in oop.
I think there is no other way, but...
While inserting a template and use unexistent variable, a warning will be shown. You can make PHP to throw exceptions in warnings and then in try ... catch block parse it. I think it's too complicated and not worth effort.
EDIT
The third idea is to create objects instead of arrays. The object would keep all your $args variable. In your template just change <?php echo $title; ?> to <?php echo $argument_object->getTitle(); ?>, and code the getTitle() method. The getTitle(), as a method not a function, would then be run only on request.
I am somewhat confused on the proper way to perform the following. Is this a class a function or an object? (Not sure) So here goes (please be kind) I'm learning codeigniter/php simultaneously.
$is_live = $this->session->userdata('email');
$is_live = ucwords($is_live);
if (!$is_live == null)
echo 'Hello, '.$is_live.
' <b>(Not you? '.'<a href="' .base_url().
'main/logout">Log Out</a>) </b>';
I wrote this code, which checks if a session is set and if true echo the details. I currently have this code on my view/main However I want to have this code displayed on all pages.
What is the proper way to do this?
Do i create a view/header which is auto loaded on each page and insert this code there?
Do I create a class/public function in a helper file and load it where needed?
Do I create a class/public function in a library file and load it where needed?
Do I create a public function in a controller?
-EXTRA-
My assumption is option 1 but I'm curious what if I want to display this information in another location on a particular page? I don't want to have to copy/paste the code block because that is sloppy.
How would I set it up so that I can just call it where i need it?
e.g. $this->load->helper('check_for_sess');
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class isLive
{
public function is_live() {
$is_live = $this->session->userdata('email');
$is_live = ucwords($is_live);
if (!$is_live == null)
echo 'Hello, '.$is_live.
' <b>(Not you? '.'Log Out) </b>';
}
}
On the view I tried this:
<?php
$isLive = new isLive;
$isLive->is_live();
?>
This however doesn't work and causes a ' function userdata() on a non-object error '
Can someone please explain the proper way to achieve my desired solution and the correct syntax. Thanks in advance!
-UPDATE-- Tried to create a library - still getting error.
#Created a library
class CheckSess
{
function IsLive()
{
$is_live = $this->session->userdata('email');
$is_live = ucwords($is_live);
if (!$is_live == null)
{
$check_msg = 'Hello, '.$is_live.
' <b>(Not you? '.'Log Out) </b>';
}
return $check_msg;
}
}
#In the View
<?php echo $this->CheckSess->IsLive(); ?>
#in the controller
$this->load->library('CheckSess');
--UPDATE--
class CheckSess {
function IsLive()
{
$CI =& get_instance();
$is_live = $CI->session->userdata('email');
$is_live = ucwords($is_live);
if (!$is_live == null)
{
return 'Hello, '.$is_live.
' <b>(Not you? '.'Log Out) </b>';
}
}
}
Set this^ is the libraries folder
the view shows <?php echo $this->CheckSess->IsLive(); ?>
and the controller shows $this->load->library('CheckSess');
please advise. Thanks again!
ERROR---
( ! ) SCREAM: Error suppression ignored for
( ! ) Fatal error: Call to a member function IsLive() on a non-object in C:\wampserver\www\test-app\application\views\homepage.php on line 56
Call Stack
Time Memory Function Location
1 0.0005 151432 {main}( ) ..\index.php:0
2 0.0014 187792 require_once( 'C:\wampserver\www\test-app\system\core\CodeIgniter.php' ) ..\index.php:202
3 0.0277 1537000 call_user_func_array ( ) ..\CodeIgniter.php:359
4 0.0277 1537048 Main->index( ) ..\CodeIgniter.php:359
5 0.0283 1540200 CI_Loader->view( ) ..\main.php:8
6 0.0283 1540640 CI_Loader->_ci_load( ) ..\Loader.php:419
7 0.0287 1566584 include( 'C:\wampserver\www\test-app\application\views\homepage.php' ) ..\Loader.php:833
The best ways to write such reusable in CodeIgniter are Models and Libraries.
With libraries being better because the code and logic you use isn't actually what models in MVC are, but with CodeIgniter and especially for a beginner it's pretty much ok to write this in models if you are not planning to distribute or share this code in any way.
I would suggest reading more on what goes where in CodeIgniter, you can find plenty information here, on SO or on official CI forums, but as a quick reference, I will write a list of what goes where.
Views
Static, dumb data output, formatting, iteration(for displaying tables, lists, etc.).
The only things you want to use here are some of the basic PHP functions, CodeIgniter helpers, for, foreach, while loops and conditionals (if, case)
Controllers
Form data capture and validation, catching data from models, sending data to views, redirects.
Models
Querying, reading, writing and updating data from databases, remote sources (APIs), files.
Helpers
Often used functions that are expected to be used in views - formatting HTML, parsing and displaying trees as lists <ul><li></li></ul>, shorthand functions for other functions like
function mydate() {
return date("d.m.Y");
}
Stuff that does not use any of the CodeIgniter libraries/models (Technically it can use, but should be avoided in most cases).
Libraries
Custom classes and objects, data processing, API interfaces, complex of the aforementioned
If you want to display some view file with data, I would put it in a library. You can put it in to a model. In my projects when I use some parts, I have a library/model (difference in our case is minimal), lets call it pmodel.
class Pmodel extends CI_Model() {
function ShowHeader() {
$data = array(
'user' => $this->usermodel->GetCurrentUser()
}
$this->load->view('header', $data);
}
function ShowMyPart() {
$is_live = $this->session->userdata('email');
$is_live = ucwords($is_live);
if (!$is_live == null)
echo 'Hello, '.$is_live.
' <b>(Not you? '.'<a href="' .base_url().
'main/logout">Log Out</a>) </b>';
}
}
Now anywhere in the view files you can call $this->pmodel->ShowMyPart() if you have loaded it before (models of this type should be autoloaded anyway).
There is one little change I would do as it's considered as good practice - never echo anything from anywhere but the view files, return values instead.
Change the echo keyword to return and in your view file simply write echo $this->pmodel->ShowMyPart().
Why should you do that? Because, if you would like to call any method that echoes something from the controller right between loading header and footer(eg.), CodeIgniter will echo this before the header as CodeIgniter does not echo your loaded views right away, it saves them to output storage and sends the finalized output right before destructing himself. More reading can be found here.
But you can do even better - use a view file to output any data you want, just create a helper and load it from your model/library as you do in your controllers.
Hope that helps!
EDIT
For your edited question, the reason why you are getting errors with your library is that your library doesn't know that an instance of CodeIgniter even exists as you are not extending your library from it, as you do with your models and controllers. You don't use $this to refer to CodeIgniter classes in your libraries, as $this is a reference to your library itself, which is a separate class. To use CodeIgniter classes you should get a CodeIgniter instance:
$CI =& get_instance();
And then you can use $CI instead of $this to call CodeIgniter classes by changing the line:
$is_live = $this->session->userdata('email');
to
$is_live = $CI->session->userdata('email');
You could make a basic html helper and autoload it in the config file.
Then you can make a function for this...
function displayLoginStatus($em)
{
$html = '';
if($em)
{
$html = 'Hello, '.ucwords($em).' <b>(Not you? '.'Log Out) </b>';
}
return $html;
}
then you call it like a normal function:
<?php echo displayLoginStatus($this->session->userdata('email')); ?>
but ideally you would place something like this in a template or header file that wraps your standard page views. There's no need to make a function out of something like this since typically you don't have the login / logout in multiple places on the page.
It could be condensed simply to:
<?php if($this->session->userdata('email')): ?>
Hello, <?php echo ucwords($this->session->userdata('email')); ?> <b>(Not you? Log Out) </b>
<?php endif; ?>
I prefer to make a site template and wrap the views with it. Makes common elements much easier to handle.
Some people template it this way:
http://codeigniter.com/forums/viewthread/88335/
There are many ways you could handle this, but the general idea is to separate your most common elements into one template view. I don't know of a succinct way of instructing you on how to do this so I will only update this post if you feel the need for much more explanation on templating.
helper docs:
http://codeigniter.com/user_guide/general/helpers.html