I'm writing a class - called MenuBuilder - in PHP 7 which is intended to build navigation menus in a web application.
The way in which the class works is by building up an output buffer which holds the markup for the menus.
It starts off by declaring an empty variable inside class MenuBuilder:
private $output_buffer = '';
There is a method to add to it:
protected function add_to_output_buffer($input = '') {
if ($input !== '') {
$this->output_buffer .= $input;
}
}
And a method to display the menu markup:
public function render() {
echo $this->output_buffer;
}
So far quite straightforward. Here's the problem... When I add a link to the menu I use this method:
public function add_menu_item($item) {
$this->menu_items[] = $item;
}
This builds up an array ($this->menu_items) and there is then another method which cycles through the items in it to add them to the output buffer. It's quite long but a shortened version of it is here:
public function create_links() {
foreach ($this->menu_items as $item) {
$output = '';
$output = ''.$item['text'].'';
$this->add_to_output_buffer($output);
}
}
There is quite a lot of logic in create_links() because various classes and other things get applied to the links, but essentially it has to work like this because it needs to loop through the full array of links.
The problem:
If I want to add random HTML at any point in the menu, I have a function to do this:
public function add_misc_html($html = '') {
$this->output_buffer .= $html;
}
But in my script which uses these functions, it doesn't know the order in which things are being called. An example:
$MenuBuilder = new MenuBuilder;
$MenuBuilder->add_menu_item(['text' => 'Link 1', 'link' => '#']);
$MenuBuilder->add_menu_item(['text' => 'Link 2', 'link' => '#']);
$MenuBuilder->add_misc_html('<h1>testing add_misc_html</h1>');
$MenuBuilder->add_menu_item(['text' => 'Link 3', 'link' => '#']);
$MenuBuilder->create_links();
$MenuBuilder->render();
So the problem is that the desired output is:
Link 1
Link 2
<h1>testing add_misc_html</h1>
Link 3
But this won't work, because when I call create_links() it's not aware of add_misc_html and where this is called in the overall structure of the calls to MenuBuilder
What ways can I achieve the desired output? Please note that add_misc_html() needs to work anywhere it gets called, prior to render(). There is a whole load of other stuff in the class to open the menu and close it, so it needs to be able to modify the $this->output_buffer at any point.
This is because your add_misc_html writes directly to your output buffer, where add_menu_item modifies the structure of your object, you can't mix these calls. You must first write your modified object state to the buffer and only then write "misc" HTML to the buffer. This is pretty bad though.
There are many solutions you can come up with but the one I will suggest to you is to not have an output buffer at all, and possibly extend the logic of the class with a couple other classes.
class MenuBuilder {
protected $items = [];
public function add(MenuItem $item) {
$this->items[] = $item;
}
public function render() {
return join('', $this->items);
}
}
Where you will have different types of menu items, like Link and Misc that are used in your example.
interface MenuItem {
public function __toString();
}
class LinkMenuItem implements MenuItem {
protected $link;
protected $text;
public function __construct($link, $text) {
$this->link = $link;
$this->text = $text;
}
public function __toString() {
return ''. $this->text .'';
}
}
class MiscMenuItem implements MenuItem {
protected $html;
public function __construct($html) {
$this->html = $html;
}
public function __toString() {
return $this->html;
}
}
And now your class will be used like this.
$builder = new MenuBuilder();
$builder->add(new LinkMenuItem('#', 'Link 1'));
$builder->add(new LinkMenuItem('#', 'Link 2'));
$builder->add(new MiscMenuItem('<h1>Testing html</h1>');
$builder->add(new LinkMenuItem('#', 'Link 3'));
And when you want to render.
$builder->render();
Edit
Based on the discussion in the comments I am coming to think that you are having trouble rendering the objects as HTML hierarchically. Again there are a number of ways you can go about this, I am just proposing one
You can add a base class that your other menu items will extend
abstract class ParentMenuItem extends MenuItem {
protected $children = [];
public function addChild(MenuItem $child) {
$this->children[] = $child;
}
public function __toString() {
return '<ul>' . join(null, $this->children) . '</ul>';
}
}
Then your LinkMenuItem will look like this
class LinkMenuItem extends ParentMenuItem {
protected $link;
protected $text;
public function __construct($link, $text) {
$this->link = $link;
$this->text = $text;
}
public function __toString() {
return ''. $this->text .'' . parent::__toString();
}
}
When you want to add children you will add them to the menu item they belong to (duh?)
$builder = new MenuBuilder();
$link1 = new LinkMenuItem('#', 'Link 1');
$link1->addChild(new LinkMenuItem('#', 'Child1'));
$link1->addChild(new LinkMenuItem('#', 'Child2'));
$builder->add($link1);
$builder->add(new LinkMenuItem('#', 'Link 2'));
$builder->add(new MiscMenuItem('<h1>Testing html</h1>'));
$builder->add(new LinkMenuItem('#', 'Link 3'));
You can also enable chaining to avoid using variables and all kinds of other good stuff like factories etc.
Related
I want to replace (if that's not possible: remove) a class variable from outside the class.
The class is like this:
class Frontend {
protected function __construct() {
add_action( 'wp_head', array( $this, 'debug_marker' ), 2 );
}
public function debug_marker() {
$marker = sprintf(
'<!-- This is some advertisement HTML to advertise our product -->',
);
return $marker;
}
}
How can I replace $marker? If that's not possible, how can I remove it in any way?
The variable is inaccessible as long as it is private and inside the class, to use it would have to be public and still create the object, or you can create a method to be able to modify it.
<?php
class Frontend {
private $marker = '';
public function debug_marker() {
$marker = "<!-- This is some advertisement HTML to advertise our product -->";
}
Public function SetMarker($var){
$this->marker = $var;
}
Public function GetMarker(){
return $this->marker;
}
}
$front = new Frontend();
$front->SetMarker("Hello");
echo $front->GetMarker();
So you can modify your variable with this metod when you want, but only if you have the instance of the class
I have create array $_en to store English words/sentences and $_no array to store Norwegian text to use it as translation for my core PHP project.
<?php
$_en = array(
'mail' => 'email',
'msg1' => 'how are you?'
);
$_no = array(
'mail' => 'epost',
'msg1' => 'hvordan har du det ?'
);
echo "EMAIL IN ENGLISH:".$_en['mail']."\n"; //email in english
echo "EMAIL IN NORWEGIAN:".$_no['mail']; //email in NORWEGIAN
echo "Message IN NORWEGIAN:".$_no['msg1']; //Message in NORWEGIAN
Based on the array and the key value the text will be called based on the site translation function.
If any better solutions and enhancements are most welcome. Correct me thanks in advance.
A better solution, as mentioned my Thamilan in the comments would be to use a class to handle conversions.
Think of it like this;
Your Template File
$trans = new Translate('en');
<h1><?php echo $trans->__('This is a title'); ?></h1>
Translate.php
class Translate {
public function __construct($lang) {
$this->lang = $lang;
}
public function __($string) {
// $translatedString = $this->getTranslatedString($string);
return $translatedString;
}
}
In this class, you'll need to fetch the translation from where it's stored. You could store in in this class in an array, or a better solution would be to load them from a CSV file.
Try this :--
parse_ini_file() is a very powerful little tool that does exactly what you expect. Using a simple file like this which we'll call en.ini:
PAGE_TITLE = My website page title
HEADER_TITLE = My website header title
SITE_NAME = My Website
SLOGAN = My slogan here
HEADING = Heading
MENU_LOGIN = Login
MENU_SIGNUP = Sign up
MENU_FIND_RIDE = Find Ride
MENU_ADD_RIDE = Add Ride
MENU_LOGOUT = Logout
You can simply use: parse_ini_file('en.ini') to return an array exactly as in your switch statement, which will be much easier for other (non-programmers) to read and write for you. And if you were to then continue naming the files with this style, you could reduce userLanguage() to something like:
public function userLanguage()
{
$file = '/path/to/language/config/' . $this->UserLng . '.ini';
if(!file_exists($file))
{
//Handle Error
}
return parse_ini_file($file);
}
Another Solution Try:--
Create Abstract class
interface Language
{
public function getPageTitle();
public function getHeaderTitle();
public function getSiteName();
public function getSlogan();
public function getHeading();
public function getMenuLogin();
public function getMenuSignup();
public function getMenuFindRide();
public function getMenuAddRide();
public function getMenuLogout();
}
Though the interface is small, implementing it may produce a big file, though it would arguably be clearer than the array style:
class English implements Language
{
public function getHeaderTitle()
{
return 'My website header title';
}
public function getHeading()
{
return 'Heading';
}
// etc...
}
Alternative:--
Alternatively, you could combine these styles and have a singleton Language with getter methods accessing that array, i.e.:
class Language
{
private $languageArray;
private $userLanguage;
public function __construct($language)
{
$this->userLanguage = $language;
$this->languageArray = self::userLanguage();
}
private static function userLanguage()
{
$file = '/path/to/language/config/' . $this->userLanguage . '.ini';
if(!file_exists($file))
{
//Handle Error
}
return parse_ini_file($file);
}
public function getPageTitle()
{
return $this->languageArray['PAGE_TITLE'];
}
public function getHeaderTitle()
{
return $this->languageArray['HEADER_TITLE'];
}
//etc...
}
Which will provide the benefits of both. Personally though, unless you're planning on adding more languages in the very near future, I believe solution #2 would suit you best.
I have a master class and semi-master class. Master class core is a class that prints all head, nav, footer and meta elements. Class Base prints content related data and all page-level classes extend it and print their data within specific div.
My problem is that functions and globals are not within scope in one of functions within Base class. Where did I go wrong?
abstract class Core {
abstract protected function print_content();
}
abstract class Base extends Core {
abstract protected function print_page_content();
public function print_content(){
ob_start();
$this->get_nav();
$output = ob_get_contents();
$output .= ' ... '.$this->print_page_content().' ...';
ob_end_clean();
}
}
class Page extends Base {
private function get_games(){...}
private function process_form(){...}
public function print_page_content(){
$this->process_form(); # <--- function doesn't see it (!)
$output = '..... '.$this->get_games().' .... '; # <-- function DOES see this tho (!)
$GLOBALS['m_id']; # <-- or any other global such as $_GET, $_POST is not under scope.
return $output;
}
When I do error reporting it doesn't print any error. It just ignores line where function was called.
However, When I would force error, error would appear.
Let's focus on the technical aspect (and not smaller glitches or design decisions).
I don't think the code snippet you've posted "contains" the error. Let me give you a version that contains everything you've shown us and only adds things that do not "collide" with your code snippet, i.e. minimal implementations of what you've left out (again: purely technical).
<?php
abstract class Core {
abstract protected function print_content();
}
abstract class Base extends Core {
abstract protected function print_page_content();
public function print_content(){
ob_start();
$this->get_nav();
$output = ob_get_contents();
$output .= '###'.$this->print_page_content().'###';
ob_end_clean();
echo $output;
}
public function get_nav() {
echo '| Base::get_nav |';
}
}
class Page extends Base {
protected $names = array();
private function get_games(){ return htmlspecialchars(var_export($this->names, true)); }
private function process_form(){ $this->names['Jean']="D'Arc"; $this->names['John']='Malkovich';}
public function print_page_content(){
$this->process_form();
$output = '<pre>'.$this->get_games().'</pre>';
$output .= '<p>'.$GLOBALS['m_id'].'</p>';
return $output;
}
}
$m_id = 4711;
$p = new Page;
$p->print_content();
it prints
| Base::get_nav |###<pre>array (
'Jean' => 'D\'Arc',
'John' => 'Malkovich',
)</pre><p>4711</p>###
( check here: https://3v4l.org/ZBQ0b )
as it is supposed to do.
So, where does your script differ from this script? Try to minimize your script for testing purposes in order to find the cause.
edit: suggested improvement(s) for your sscce
private function process_form(){
// $stmt = add your prepare()/statement here as a comment
$params = array(/* put in all the variables here, you would bind*/);
// <-- maybe the actual bind code here as comment -->
return var_export( $params, true );
}#endfunc(process_form)
public function print_page_content(){
$pf = $this->process_form();
$output = 'print_page_content stuff...'.$this->get_games()
.' <pre>'.$pf.'</pre>';
return $output;
}#endfunc(print_page_content)
After the abstract class is inherited, the abstract method cannot be rewritten.
I have a reoccuring problem that I am currently tackling like so -
a POST variable coming in to the script which has a platform, the platform is from a list such as: xbox,ps3,pc,mobileapp,mobilegame etc
for each different platform I want to be able to do something different in my script but in some cases I want code to do very similar things at the moment I do something like this:
$platformArray = array(
'ps3'=>array('displayName'=>'playstation 3','function'=>'funcPS3'),
'xbox'=>array('displayName'=>'Xbox','function'=>'funcXbox')
)
//similar amongst all platforms code on line below
echo 'you have a :'.$platformArray[$_POST['platform']]['displayName'].' for playing games';
call_user_func($platformArray[$_POST['platform']['function']);
function funcPS3(){
echo 'ps3 specific code';
}
function funcXbox(){
echo 'xbox specific code';
}
I want to move towards a OOP approach in my code, I want to use objects as my data storage medium rather than arrays as I'm doing now, but I do sometimes need to define attributes in the code ahead of time, how could I do the above but with objects?
I would recommend for you to start by understanding polymorphism. This lecture should be good start.
When you are trying to create behavior, based on some flag, you should implement two classes with same interface:
class Xbox
{
private $displayName = 'XBox 360';
public function identify()
{
// Xbox-specific stuff
return ':::::::::::'. $this->displayName;
}
}
class PS3
{
private $displayName = 'Playstation 3';
public function identify()
{
// playstation-specific stuff
return '+++'. $this->displayName . '+++';
}
}
The two classes have method with same name that would do different things;
$platform = $_POST['platform'];
// classes in PHP are case-insensitive
// expected values would be: xbox, Xbox, ps3, pS3
if ( !class_exists($platform) )
{
echo "Platform '{$platform}' is not supported";
exit;
// since continuing at this point would cause a fatal error,
// better to simply exit
}
$object = new $platform;
echo $object->identify();
Basically, in this case you really do not care, which type of platform you are working with. All you need to know is that they both have same public interface. This is called "polymorphic behavior".
I'm going to work from a very naive OO version, to what is considered "good" OO code, using polymorphic behavior and avoiding global state.
1. Not polymorphic and has global static data
This is pretty bad because it is really just a wrapper object over procedural code. It needs a map of functions to call for each type of platform.
class Platform {
private static $platformArray = array(
'ps3' => array(
'displayName'=>'playstation 3',
'function'=>'funcPS3'
),
'xbox' => array(
'displayName'=>'Xbox',
'function'=>'funcXbox'
)
);
private $type;
public function __construct($type) {
if (!array_key_exists($type, self::$platformArray)) {
throw new Exception("Invalid Platform type $type" );
}
$this->type = $type;
}
public function printCode() {
// This was a question embedded within your question, you can use
// http://php.net/manual/en/function.call-user-func.php
// and pass an instance with a method name.
return call_user_func( array($this, self::$platformArray[$this->type]) );
}
private function funcPS3(){
echo 'ps3 specific code';
}
private function funcXbox(){
echo 'xbox specific code';
}
}
$plat = new Platform($_POST['platform']);
$plat->printCode();
2. Polymorphic... but it still uses global data
By creating a base class you can implement behavior in subclasses, creating separate class for each concern. The big problem here is that subclasses need to register with a global registry.
abstract class Platform {
abstract protected function getCode();
public function printCode() {
echo $this->getCode();
}
private function __construct() {} // so only factory can instantiate it
private static $platformArray = array();
public static function create($type) {
if (!array_key_exists($type, self::$platformArray)) {
throw new Exception("Invalid Platform type $type" );
}
return new self::$platformArray[$type];
}
public static function addPlatform($type, $ctor) {
if (!is_subclass_of($ctor, 'Platform')) {
throw new Exception("Invalid Constructor for Platform $ctor" );
}
self::$platformArray[$type] = $ctor;
}
}
class PlatformXBox extends Platform{
protected function getCode() {
return 'xbox specific code';
}
}
Platform::addPlatform('xbox', 'PlatformXBox');
class PlatformPs3 extends Platform {
protected function getCode() {
return 'ps3 specific code';
}
}
Platform::addPlatform('ps3', 'PlatformPs3');
$plat = Platform::create($_POST['platform']);
$plat->printCode();
3. Polymorphic, no global data
By putting your code into a namespace, you avoid the static code in the base class and avoid the dangers of mapping post parameters directly into classes.
namespace platform {
interface IPlatform {
public function getDisplayName();
public function getCode();
}
class PlatformFactory {
static public function create($platformType) {
$className = "\\platform\\$platformType";
if ( !is_subclass_of($className, "\\platform\\IPlatform") ){
return null;
}
return new $className;
}
}
class Xbox implements IPlatform {
public function getDisplayName(){
return 'xbox';
}
public function getCode(){
return 'xbox code';
}
}
class Ps3 implements IPlatform {
public function getDisplayName(){
return 'ps3';
}
public function getCode(){
return 'ps3 code';
}
}
}
Now you can use those classes like the following
$platform = platform\PlatformFactory::create('xbox');
echo $platform->getCode() ."\n" ;
$platform2 = platform\PlatformFactory::create('ps3');
echo $platform2->getDisplayName()."\n";
$noPlatform = platform\PlatformFactory::create('dontexist');
if ($noPlatform) {
echo "This is bad, plaftorm 'dontexist' shouldn't have been created";
} else {
echo "Platform 'dontexist' doesn't exist";
}
You might want to create a class called platforms and within the class a different method for each platform:
class platforms {
//Create your variables here, also called properties.
public $displayName;
//Create a function, also called a method for each platform you intent to use.
public function xboxPlatform(){
//Code comes here what you want to do.
}
}
Hope this helps.
I'm trying to whip up a skeleton View system in PHP, but I can't figure out how to get embedded views to receive their parent's variables. For example:
View Class
class View
{
private $_vars=array();
private $_file;
public function __construct($file)
{
$this->_file='views/'.$file.'.php';
}
public function set($var, $value=null)
{
if (is_array($var))
{
$this->_vars=array_merge($var, $this->_vars);
}
else
$this->_vars[$var]=$value;
return $this;
}
public function output()
{
if (count($this->_vars))
extract($this->_vars, EXTR_REFS);
require($this->_file);
exit;
}
public static function factory($file)
{
return new self($file);
}
}
test.php (top level view)
<html>
<body>
Hey <?=$name?>! This is <?=$adj?>!
<?=View::factory('embed')->output()?>
</body>
</html>
embed.php (embedded in test.php
<html>
<body>
Hey <?=$name?>! This is an embedded view file!!
</body>
</html>
Code:
$vars=array(
'name' => 'ryan',
'adj' => 'cool'
);
View::factory('test')->set($vars)->output();
Output:
Hey ryan! This is cool! Hey [error for $name not being defined]
this is an embedded view file!!
The problem is the variables I set in the top level view do not get passed to the embedded view. How could I make that happen?
So, I'm not exactly answering your question, but here's my super-simple hand-grown template system. It supports what you're trying to do, although the interface is different.
// Usage
$main = new SimpleTemplate("templating/html.php");
$main->extract($someObject);
$main->extract($someArray);
$main->name = "my name";
$subTemplate = new SimpleTemplate("templating/another.php");
$subTemplate->parent($main);
$main->placeholderForAnotherTemplate = $subTemplate->run();
echo $main; // or $main->run();
// html.php
<html><body><h1>Title <?= $name ?></h1><p><?= $placeHolderForAnotherTemplate ?></p></body></html>
<?php
// SimpleTemplate.php
function object_to_array($object)
{
$array = array();
foreach($object as $property => $value)
{
$array[$property] = $value;
}
return $array;
}
class SimpleTemplate
{
public $source;
public $path;
public $result;
public $parent;
public function SimpleTemplate($path=false, $source=false)
{
$this->source = array();
$this->extract($source);
$this->path($path);
}
public function __toString()
{
return $this->run();
}
public function extract($source)
{
if ($source)
{
foreach ($source as $property => $value)
{
$this->source[$property] = $value;
}
}
}
public function parent($parent)
{
$this->parent = $parent;
}
public function path($path)
{
$this->path = $path;
}
public function __set($name, $value)
{
$this->source[$name] = $value;
}
public function __get($name)
{
return isset($this->source[$name]) ? $this->source[$name] : "";
}
public function mergeSource()
{
if (isset($this->parent))
return array_merge($this->parent->mergeSource(), $this->source);
else
return $this->source;
}
public function run()
{
ob_start();
extract ($this->mergeSource());
include $this->path;
$this->result = ob_get_contents();
ob_end_clean();
return $this->result;
}
}
well, you create a new instance of the class, so there are no variables defined in the embedded template. you should try to copy the object, rather than creating a new one.
edit: I'm talking about the factory method
The main issue is that your views have no direct knowledge of each other. By calling this:
<?=View::factory('embed')->output()?>
in your "parent" view, you create and output a template that has no knowledge of the fact that it is inside another template.
There are two approaches I could recommend here.
#1 - Associate your templates.
By making your embedded templates "children" of a parent template, you could allow them to have access to the parent's variables at output() time. I utilize this approach in a View system I built. It goes something like this:
$pView = new View_Parent_Class();
$cView = new View_Child_Class();
$pView->addView($cView);
At $pview->render() time, the child view is easily given access to the parent's variables.
This method might require a lot of refactoring for you, so I'll leave out the dirty details, and go into the second approach.
#2 - Pass the parent variables
This would probably be the easiest method to implement given the approach you've taken so far. Add an optional parameter to your output method, and rewrite it slightly, like this:
public function output($extra_vars = null)
{
if (count($this->_vars))
extract($this->_vars, EXTR_REFS);
if (is_array($extra_vars)) extract($extra_vars, EXTR_REFS);
require($this->_file);
exit;
}
If you add a simple getter method as well:
public function get_vars()
{
return $this->_vars;
}
Then you can embed your files with what is effectively read-access to the parent's variables:
<?=View::factory('embed')->output($this->get_vars())?>
$this will be a reference to the current template, ie. the parent. Note that you can have variable name collisions via this method because of the two extract calls.
You could make your $_vars property static, not particularly elegant, but would work for what you are trying to achieve.
On a side note... your array_merge() in the set() function is wrong, swap your 2 variables around.