Logger Object - Variable Scope - access to Class/Function/etc - php

So I want to find the best method of allowing my Logger class to access any part of the script either another class/function/etc... How would I do this? How do I make it global.
Could I do something like this:
Logger::info('Add message like this?');
Calling script: calling.php
require_once('Logger.class.php'); // Just adding the class initializes the Logger Object
require_once('Another.class.php');
require_once('Functions.php');
$logEntry->info("Log info");
$logEntry->error("Log error");
$logEntry->warning("Log warning");
$logEntry->notice("Log notice");
$logEntry->enableDebug(); // prints debug to log
$logEntry->debug("Log debug enabled");
$logEntry->disableDebug();
$logEntry->debug("Log debug disabled"); // will not print to log
$another_obj = new Another(); // want the Logger to have access inside this class
More(); // want the Logger to have access inside this function
Another.class.php
class Another {
private $var;
// I want to add the Logger here
$logEntry->info("Another Log");
// More code here it's just an example ...
}
Functions.php
function More() {
// I want to add the Logger here
$logEntry->info("More Log");
}
Here is the Logger.class.php script
<?php
//Define Constants
define("LOG_FILE_DIRECTORY", "/var/www/logs");
ini_set("memory_limit","128M"); // Logger class is taking up memory
class Logger {
private $log_file_directory = LOG_FILE_DIRECTORY;
private $first_run; // Flag to add line break at the beginning of script execution
private $calling_script; // Base name of the calling script
private $log_file; // log file path and name
private $log_entry; // information to be logged
private $log_level; // Log severity levels: error, warning, notice, debug, info
private $fh; // File handle
private $file_name; // File path and name
private $file_parts; // Array of $file_name
private $script_name; // Script Name
private $script_parts; // Array of $script_name
private $line_number_arr; // Line number of where the logging event occurred
private $debug_flag; // Set to true if you want to log your debug logger
function __construct() {
$this->first_run = true;
$this->debug_flag = false;
$this->calling_script = '';
$this->log_file = '';
$this->log_entry = '';
$this->log_level = '';
$this->fh = '';
$this->file_name = '';
$this->file_parts = '';
$this->script_name = '';
$this->script_parts = '';
$this->line_number_arr = '';
}
/**
* #enableDebug
*/
public function enableDebug() {
$this->debug_flag = true;
}
/**
* #disbaleDebug
*/
public function disableDebug() {
$this->debug_flag = false;
}
/**
* #info
*/
public function info($message) {
$this->log_level = 'info';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #error
*/
public function error($message) {
$this->log_level = 'error';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #warning
*/
public function warning($message) {
$this->log_level = 'warning';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #notice
*/
public function notice($message) {
$this->log_level = 'notice';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #debug
* must add the below to the script you wish to debug
* define("DEBUG", true); // true enables, false disables
*/
public function debug($message) {
if($this->debug_flag) {
$this->log_level = 'debug';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
}
private function addEntry($message) {
$this->calling_script = $this->getScriptBaseName();
$this->log_file = $this->log_file_directory."/".$this->calling_script.".log";
$this->fh = fopen($this->log_file, 'a') or die("Can't open log file: ".$this->log_file);
if($this->first_run) {
$this->log_entry = "\n[" . date("Y-m-d H:i:s", mktime()) . "][line:".$this->line_number_arr[0]['line']."|".$this->log_level."]:\t".$message."\n";
} else {
$this->log_entry = "[" . date("Y-m-d H:i:s", mktime()) . "][line:".$this->line_number_arr[0]['line']."|".$this->log_level."]:\t".$message."\n";
}
fwrite($this->fh, $this->log_entry);
fclose($this->fh);
$this->first_run = false;
}
/**
* return the base name of the calling script
*/
private function getScriptBaseName() {
$this->file_name = $_SERVER["SCRIPT_NAME"];
$this->file_parts = explode('/', $this->file_name);
$this->script_name = $this->file_parts[count($this->file_parts) - 1];
$this->script_parts = explode('.', $this->script_name);
// If file doesn't exists don't add line break
if(!file_exists($this->script_parts[0].".log")) {
$this->first_run = false;
}
return $this->script_parts[0];
}
}
// Start log instance
$logEntry = new Logger();
?>

You could implement it as a class full of static functions, e.g.:
class Logger {
protected $logfile = null;
public static load() {
self::$logfile = fopen('error.log', 'a');
}
public static info($msg) {
if(self::$logfile == null)
self::load();
fwrite(self::$logfile, $msg);
}
}
and then use it with Logger::info("My message..");. Another common method of doing this is using a singleton class, so that you can only create a single object of "Logger" and retrieve it using e.g. Logger::getInstance()->logInfo("My message");.
In your case (as you already implemented Logger as a normal class) I would make the __construct private and implement the class as a singleton. It's not possible to make $logEntry globally available. Your code would become:
<?php
//Define Constants
define("LOG_FILE_DIRECTORY", "/var/www/logs");
ini_set("memory_limit","128M"); // Logger class is taking up memory
class Logger {
private $log_file_directory = LOG_FILE_DIRECTORY;
private $first_run; // Flag to add line break at the beginning of script execution
private $calling_script; // Base name of the calling script
private $log_file; // log file path and name
private $log_entry; // information to be logged
private $log_level; // Log severity levels: error, warning, notice, debug, info
private $fh; // File handle
private $file_name; // File path and name
private $file_parts; // Array of $file_name
private $script_name; // Script Name
private $script_parts; // Array of $script_name
private $line_number_arr; // Line number of where the logging event occurred
private $debug_flag; // Set to true if you want to log your debug logger
private static $instance = null;
private function __construct() {
$this->first_run = true;
$this->debug_flag = false;
$this->calling_script = '';
$this->log_file = '';
$this->log_entry = '';
$this->log_level = '';
$this->fh = '';
$this->file_name = '';
$this->file_parts = '';
$this->script_name = '';
$this->script_parts = '';
$this->line_number_arr = '';
}
public static function getInstance() {
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
}
return self::$instance;
}
/**
* #enableDebug
*/
public function enableDebug() {
$this->debug_flag = true;
}
/**
* #disbaleDebug
*/
public function disableDebug() {
$this->debug_flag = false;
}
/**
* #info
*/
public function info($message) {
$this->log_level = 'info';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #error
*/
public function error($message) {
$this->log_level = 'error';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #warning
*/
public function warning($message) {
$this->log_level = 'warning';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #notice
*/
public function notice($message) {
$this->log_level = 'notice';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
/**
* #debug
* must add the below to the script you wish to debug
* define("DEBUG", true); // true enables, false disables
*/
public function debug($message) {
if($this->debug_flag) {
$this->log_level = 'debug';
$this->line_number_arr = debug_backtrace();
$this->addEntry($message);
}
}
private function addEntry($message) {
$this->calling_script = $this->getScriptBaseName();
$this->log_file = $this->log_file_directory."/".$this->calling_script.".log";
$this->fh = fopen($this->log_file, 'a') or die("Can't open log file: ".$this->log_file);
if($this->first_run) {
$this->log_entry = "\n[" . date("Y-m-d H:i:s", mktime()) . "][line:".$this->line_number_arr[0]['line']."|".$this->log_level."]:\t".$message."\n";
} else {
$this->log_entry = "[" . date("Y-m-d H:i:s", mktime()) . "][line:".$this->line_number_arr[0]['line']."|".$this->log_level."]:\t".$message."\n";
}
fwrite($this->fh, $this->log_entry);
fclose($this->fh);
$this->first_run = false;
}
/**
* return the base name of the calling script
*/
private function getScriptBaseName() {
$this->file_name = $_SERVER["SCRIPT_NAME"];
$this->file_parts = explode('/', $this->file_name);
$this->script_name = $this->file_parts[count($this->file_parts) - 1];
$this->script_parts = explode('.', $this->script_name);
// If file doesn't exists don't add line break
if(!file_exists($this->script_parts[0].".log")) {
$this->first_run = false;
}
return $this->script_parts[0];
}
}
?>
You would then use your class globally using Logger::getInstance()->info($msg);

Related

How to add auto complete to classes instantiated with custom function?

I instantiate my classes through custom functions.
The classes are in app/code/core/ e.g.
app/code/core/Property/Helper/Property.php
require_once("Core/Helper.php");
class Property_Helper_Property extends Core\Helper
{
public function __construct($con)
{
parent::__construct($con);
}
public function test()
{
return "hello";
}
}
app/code/core/Core/Helper.php
<?php
namespace Core;
abstract class Helper
{
protected $con;
public function __construct($con)
{
$this->con = $con;
}
}
I can call the method test() of the class Property_Helper_Property from any file like this:
require_once 'app/Main.php'; // always needed
Main::getHelper("Property/Property")->test();
app/Main.php
This file contains the final class "Main" which has the static method getHelper
if (!defined('DS')) define('DS', DIRECTORY_SEPARATOR);
if (!defined('PS')) define('PS', PATH_SEPARATOR);
if (!defined('BP')) define('BP', dirname(dirname(__FILE__)));
/**
* Set include path
*/
Main::register('original_include_path', get_include_path());
$paths = array();
$paths[] = Main::CORE_PATH;
$paths[] = Main::LOCAL_PATH;
$paths[] = BP . DS . 'lib';
$paths[] = BP . DS . 'inc';
$appPath = implode(PS, $paths);
set_include_path($appPath . PS . Main::registry('original_include_path'));
final class Main
{
const CORE_PATH = BP . DS . 'app' . DS . 'code' . DS . 'core';
const LOCAL_PATH = BP . DS . 'app' . DS . 'code' . DS . 'local';
/**
* Registry collection
*
* #var array
*/
static private $_registry = array();
public static function getDbConnection()
{
return self::registry("db_connection");
}
/**
* Register a new variable
*
* #param string $key
* #param mixed $value
* #param bool $graceful
*/
public static function register($key, $value, $graceful = false)
{
if (isset(self::$_registry[$key])) {
if ($graceful) {
return;
}
self::throwException('Main registry key "'.$key.'" already exists');
}
self::$_registry[$key] = $value;
}
/**
* Unregister a variable from register by key
*
* #param string $key
*/
public static function unregister($key)
{
if (isset(self::$_registry[$key])) {
if (is_object(self::$_registry[$key]) && (method_exists(self::$_registry[$key], '__destruct'))) {
self::$_registry[$key]->__destruct();
}
unset(self::$_registry[$key]);
}
}
/**
* Retrieve a value from registry by a key
*
* #param string $key
* #return mixed
*/
public static function registry($key)
{
if (isset(self::$_registry[$key])) {
return self::$_registry[$key];
}
return null;
}
public static function getHelper($name)
{
$classPath = self::getClassPath($name, "Helper");
if (!$classPath) { return false; }
$fullClassPath = self::getFullClassPath($classPath);
if (!$fullClassPath) { return false; }
$obj = self::getClassInstance($fullClassPath, $classPath);
if (!$obj) { return false; }
return $obj;
}
public static function getModel($name)
{
$classPath = self::getClassPath($name, "Model");
if (!$classPath) { return false; }
$fullClassPath = self::getFullClassPath($classPath);
if (!$fullClassPath) { return false; }
$obj = self::getClassInstance($fullClassPath, $classPath);
if (!$obj) { return false; }
return $obj;
}
private function getClassInstance($fullClassPath, $classPath)
{
if (!$classPath) { return false; }
require_once($fullClassPath);
$className = str_replace("/", "_", $classPath);
if (class_exists($className)) {
return new $className(self::getDbConnection());
} else {
return false;
}
}
private function getFullClassPath($classPath)
{
$modulPaths = [self::CORE_PATH, self::LOCAL_PATH];
return self::checkIfFileExistInModule($modulPaths, $classPath);
}
private function getClassPath($modelName, $identifier="Model")
{
if (strpos($modelName, '/') === false) { return false; }
if (substr_count($modelName, "/") == 1) {
$exp = explode("/", $modelName);
return $exp[0] . "/$identifier/" . $exp[1];
} else {
return false;
}
}
private function checkIfFileExistInModule($modulPaths, $modelname)
{
foreach($modulPaths as $path) {
$path = $path . DS . $modelname . ".php";
if (file_exists($path)) {
return $path;
}
}
return "0";
}
}
This works just fine... now the actual question.
If I am writing...
$obj = Main::getHelper("Property/Property");
$obj->
...then my IDE (NetBeans) does not auto suggest the public methods/properties which I can use.
Is there a way to "teach" my logic to add auto suggestion / auto completion so that it automatically shows all public methods/properties available in the object?
You need to use phpdoc blocks. Pretty sure they are supported by NetBeans:
/** #var Property_Helper_Property $obj */
$obj = Main::getHelper("Property/Property");
From that point forward auto-completion and static analysis will work, since it will be understood that $obj will be an instance of Property_Helper_Property.

Load database value with function

I didn't see any topic like this so I posted a new one.
First of all, I'm sorry about my bad English.
I want an public function who can storage in a global variable a value from database, and with that value the php script select the website templates. Can you help me?
private $page_title = null;
private $body_title = null;
private $body_content = null;
private $template = null;
private $settings = array();
private $file_template = 'content';
private $path = 'templates/';
private $parse_page = false;
global $template2;
public function GetHASH()
{
return $this->hash;
}
function Tema()
{
global $db,$user_class;
if($user_class->authorized)
{
$row = $db->sql("SELECT * FROM `ucp_users` WHERE `user_hash` = '".$this->GetHASH()."'");
$array = $db->get_array($row);
$template2 = $array['template'];
}
else
{
$template2 = '1';
}
return $template2;
}
function __construct() {
switch($template2) {
default:
{
$template_name = 'dsa/';
$template2 = 1;
}
break;
case 0: $template_name = 'lsrp/';
break;
case 1: $template_name = 'dsa/';
break;
}
$this->path = $this->path.''.$template_name;
}
Thank you.
When you instantiate your class, the constructor is called, at this time your tema method has not been invoked yet since it cannot be called without an instance. One solution would be to call your tema method in your constructor like this :
function __construct() {
$template2 = this->tema();
switch($template2) {
default:
{
$template_name = 'dsa/';
$template2 = 1;
}
break;
case 0: $template_name = 'lsrp/';
break;
case 1: $template_name = 'dsa/';
break;
}
$this->path = $this->path.''.$template_name;
}

Magento 2: Argument 1 passed to Controller::__construct() must be an instance of ..\..\..\Action\Context, instance of ..\..\..\ObjectManager given

I am getting the following error when I try to run my Magento 2 module:
Fatal error: Uncaught TypeError: Argument 1 passed to
MyModule\Service\Controller\Module\Version::__construct() must be an
instance of Magento\Framework\App\Action\Context, instance of
Magento\Framework\ObjectManager\ObjectManager given, called in
/srv/www/vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php
on line 93 and defined in
/srv/www/app/code/MyModule/Service/Controller/Module/version.php:16
This happens after I compile running this command:
magento setup:di:compile
I've read a lot of posts that suggest clearing out the /var/di and /var/generation folders and while that fixes the error, that only works in a development environment. I cannot clear those folders in a production environment as it will cause the other extensions to break.
This is my Controller:
namespace MyModule\Service\Controller\Module;
class Version extends \MyModule\Service\Controller\Module {
protected $resultJsonFactory;
protected $objectManager;
protected $helper = null;
protected $config = null;
/**
* #param \Magento\Framework\App\Action\Context $context
* #param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
* #param \MyModule\Service\Helper\Data $helper
*/
public function __construct(
\Magento\Framework\App\Action\Context $context,
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
\MyModule\Service\Helper\Data $helper
) {
$this->resultJsonFactory = $resultJsonFactory;
$this->helper = $helper;
$this->objectManager = $context->getObjectManager();
parent::__construct($context);
parent::initParams();
}
/**
* #return \Magento\Framework\Controller\Result\Json
*/
public function execute()
{
$result = $this->resultJsonFactory->create();
$data = new \stdClass();
$data->magentoVersion = (string) $this->objectManager->get('\Magento\Framework\App\ProductMetadata')->getVersion();
$data->phpVersion = (string) phpversion();
$data->moduleEnabled = $this->helper->getConfig()['enabled'];
$data->apiVersion = "2.0";
return $result->setData($data);
}
}
And this is what I have for MyModule\Service\Controller
namespace MyModule\Service\Controller;
abstract class Module extends \Magento\Framework\App\Action\Action {
protected $pageSize = null;
protected $pageNum = 0;
protected $startDate = null;
protected $endDate = null;
protected $sortDir = 'asc';
protected $filterField = 'created_at';
protected $id = null;
protected $helper;
protected function initParams() {
if ((bool) $pageSize = $this->getRequest()->getParam('page_size')) {
$this->pageSize = $pageSize;
}
if ((bool) $pageNum = $this->getRequest()->getParam('page_num')) {
$this->pageNum = $pageNum;
}
if ((bool) $startDate = $this->getRequest()->getParam('start_date')) {
$this->startDate = $startDate;
if ((bool) $endDate = $this->getRequest()->getParam('end_date')) {
$this->endDate = $endDate;
} else {
$this->endDate = date('Y-m-d');
}
} elseif ((bool) $updatedStartDate = $this->getRequest()->getParam('updated_start_date')) {
$this->filterField = 'updated_at';
$this->startDate = $updatedStartDate;
if ((bool) $updatedEndDate = $this->getRequest()->getParam('updated_end_date')) {
$this->endDate = $updatedEndDate;
} else {
$this->endDate = date('Y-m-d');
}
}
if ((bool) $sortDir = $this->getRequest()->getParam('sort_dir')) {
$this->sortDir = $sortDir;
}
if ((bool) $id = $this->getRequest()->getParam('id')) {
$this->id = $id;
}
}
protected function isEnabled() {
return $this->helper->getConfig()['enabled'];
}
protected function isAuthorized() {
$token = $this->helper->getConfig()['security_token'];
$authToken = (isset($_SERVER['HTTP_X_TOKEN']) ? $_SERVER['HTTP_X_TOKEN'] : $_SERVER['X_TOKEN']);
if (empty($authToken)) {
return false;
}
if (trim($token) != trim($authToken)) {
$this->helper->log('feed request with invalid security token');
return false;
}
return true;
}
}
If you are in production mode, change to maintenance mode, clear var/cache,var/generation and re-run compile.
And don't forgot disable maintenance mode
Try to remove an old generated files using rm -rf var/generation/* command from the magento root directory, because magento pre-generates all class files with their constructors. Generated classes extends the original classes and used by magento to call a plugins.

Fatal error: Namespace declaration statement has to be the very first statement in the script

I have error, while trying to include this file. I have a error
Fatal error: Namespace declaration statement has to be the very first statement in the script in /var/www/html/phpinstagram/phpinstagram/Instagram.php on line 2
Code:
<?php
namespace phpinstagram;
class Instagram {
/*
* #var array
*/
protected $_commands = array();
/*
* #var \phpinstagram\Instagram\Client\Communication
*/
protected $_communication;
/*
* #var \phpinstagram\Instagram\Api\Feed
*/
public $feed;
/*
* #var \phpinstagram\Instagram\Api\Auth
*/
public $auth;
public function __construct() {
$this->feed = new \phpinstagram\Instagram\Api\Feed();
$this->feed->setInstagram($this);
$this->auth = new \phpinstagram\Instagram\Api\Auth();
$this->auth->setInstagram($this);
$this->_communication = new \phpinstagram\Instagram\Client\Communication();
}
public function addCommand(\phpinstagram\Instagram\Command\ICommand $cmd) {
$this->_commands[] = $cmd;
}
public function run() {
$executed = array();
//$cookieJar = null;
foreach ($this->_commands as $utcmd) {
if ($utcmd instanceof \phpinstagram\Instagram\Command\ICommand) {
/*
* #var \phpinstagram\Instagram\Command\ICommand
*/
$cmd = $utcmd;
//if (!is_null($cookieJar))
// $this->_communication->getClient()->setCookieJar($cookieJar);
foreach ($cmd->dependsOn() as $dependency) {
foreach ($executed as $previous) {
if (get_class($previous) == '\Instagram\Command\\' . $dependency) {
break 2;
}
}
throw new \phpinstagram\Instagram\Command\DependencyException(
'Command ' . get_class($cmd) . " depends on $dependency but it never was executed!\n\n"
);
}
$cmd->setCommunication($this->_communication);
$cmd->validate();
//echo get_class($cmd)." is valid. executing...\n\n";
$cmd->exec();
$executed[] = $cmd;
// reset parameters
$this->_communication->getClient()->resetParameters();
//$cookieJar = $this->_communication->getClient()->getCookieJar();
}
}
// reset commands
$this->_commands = array();
}
}
You probably use UTF-8 encoding with Byte Order Mark (see https://en.wikipedia.org/wiki/Byte_order_mark). Try to convert your files to "UTF-8 without BOM".

get set properties in php

I'm from the C# environment and I'm starting to learn PHP in school.
I'm used to set my properties in C# like this.
public int ID { get; set; }
What's the equivalent to this in php?
Thanks.
There is none, although there are some proposals for implementing that in future versions.
For now you unfortunately need to declare all getters and setters by hand.
private $ID;
public function setID($ID) {
$this->ID = $ID;
}
public function getID() {
return $this->ID;
}
for some magic (PHP likes magic), you can look up __set and __get magic methods.
Example
class MyClass {
private $ID;
private function setID($ID) {
$this->ID = $ID;
}
private function getID() {
return $this->ID;
}
public function __set($name,$value) {
switch($name) { //this is kind of silly example, bt shows the idea
case 'ID':
return $this->setID($value);
}
}
public function __get($name) {
switch($name) {
case 'ID':
return $this->getID();
}
}
}
$object = new MyClass();
$object->ID = 'foo'; //setID('foo') will be called
Thanks for your answers everyone. It helped me to create something like this:
In my parent class:
public function __get($name){
if (ObjectHelper::existsMethod($this,$name)){
return $this->$name();
}
return null;
}
public function __set($name, $value){
if (ObjectHelper::existsMethod($this,$name))
$this->$name($value);
}
ObjectHelper::existsMethod is a method which just check if given protected method exists.
private $_propertyName = null;
protected function PropertyName($value = ""){
if (empty($value)) // getter
{
if ($this-> _propertyName != null)
return $this->_propertyName;
}
else // setter
{
$this-> _propertyName = $value;
}
return null;
}
So I can use something like this in any class:
$class = new Class();
$class->PropertyName = "test";
echo $class->PropertyName;
I was inspired by C# :)
What do you think about this, guys?
Here is my ObjectHelper if someone would like to use it:
namespace Helpers;
use ReflectionMethod;
class ObjectHelper {
public static function existsMethod($obj, $methodName){
$methods = self::getMethods($obj);
$neededObject = array_filter(
$methods,
function ($e) use($methodName) {
return $e->Name == $methodName;
}
);
if (is_array($neededObject))
return true;
return false;
}
public static function getMethods($obj){
$var = new \ReflectionClass($obj);
return $var->getMethods(ReflectionMethod::IS_PROTECTED);
}
}
Mchi is right, but there is another way of doing it by using single function
private $ID;
public function ID( $value = "" )
{
if( empty( $value ) )
return $this->ID;
else
$this->ID = $value;
}
But yeah this approach is pretty much inline with what you do in c#. but this is only an alternative
Or try using php's __set and __get in your class more info here
http://php.net/manual/en/language.oop5.overloading.php
Another exampled using Variable function name
class MyClass {
private $ID;
protected $ID2;
private function setID($ID) {
$this->ID = $ID;
}
private function getID() {
return $this->ID;
}
private function setID2($ID2) {
$this->ID2 = $ID2;
}
private function getID2() {
return $this->ID2;
}
public function __set($name,$value) {
$functionname='set'.$name;
return $this->$functionname($value);
}
public function __get($name) {
$functionname='get'.$name;
return $this->$functionname();
}
}
$object = new MyClass();
$object->ID = 'foo'; //setID('foo') will be called
$object->ID2 = 'bar'; //setID2('bar') will be called
private $ID;
public function getsetID($value = NULL)
{
if ($value === NULL) {
return $this->ID;
} else {
$this->ID = $value;
}
}
I know I am a bit late to the party on this question, but I had the same question/thought myself. As a C# developer who does PHP, when the job requires, I want to have a simple way to create properties just I would be able to in C#.
I whipped up a first draft this afternoon which allows you to create the backing fields and specify their accessors or have pure accessors with no backing field. I will update my answer as the code evolves and provide a link when I get it to the state where it can be imported as a composer package.
For simplicity, I created the functionality as a PHP trait so you can drop it in to any class you want instead of having to extend a base class. Eventually I hope to extend this functionality to discern between external public calls to the properties and protected/private calls.
Here is the code for the trait itself:
trait PropertyAccessorTrait
{
private static $__propertyAccessors = [];
/* #property string $__propertyPrefix */
public function __get($name)
{
$this->__populatePropertyAcessors($name);
return $this->__performGet($name);
}
public function __set($name, $value)
{
$this->__populatePropertyAcessors($name);
$this->__performSet($name, $value);
}
public function __isset($name)
{
// TODO: Implement __isset() method.
}
public function __unset($name)
{
// TODO: Implement __unset() method.
}
protected function __getBackingFieldName($name)
{
if (property_exists(self::class, '__propertyPrefix')) {
$prefix = $this->__propertyPrefix;
} else {
$prefix = '';
}
return $prefix . $name;
}
protected function __canget($name)
{
$accessors = $this->__getPropertyAccessors($name);
return $accessors !== null && isset($accessors['get']);
}
protected function __canset($name)
{
$accessors = $this->__getPropertyAccessors($name);
return $accessors !== null && isset($accessors['set']);
}
protected function __performGet($name)
{
if (!$this->__canget($name)) {
throw new \Exception('Getter not allowed for property: ' . $name);
}
$accessors = $this->__getPropertyAccessors($name)['get'];
/* #var \ReflectionMethod $method */
$method = $accessors['method'];
if (!empty($method)) {
return $method->invoke($this);
}
return $this->{$this->__getBackingFieldName($name)};
}
protected function __performSet($name, $value)
{
if (!$this->__canset($name)) {
throw new \Exception('Setter not allowed for property: ' . $name);
}
$accessors = $this->__getPropertyAccessors($name)['set'];
/* #var \ReflectionMethod $method */
$method = $accessors['method'];
if (!empty($method)) {
return $method->invoke($this, $value);
}
$this->{$this->__getBackingFieldName($name)} = $value;
}
protected function __getPropertyAccessors($name)
{
return isset(self::$__propertyAccessors[$name])
? self::$__propertyAccessors[$name]
: null
;
}
protected function __getAccessorsFromDocBlock($docblock)
{
$accessors = [];
if (!empty(trim($docblock))) {
$doclines = null;
if (!empty($docblock)) {
$doclines = explode("\n", $docblock);
}
if (!empty($doclines)) {
foreach ($doclines as $line) {
if (preg_match('/#(get|set)\\s+(public|private|protected)/', $line, $matches)) {
$accessors[$matches[1]]['visibility'] = $matches[2];
}
}
}
}
return $accessors;
}
protected function __populatePropertyAcessors($name)
{
if ($this->__getPropertyAccessors($name) !== null) return;
try {
$property = new \ReflectionProperty(self::class, $this->__getBackingFieldName($name));
} catch (\ReflectionException $ex) {
$property = null;
}
$accessors = [];
if ($property != null) {
$accessors = $this->__getAccessorsFromDocBlock($property->getDocComment());
}
try {
$methodName = 'get' . ucfirst($name);
$method = new \ReflectionMethod(self::class, $methodName);
$method->setAccessible(true);
$accessors = array_merge($accessors, $this->__getAccessorsFromDocBlock($method->getDocComment()));
} catch (\ReflectionException $ex) {
$method = null;
}
if ($method !== null || isset($accessors['get'])) {
$accessors['get']['method'] = $method;
}
try {
$methodName = 'set' . ucfirst($name);
$method = new \ReflectionMethod(self::class, $methodName);
$method->setAccessible(true);
$accessors = array_merge($accessors, $this->__getAccessorsFromDocBlock($method->getDocComment()));
} catch (\ReflectionException $ex) {
$method = null;
}
if ($method !== null || isset($accessors['set'])) {
$accessors['set']['method'] = $method;
}
self::$__propertyAccessors[$name] = $accessors;
}
}
Here is a quick unit test I created using the Codeception format:
<?php
class PropertyAssesorTraitTestClass
{
use PropertyAccessorTrait;
private $__propertyPrefix = '_';
/**
* #get public
* #set public
*/
private $_integer = 1;
/**
* #get public
*/
private $_getonly = 100;
/**
* #set public
*/
private $_setonly;
private $_customDoubler;
private function getCustomDoubler()
{
return $this->_customDoubler * 2;
}
private function setCustomDoubler($value)
{
$this->_customDoubler = $value * 2;
}
public $publicField = 1234;
/**
* #return int
* #get public
*/
private function getPureAccessor()
{
return $this->publicField;
}
/**
* #param $value
* #set public
*/
private function setPureAccessor($value)
{
$this->publicField = $value;
}
private $_purePrivate = 256;
}
$I = new UnitTester($scenario);
$I->wantTo('Ensure properties are accessed correctly');
$instance = new PropertyAssesorTraitTestClass();
$I->assertSame(1, $instance->integer);
$instance->integer = 2;
$I->assertSame(2, $instance->integer);
$instance->integer = $instance->integer + 1;
$I->assertSame(3, $instance->integer);
$instance->integer++;
$I->assertSame(4, $instance->integer);
$I->assertSame(100, $instance->getonly);
$I->expectException('Exception', function () use ($instance) { $instance->getonly = 50; });
$instance->setonly = 50;
$I->expectException('Exception', function () use ($instance) { $a = $instance->setonly; });
$instance->customDoubler = 100;
$I->assertSame(400, $instance->customDoubler);
$I->assertSame(1234, $instance->publicField);
$instance->pureAccessor = 1000;
$I->assertSame(1000, $instance->publicField);
$instance->publicField = 1234;
$I->assertSame(1234, $instance->publicField);
$I->assertSame(1234, $instance->pureAccessor);
$I->expectException('Exception', function () use ($instance) { return $instance->purePrivate; });
I like to use this pattern:
class foo
{
//just add p as prefix to be different than method name.
protected $pData;
public funtion __construct() {}
public funtion __destruct() {}
public funtion __clone() {}
public function Data($value == "")
{
if ($value != "") {
$this->pData = $value;
}
return $this->pData;
}
}
$myVar = new foo();
//for SET
$myVar->Data("A Value");
//for GET
$item = $myVar->Data();
class MyClass
{
private $name = null;
public function __construct($name = null)
{
$this->name = $name;
}
public function __set($name, $value)
{
if (property_exists($this, $name)) {
$this->name = $value;
}
return $this;
}
public function __get($name)
{
if (property_exists($this, $name)) {
return $this->$name;
}
return null;
}
}
this is PHP ; you don't need get set
class MyClass {
public $ID;
}
$object = new MyClass();
$object->ID = 'foo';
echo $object->ID;
will work

Categories