I have created a PHP class and I want to access its methods from a function in another file. Below is my code:
include_once PLUGIN_DIR_PATH."/classes/Brands.php";
function create_tables(){
$brand_obj = new Brands;
$brand_obj->create_brand_table();
}
function delete_tables() {
$brand_obj = new Brands;
$brand_obj->delete_brand_table();
}
Is it possible to create the object only once and then reuse it in every function? Right now, I am creating object in every function which is not a good practice.
You can use the global keyword to use a variable created outside a function inside a function:
global $object
code example :
include_once PLUGIN_DIR_PATH."/classes/Brands.php";
$brand_obj = new Brands;
function create_tables(){
global $brand_obj;
$brand_obj->create_brand_table();
}
function delete_tables() {
global $brand_obj;
$brand_obj->delete_brand_table();
}
I'm going to assume your actual functions do more than what you posted, otherwise you don't really need functions and can simply do this:
$brand_obj = new Brands;
$brand_obj->create_brand_table();
$brand_obj->delete_brand_table();
Otherwise, you can make a class and inject the Brands dependency into one of its properties via its constructor:
class TablesManager
{
private $brands;
public function __construct(Brands $brands)
{
$this->brands = $brands;
}
public function create_tables(): void
{
$this->brands->create_brand_table();
}
public function delete_tables(): void
{
$this->brands->delete_brand_table();
}
}
Usage:
$brands = new Brands();
$tables_manager = new TablesManager($brands);
$tables_manager->create_tables();
$tables_manager->delete_tables();
Note: calling a class SomethingManager is sometimes considered bad practice / a sign that the class does too many things. Consider (at least) giving it a more accurate name for your needs.
Demo: https://3v4l.org/iTmY6
Non-OOP alternative
function create_tables(Brands $brand_obj): void {
$brand_obj->create_brand_table();
}
function delete_tables(Brands $brand_obj): void {
$brand_obj->delete_brand_table();
}
$brand_obj = new Brands();
create_tables($brand_obj);
delete_tables($brand_obj);
Here's the code (didn't include namespaces, routing):
class OneController extends Controller{
public $variable = "whatever";
public function changeVariableAction(){
$this->variable = "whenever";
// any code...
$this->redirectToRoute("class_two_route_name");
}
}
use AppBundle\Controller\OneController;
class Two{
public function otherFunctionAction(){
$reference = new One();
return new Response($reference->variable);
}
}
Why do I see "whatever" instead "whenever"? I know there is no line in the code executing changeVariableAction() but it is being executed when sb enters the route matching this action in class One ???
EDIT:
When I write the scheme outside SF3 I'm OK.
class One{
public $variable = "whatever";
public function changeVariable(){
$this->variable = "whenever";
}
}
class Two{
public function otherFunction(){
$reference = new One();
$reference->changeVariable();
echo $reference->variable;
}
}
$reference2 = new Two();
$reference2->otherFunction();
You are seeing "Whatever" instead of "Whenever" because of this line:
new One();
By calling "new One();" you are creating a new instance of the class "OneController" thus, it will set its default value "whatever" as the function "changeVariableAction" is not being called in your new instance $reference.
After research I can see that in SF (as it is a framework) we don't treat Action functions as typical functions (it's sth about http etc.) so we cannot execute them in another class. What's more, the whole code inside Action function doesn't influence the code outside the Action function. The only way to get new property value is to send them via argument in url (I don't think we want that) or send to db and retrieve it from database in another class.
Here's the proof:
class FirstController extends Controller{
public $variable = "whatever";
/**
* #Route("/page")
*/
public function firstAction(){
$this->variable = "whenever";
return $this->redirectToRoute("path");
}
}
class SecondController{
/**
* #Route("/page/page2", name = "path")
*/
public function secondAction(){
$reference = new FirstController();
$reference->firstAction();
return new Response($reference->variable);
}
}
This code gives an error: Call to a member function get() on null.
When I delete line $reference->firstAction(); there is no error and "whatever" shows up (so the original).
I have following setup.
index.php
require_once "common.php";
...
common.php
...
$obj = new MyClass;
require_once "config.php"
...
config.php
...
require_once "settings.php";
...
settings.php
$obj->dostuff = true;
...
When i open index.php i get: Strict Standards: Creating default object from empty value in settings.php on 3
If i put $obj->dostuff = true; inside config.php it does not produce error message.
Can someone explain why i get this error? I am not asking how to fix it just understand why.
EDIT: My bad i had 2 config.php classes for each part of site and i only changed something in one of them leaving old include order in another now it works fine after it all loads in correct order.
It looks a scope issue. In settings.php, the $obj is not accessible. PHP is creating new one from standard class, and giving you a warning. You can confirm it by putting
echo get_class($obj);
in Your settings.php, just after the line that is producing the error. If it echos "StdClass", then that is the case.
Are You sure the $obj is not created within a function/method ?
If $obj is meant to be a system wide globally accessible object, you can you use the singleton pattern to access from anywhere:
class MyClass
{
protected static $_instance;
static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new self();
}
return self::$_instance;
}
}
You can then create your methods in this class. To get the object itself simply call:
$obj = MyClass::getInstance();
Additionally, if you just want to call one of its methods but theres no need to return anything:
MyClass::getInstance()->objectMethod();
I find this to be a very efficient way to organize integral singleton based system wide operations.
In practice, my project uses this to get configuration from anywhere in the system:
class syConfig
{
protected static $_instance;
private $_config;
static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new self();
}
return self::$_instance;
}
public function load($xmlString)
{
$xml = simplexml_load_string($xmlString);
$this->_config = $xml;
}
public function getConfig()
{
return $this->_config;
}
}
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.
consider the following code scenario:
<?php
//widgetfactory.class.php
// define a class
class WidgetFactory
{
var $oink = 'moo';
}
?>
<?php
//this is index.php
include_once('widgetfactory.class.php');
// create a new object
//before creating object make sure that it already doesn't exist
if(!isset($WF))
{
$WF = new WidgetFactory();
}
?>
The widgetfactory class is in widgetfactoryclass.php file, I have included this file in my index.php file, all my site actions runs through index.php, i.e. for each action this file gets included, now I want to create object of widgetfactory class ONLY if already it doesn't exist. I am using isset() for this purpose, is there any other better alternative for this?
Using globals might be a way to achieve this. The common way to do this are singleton instances:
class WidgetFactory {
private static $instance = NULL;
static public function getInstance()
{
if (self::$instance === NULL)
self::$instance = new WidgetFactory();
return self::$instance;
}
/*
* Protected CTOR
*/
protected function __construct()
{
}
}
Then, later on, instead of checking for a global variable $WF, you can retrieve the instance like this:
$WF = WidgetFactory::getInstance();
The constructor of WidgetFactory is declared protected to ensure instances can only be created by WidgetFactory itself.
This should do the job:
if ( ($obj instanceof MyClass) != true ) {
$obj = new MyClass();
}