I'm new to PHP "class". I'm looking for a script that can help me create PHP templates, I found this one but I can't seem to understand what is happening here. Can anyone please explain in details. I will be very grateful. Thanks..
<?php
class skin {
var $filename;
public function __construct($filename) {
$this->filename = $filename;
}
public function mk($filename) {
$this->filename = $filename;
return $this->make();
}
public function make() {
global $CONF;
$file = sprintf('./'.$CONF['theme_path'].'/'.$CONF['theme_name'].'/html/%s.html', $this->filename);
$fh_skin = fopen($file, 'r');
$skin = #fread($fh_skin, filesize($file));
fclose($fh_skin);
return $this->parse($skin);
}
private function parse($skin) {
global $TMPL, $LNG;
$skin = preg_replace_callback('/{\$lng->(.+?)}/i', create_function('$matches', 'global $LNG; return $LNG[$matches[1]];'), $skin);
$skin = preg_replace_callback('/{\$([a-zA-Z0-9_]+)}/', create_function('$matches', 'global $TMPL; return (isset($TMPL[$matches[1]])?$TMPL[$matches[1]]:"");'), $skin);
return $skin;
}
}
?>
At the beginning of the class __construct() assigns the filename to a variable so to be used everywhere in the class ($filename).
Then there is a function that parse it parse().
But this function is nested into another function for technical purposes. the private keyword before parse() means it could be used by the class itself.
Hence, the function make() is responsible for making use of parse();
A final function mk() calls make().
parse() parses the template. Means it replaces the actual final parsed variables and data with the template special syntax (which you must refer to in your template file).
make() function gets the content of the file and printf() in it generates a usable full URL from the given params to it.
Related
So I'm using the following output method to output a file called profile-card.php inside my template directory.
public function output()
{
ob_start();
$profile = $this;
include $_SERVER['DOCUMENT_ROOT'] . '/wp-content/mu-plugins/s/templates/profile-card.php';
$output = ob_get_clean();
return $output;
}
So instead of having that method output all that code, I want to convert is to something like this where I can just call a string on the filename and it will render the template:
public function output()
{
return Template::load($this, 'profile-card');
}
What would be a good start/approach to tackle this? I have my Template::load() method setup with the following:
class Template
{
public function __construct()
{
// #todo: Initialize the object's methods
}
public static function init()
{
// #todo: Build out the template from the directory
}
public static function load()
{
// #todo: Add in more code in here
}
}
You could have a simple method taking a path and an array of parameters to pass to your view:
class Template
{
private const PATH_FROM_DOCUMENT_ROOT = '/wp-content/mu-plugins/s/templates';
public static function show(string $path, array $params = []): string
{
ob_start();
extract($params, EXTR_OVERWRITE);
require $_SERVER['DOCUMENT_ROOT'] . self::PATH_FROM_DOCUMENT_ROOT . $path . '.php';
return ob_get_clean();
}
}
Usage:
Template::show('profile-card', ['profile' => $profile]);
// ^ $this, in your case
Demo: https://3v4l.org/NEt4r (non-working there for obvious reasons, but shows a full sample)
Note: using a static method for this is probably not the greatest idea (I'd go as far as say it's to be avoided in general, but that's another discussion). I've kept this sample very basic to give you the general idea.
I am trying to get into the world of OOP and therefore building my first class out of some file handler functions. I am having a hard time understanding the whole idea of objects and methods, and I am not quite sure if I did it the right way, though the output is as expected.
I would appreciate your help with the following example. How could I add a method to it the right way?
class.File.php
class File {
public $file = '';
public $data = '';
public function __construct($file = '') {
$this->file = $file;
}
function put($create = false) {
// Check if file is writeable and put content, won't create new file unsless $create is set to true
if($create == false) {
is_writable($this->file) ? file_put_contents($this->file, $this->data, LOCK_EX) : exit;
} else {
file_put_contents($this->file, $this->data);
}
}
}
example call
$f = new File();
$f->file = "/www/htdocs/somehost/folder/data/helloworld.txt";
$f->data = "Hello world";
$f->put('true');
Object is the concrete entity of the real world. That has properties and behaviour (methods).
What are the properties of the concrete file? It is a name, permissions, a date of creation, etc. All properties should be hidden from real world.
What behaviour can the file have? Read content, rewrite content, append content, rename, delete, change permissions, etc. All that you do with the file. Note it is better to have two methods "rewrite content" and "append content" than one "put" with arguments. Because these are different actions on the file.
So let's write the class File.
class File
{
private $name;
private $permissions;
// name and permissions are mandatory because file cannot exist without these properties
public function __construct($name, $permissions)
{
$this->name = $name;
$this->permissions = $permissions;
}
public function read()
{
// you can check if the file exists
return file_get_contents($this->name);
}
// may also named `put`
public function write($content)
{
file_put_contents($this->name, $content);
}
public function append($content)
{
file_put_contents($this->name, $content, FILE_APPEND);
}
public function rename($name)
{
$this->name = $name;
}
}
Any checks (if file exists, if file is writeble) is concrete implementation, not about OOP.
PS
You can read this article http://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html about getters and setters.
I'm just very slowly starting to sink into object-oriented programming, so please be gentle on me.
I have a custom class for Smarty that was partially borrowed. This is how the only example reflects the basic idea of using it across my current project:
class Template {
function Template() {
global $Smarty;
if (!isset($Smarty)) {
$Smarty = new Smarty;
}
}
public static function display($filename) {
global $Smarty;
if (!isset($Smarty)) {
Template::create();
}
$Smarty->display($filename);
}
Then in the PHP, I use the following to display templates based on the above example:
Template::display('head.tpl');
Template::display('category.tpl');
Template::display('footer.tpl');
I made the following example of code (see below) work across universally, so I wouldn't repeat the above lines (see 3 previous lines) all the time in each PHP file.
I would just like to set, e.g.:
Template::defauls();
that would load:
Template::display('head.tpl');
Template::display('template_name_that_would_correspond_with_php_file_name.tpl');
Template::display('footer.tpl');
As you can see Template::display('category.tpl'); will always be changing based on the PHP file, which name is corresponded with the template name, meaning, if for example, PHP file is named stackoverflow.php then the template for it would be stackoverflow.tpl.
I've tried my solution that have worked fine but I don't like it the way it looks (the way it's structured).
What I did was:
Assigned in config a var and called it $current_page_name (that derives the current PHP page name, like this: basename($_SERVER['PHP_SELF'], ".php"); ), which returned, for e.g.: category.
In PHP file I used Template::defaults($current_page_name);
In my custom Smarty class I added the following:
public static function defaults($template) {
global $Smarty;
global $msg;
global $note;
global $attention;
global $err;
if (!isset($Smarty)) {
Templates::create();
}
Templates::assign('msg', $msg);
Templates::assign('note', $note);
Templates::assign('attention', $attention);
Templates::assign('err', $err);
Templates::display('head.tpl');
Templates::display($template . '.tpl');
Templates::display('footer.tpl');
}
Is there a way to make it more concise and well structured? I know about Code Review but I would like you, guys, to take a good look at it.
This looks like you haven't loaded Smarty, that's why the error happens. You need to start by including Smarty before the class starts. If you follow my other config suggestion you should start by including that one as well.
In you Template class, just add the following function:
function defaults() {
// Don't know if you need the assignes, havn't used Smarty, but if so, insert them here...
Template::display( Config::get('header_template') ); //header_template set in the Config file
Template::display( basename($_SERVER['PHP_SELF'], ".php") . '.tpl' );
Template::display( Config::get('footer_template') ); //footer_template set in the Config file
}
Now you should be able to use it in any file:
$template = new Template();
$template->defaults();
EDIT:
A singleton is in every sense the same as a global, that will keep your same problem.
But your problem is that if you try to use one of the Template's static functions you are in the "static" mode, which means the constructor have not been run. And Smarty has not been assigned. If you want to go this road, you can do one of two thinks:
Make the Template a real singleton, meaning set the constructor to private add a function getInstance, that returns a instance of the class, and then use that object to call the functions in it (which should not be static), or
Make all those static functions check if smarty is set, and if it's not, create a new instance of smarty, otherwise use the one that already is instantiated to run its function.
EDIT 2:
Here's the proper way to make a singleton:
class Singleton {
private static $instance = null;
// private static $smarty = null;
private function __construct() {
//self::$smarty = new Smarty();
}
public static function getInstance() {
if( self::$instance === null ) {
self::$instance = self();
}
return self::$instance;
}
public function doSomething() {
//self::$smarty->doSomething();
}
}
It's used like this:
$singleton = Singletong::getInstance();
$singleton->doSomething();
I commented out the things you probably want do to to make this a singleton wrapper around a singleton Smarty object. Hope this helps.
EDIT 3:
Here's a working copy of your code:
class Template {
private static $smarty_instance;
private static $template_instance;
private function Template() {
self::$smarty_instance = new Smarty();
$this->create();
}
public static function getInstance() {
if( ! isset( self::$template_instance ) ) {
self::$template_instance = new self();
}
return self::$template_instance;
}
private function create() {
self::$smarty_instance->compile_check = true;
self::$smarty_instance->debugging = false;
self::$smarty_instance->compile_dir = "/home/docs/public_html/domain.org/tmp/tpls";
self::$smarty_instance->template_dir = "/home/docs/public_html/domain.org";
return true;
}
public function setType($type) {
self::$smarty_instance->type = $type;
}
public function assign($var, $value) {
self::$smarty_instance->assign($var, $value);
}
public function display($filename) {
self::$smarty_instance->display($filename);
}
public function fetch($filename) {
return self::$smarty_instance->fetch($filename);
}
public function defaults($filename) {
global $user_message;
global $user_notification;
global $user_attention;
global $user_error;
self::$smarty_instance->assign('user_message', $user_message);
self::$smarty_instance->assign('user_notification', $user_notification);
self::$smarty_instance->assign('user_attention', $user_attention);
self::$smarty_instance->assign('user_error', $user_error);
self::$smarty_instance->assign('current_page', $filename);
self::$smarty_instance->display('head.tpl');
self::$smarty_instance->display($filename . '.tpl');
self::$smarty_instance->display('footer.tpl');
}
}
When using this function, you should use it like this:
$template = Template::getInstance();
$template->defaults($filename);
Try it now.
You can get current file name in your defaults() function. Use this piece of code:
$currentFile = $_SERVER['REQUEST_URI'];
$parts = explode('/', $currentFile);
$fileName = array_pop($parts);
$viewName = str_replace('.php', '.tpl', $fileName);
$viewName is the name that you need.
This is a quick wrapper I made for Smarty, hope it gives you some ideas
class Template extends Smarty
{
public $template = null;
public $cache = null;
public $compile = null;
public function var($name, $value, $cache)
{
$this->assign($name, $value, $cache);
}
public function render($file, $extends = false)
{
$this->prep();
$pre = null;
$post = null;
if ($extends)
{
$pre = 'extends:';
$post = '|header.tpl|footer.tpl';
}
if ($this->prep())
{
return $this->display($pre . $file . $post);
}
}
public function prep()
{
if (!is_null($this->template))
{
$this->setTemplateDir($this->template);
return true;
}
if (!is_null($this->cache))
{
$this->setCacheDir($this->cache);
}
if (!is_null($this->compile))
{
$this->setCompileDir($this->compile);
return true;
}
return false;
}
}
Then you can use it like this
$view = new Template();
$view->template = 'path/to/template/';
$view->compile = 'path/to/compile/'
$view->cache = 'path/to/cache';
$view->assign('hello', 'world');
// or
$view->var('hello', 'world');
$view->render('index.tpl');
//or
$view->render('index.tpl', true); // for extends functionality
I did this kinda fast, but just to show you the basic ways you can use smarty. In a more complete version you could probably want to check to see if compile dir is writable, or if file templates exist etc.
After trying for few days to solve this simple problem, I have finally came up with working and fully satisfying solution. Remember, I'm just a newby in object-oriented programming and that's the main reason why it took so long.
My main idea was not to use global $Smarty in my initial code that worked already fine. I like to use my Smarty as just simple as entering, e.g.: Template::assign('array', $array). To display defaults, I came up with the trivial solution (read my initial post), where now it can be just used Template::defaults(p()); to display or assign anything that is repeated on each page of your project.
For doing that, I personally stopped on the following fully working solution:
function p() {
return basename($_SERVER['PHP_SELF'], ".php");
}
require('/smarty/Smarty.class.php');
class Template
{
private static $smarty;
static function Smarty()
{
if (!isset(self::$smarty)) {
self::$smarty = new Smarty();
self::Smarty()->compile_check = true;
self::Smarty()->debugging = false;
self::Smarty()->plugins_dir = array(
'/home/docs/public_html/domain.com/smarty/plugins',
'/home/docs/public_html/domain.com/extensions/smarty');
self::Smarty()->compile_dir = "/home/docs/public_html/domain.com/cache";
self::Smarty()->template_dir = "/home/docs/public_html/domain.org";
}
return self::$smarty;
}
public static function setType($type)
{
self::Smarty()->type = $type;
}
public static function assign($var, $value)
{
self::Smarty()->assign($var, $value);
}
public static function display($filename)
{
self::Smarty()->display($filename);
}
public static function fetch($filename)
{
self::Smarty()->fetch($filename);
}
public static function defaults($filename)
{
Template::assign('current_page_name', $filename);
Template::display('head.tpl');
Template::display($filename . '.tpl');
Template::display('footer.tpl');
}
}
Please use it if you like it in your projects but leave comments under this post if you think I could improve it or you have any suggestions.
Initial idea of doing all of that was learning and exercising in writing a PHP code in object-oriented style.
Supposed there is a function in a \AW\Blog\Model\post.php.there is a function in it.
public function PreNext($type){
$id = $this->_data['post_id'];
$blog = Mage::getResourceModel('blog/post_collection');
$blog->getSelect()->where('post_id>?',$id);
return $blog->getFirstItem();
}
why it write $this->_data['post_id']; could i write it with another.
what are the four lines meaning which in the function within magento.? many thanks.
the post.php
class AW_Blog_Model_Post extends Mage_Core_Model_Abstract{
const NOROUTE_PAGE_ID = 'no-route';
protected function _construct(){
$this->_init('blog/post');
}
public function load($id, $field=null){
return $post = parent::load($id, $field);
}
public function noRoutePage(){
$this->setData($this->load(self::NOROUTE_PAGE_ID, $this->getIdFieldName()));
return $this;
}
public function getShortContent(){
$content = $this->getData('short_content');
if(Mage::getStoreConfig(AW_Blog_Helper_Config::XML_BLOG_PARSE_CMS)){
$processor = Mage::getModel('core/email_template_filter');
$content = $processor->filter($content);
}
return $content;
}
public function getPostContent(){
$content = $this->getData('post_content');
if(Mage::getStoreConfig(AW_Blog_Helper_Config::XML_BLOG_PARSE_CMS)){
$processor = Mage::getModel('core/email_template_filter');
$content = $processor->filter($content);
}
return $content;
}
public function loadByIdentifier($v) {
return $this->load($v, 'identifier');
}
}
This is code of a custom extension, so only people having this extension can know, what this post.php file contains, and whether you can get the value using other ways than $this->_data['post_id'].
If the extension uses standard Magento Getters/Setters, maybe $this->getPostId() may work, too.
The rest loads a collection of records having a post_id greater than $this->_data['post_id'], but returns only the first record found.
Update
The class you posted extends
Mage_Core_Model_Abstract
which in turn extends
Varien_Object
in a standard Magento OOB.
The Varien_Object class defines the standard getters/setters I was talking about, so yes, you can also use $this->getPostId() to get the value.
To understand how these getters/setters work, I'd recommend to check the Varien_Object and read about PHPs magic methods, like __call(), __get() and __set().
OK. here is what I'm trying to do:
class Image{
public $_image;
public $_extension;
public $_mime;
public $_size;
public $_location;
public $_description;
public function __construct($image, $location){
$this->_image = $image;
$this->_location = $location;
$this->_extension = getExtension();
$this->_mime = getMime();
$this->_size = getSize();
}
private functions fallow.....
}
But I keep getting an internal server error when I try to run it. When I comment out the method calls it works. So the question is can I call methods from inside the constructor or am I doing something wrong with the methods.
Do your functions getExtension, getMime and getSize exist? Are they methods on this class? If they are methods, they need to be called with $this->... as in
$this->_extension = $this->getExtension();
If they are not methods, and are functions, you need to make sure the files that contain/define them are loaded before you run the constructor.
Well ..this fragment of code will work as expected:
class Foo
{
protected $secret = null;
public function __construct( $data )
{
$this->secret = $this->makeSecret($data);
}
public function makeSecret( $data )
{
return md5( $data );
}
}
$bar = new Foo( 'lorem ipsum' );
That is not a problem.
But you should know, that is considered to be a bad practice - to do computation/work in the constructor. It makes that class practically untestable. Instead, if you need to perform some computation before "releasing" the object to the rest of the code, you should use a factory. Something along the lines of :
class ImageFactory
{
public function build($image, $location)
{
$instance = new Image($image, $location);
$instance->prepare();
return $instance;
}
}
The class would need some changes:
class Image
{
protected $_image; // you were leaking abstraction
protected $_extension;
protected $_mime;
protected $_size;
protected $_location;
protected $_description;
public function __construct($image, $location)
{
$this->_image = $image;
$this->_location = $location;
}
public function prepare()
{
$this->_extension = $this->getExtension();
$this->_mime = $this->getMime();
$this->_size = $this->getSize();
}
private functions fallow.....
}
Now when you need to create new object you do:
$factory = new ImageFactory;
$image = $factory->build( $file, '/uploads/' );
Of course the instance of ImageFactory can be reusable, and if all your images use the same $location, then you would pass that variable to factory at the initialization. And the factory would be able to "remember it" and pass to all the images it creates:
$factory = new ImageFactory('/uploads/');
$img1 = $factory->build( $file );
$img2 = $factory->build( $something_else );
This is actually how one should deal with creating multiple objects, which all need access to same DB connection instance.
Yes, you can call methods from within the constructor. Remember that the __construct() magic method was implemented in PHP 5. Prior to that, you created a function named the same as your class which acted as your constructor so depending on your PHP version, that could be a problem.
Additionally, the function calls you are making, are they in the class or external? If they are inside the class you need to call them this way:
$this->_extension = $this->getExtension();
You didnt specified what error you are expiriencing clearly. But try calling you class methods even inside the class using this keyword, otherwise it would not work:
public function __construct($image, $location)
{
$this->_image = $image;
$this->_location = $location;
$this->_extension = $this->getExtension();
$this->_mime = $this->getMime();
$this->_size = $this->getSize();
}
Would be a better idea to post your code for the methods you wrote. There could be something wrong within them as well. Possibly forgetting to return a result or something...