I've got a small input helper class.
I read a lot that static classes are not the best to use. But I still haven't grasp why I shouldn't use static classes. I'm not that advanced yet, so maybe I will figure it out later on.
The reason I made it static is that it's not an object that's unique every time it's being called in my script.
Is it a good thing that I made the class static?
class Input {
public static function exists($type = 'post') {
switch($type) {
case 'post':
return (!empty($_POST)) ? true : false;
break;
case 'get':
return (!empty($_GET)) ? true : false;
break;
default:
return false;
break;
}
}
public static function get($input, $source = 'post') {
if($source == 'post' && isset($_POST[$input])) {
return $_POST[$input];
} else if($source == 'get' && isset($_GET[$input])) {
return $_GET[$input];
}
return false;
}
}
EDIT: Updated code
class Input {
private $_get,
$_post;
public function __construct($get, $post) {
$this->_get = $get;
$this->_post = $post;
}
public function exists($type = 'POST') {
switch($type) {
case 'POST':
return (!empty($this->_post)) ? true : false;
break;
case 'GET':
return (!empty($this->_get)) ? true : false;
break;
default:
return false;
break;
}
}
public function get($input, $source = 'POST') {
if($source == 'POST' && isset($this->_post[$input])) {
return $this->_post[$input];
} elseif($source == 'GET' && isset($this->_get[$input])) {
return $this->_get[$input];
}
throw new Exception('Undefined index: ' . $input);
}
}
Just because you have only one of something doesn't mean it has to be static. Especially in regard to Unit tests, static classes are hard to deal with.
Your class Input is bound to $_GET and $_POST. This means you can not use it in Unit tests.
If instead your class looked like this:
$input = new Input($_GET, $_POST);
ie. you give it the global dependencies, now Input can be tested because you can mock the values of $_GET and $_POST.
If you don't want a static class, you could do something like this :
class Input {
private $array;
function __construct($array) {
$this->array = $array;
}
public function exists() {
return isset($this->array);
}
public function get($input) {
if($this->exists() && isset($this->array[$input])) {
return $this->array[$input];
}
return false;
}
}
$get = new Input($_GET);
$value = $get->get('key');
$post = new Input($_POST);
$value = $post->get('key');
Related
There is the following function in the controller.php of framework thinkphp used in a project, where there is this line of codes: "$conf = $this->$action".
"if (property_exists($this, $action))" is checking if there exists property $action in the class $this. Form the next line of codes "if (key_exists('login', $conf))", it looks to me that $conf is an array.
My question is: what exactly is "$conf = $this->$action" doing, assigning an array (the property $action? why $action is an array?) to the variable $conf?
public function checkAccessControl()
{
if (Config::get('frame.controller.checkAccessControl.exec')) {
$action = $this->request ->action(true);
if (property_exists($this, $action)) {
$conf = $this->$action;
if (key_exists('login', $conf)) {
if ($conf['login'] === true && $this->if_login === false) {
$this->access_control_check = false;
$this->response(["error"=>"未登录用户无权访问"],[],401,'json');//要求用户的身份认证
} else {
$this->access_control_check = true;
}
} else {
$this->access_control_check = false;
$this->response(["error"=>"代码错误:未标注访问类型"],[],500);//服务器内部错误,无法完成请求(未在控制器中标注login的访问控制)
}
} else {
$this->access_control_check = false;
$this->response(["error"=>"代码错误:未标注访问控制"],[],500,'json',['action'=>$this->request->action(true)]);//服务器内部错误,无法完成请求(未在控制器中标注方法的访问控制)
}
} else {
$this->access_control_check = Config::get('frame.controller.checkAccessControl.default.access_control_check');
}
}
Say I have to similar function :
public function auth(){
return $someResponse;
}
public function collect(){
return $someOtherResponse
}
Question : When one of the response get passed to another class, is there any way to check which function returned the response ?
In a purely object-oriented way, wanting to attach information to a value is akin to wrapping it into a container possessing context information, such as:
class ValueWithContext {
private $value;
private $context;
public function __construct($value, $context) {
$this->value = $value;
$this->context = $context;
}
public value() {
return $this->value;
}
public context() {
return $this->context;
}
}
You can use it like this:
function auth()
{
return new ValueWithContext($someresponse, "auth");
}
function collect()
{
return new ValueWithContext($someotherrpesonse, "collect");
}
This forces you to be explicit about the context attached to the value, which has the benefit of protecting you from accidental renamings of the functions themselves.
As per my comment, using arrays in the return will give you a viable solution to this.
It will allow a way to see what has been done;
function auth()
{
return (array("auth" => $someresponse));
}
function collect()
{
return (array("collect" => $someotherrpesonse));
}
class myClass
{
function doSomething($type)
{
if (function_exists($type))
{
$result = $type();
if (isset($result['auth']))
{
// Auth Used
$auth_result = $result['auth'];
}
else if (isset($result['collect']))
{
// Collect used
$collect_result = $result['collect'];
}
}
}
}
It can also give you a way to fail by having a return array("fail" => "fail reason")
As comments say also, you can just check based on function name;
class myClass
{
function doSomething($type)
{
switch ($type)
{
case "auth" :
{
$result = auth();
break;
}
case "collect" :
{
$result = collect();
break;
}
default :
{
// Some error occurred?
}
}
}
}
Either way works and is perfectly valid!
Letting the two user defined functions auth() & collect() call a common function which makes a call to debug_backtrace() function should do the trick.
function setBackTrace(){
$backTraceData = debug_backtrace();
$traceObject = array_reduce($backTraceData, function ($str, $val2) {
if (trim($str) === "") {
return $val2['function'];
}
return $str . " -> " . $val2['function'];
});
return $traceObject;
}
function getfunctionDo1(){
return setBackTrace();
}
function getfunctionDo2(){
return setBackTrace();
}
class DoSomething {
static function callfunctionTodo($type){
return (($type === 1) ? getfunctionDo1() : getfunctionDo2());
}
}
echo DoSomething::callfunctionTodo(1);
echo "<br/>";
echo DoSomething::callfunctionTodo(2);
/*Output
setBackTrace -> getfunctionDo1 -> callfunctionTodo
setBackTrace -> getfunctionDo2 -> callfunctionTodo
*/
The above function would output the which function returned the response
I am struggling to create an access object to sections stored in the Database. This is a skellington of the process, this contains static data until I can get the principle working.
class User {
const IS_ADMIN = 1;
const IS_MODERATOR = 2;
const IS_MEMBER = 4;
}
This class will auto-load data from the database eventually but for the time being, this class has default values.
class Scope {
private $priv = [];
public function __construct() {
$this->priv = [1];
}
public function getPrivilidges() {
return $this->priv;
}
}
This is where it messes up, I can tell that the second and third conditions cannot be met if the first fails, how can I stop this?
class Priverlidges {
public function canView($type, Scope $scope) {
if($type & User::IS_ADMIN) {
foreach($scope->getPrivilidges() as $p) {
if($p == User::IS_ADMIN) continue;
return false;
}
return true;
}
if($type & User::IS_MODERATOR) {
foreach($scope->getPrivilidges() as $p) {
if($p == User::IS_MODERATOR) continue;
return false;
}
return true;
}
if($type & User::IS_MEMBER) {
foreach($scope->getPrivilidges() as $p) {
if($p == User::IS_MEMBER) continue;
return false;
}
return true;
}
}
}
Example usage which works fine when the default value of the priverlidge is 1:
echo (int)(new Priverlidges)->canView(User::IS_ADMIN, new Scope());
Example usage which works fine when the default value of the priverlidge is 2:
echo (int)(new Priverlidges)->canView(User::IS_MODERATOR | User::IS_ADMIN, new Scope()); // it returns false at the first condition
Can anyone help me with when to return true or false? Thanks in advance.
P.S - Users can be both Mods and Admins
EDIT: I have tried to use in_array() and still am unsure when to return the value true or false because it get's overwrite if the second method runs.
I figured it out. First, check the user is not already authenticated using a placeholder ($this->_state). Then check the type of user and check it is inside the scope.
class Priverlidges {
private $_state = false;
public function canView($type, Scope $scope) {
if(!$this->_state && $type & User::IS_ADMIN && in_array(User::IS_ADMIN, $scope->getPrivilidges())) {
$this->_state = true;
}
if(!$this->_state && $type & User::IS_MODERATOR && in_array(User::IS_MODERATOR, $scope->getPrivilidges())) {
$this->_state = true;
}
if(!$this->_state && $type & User::IS_MEMBER && in_array($scope->getPrivilidges(), User::IS_MEMBER)) {
$this->_state = true;
}
return $this->_state;
}
}
Consider the following PHP snippet:
<?php
class Is
{
function __get($key)
{
$class = __CLASS__ . '_' . $key;
if (class_exists($class) === true)
{
return $this->$key = new $class();
}
return false;
}
function Domain($string)
{
if (preg_match('~^[0-9a-z\-]{1,63}\.[a-z]{2,6}$~i', $string) > 0)
{
return true;
}
return false;
}
}
class Is_Domain
{
function Available($domain)
{
if (gethostbynamel($domain) !== false)
{
return true;
}
return false;
}
}
$Is = new Is();
var_dump($Is->Domain('google.com')); // true
var_dump($Is->Domain->Available('google.com')); // false
?>
Is it possible to call the Available() method like this (and still return solely true or false if the Available method is not called)?
var_dump($Is->Domain('google.com')->Available()); // false
If yes, how?
EDIT: Would this do the trick?
class Is
{
function __get($key)
{
// same as before
}
function Domain($string)
{
if (preg_match('~^[0-9a-z\-]{1,63}\.[a-z]{2,6}$~i', $string) > 0)
{
return (bool) $this->Domain;
}
return false;
}
}
class Is_Domain
{
function __toString()
{
return true;
}
function Available($domain)
{
if (gethostbynamel($domain) !== false)
{
return true;
}
return false;
}
}
Thanks in Advance!
PS: This snippet is truncated, so don't expect it to make it a lot of sense just by its own.
Essentially you want a method to return either a bool or an object based on whether a subsequent method call to the result is going to occur. I don't think this will be possible without some massive hack (e.g. reading the PHP file in yourself and looking ahead), and it shouldn't be because your objects shouldn't be worrying about the context in which they are used.
Instead you could get the first call to return an object which is relevant in both cases, e.g. DomainLookupResult, which has two methods e.g. Exists() and IsAvailable(). You could then do:
$result = $Is->Domain('google.com');
$isValid = $result->Exists();
$isAvaliable = $result->IsAvailable();
//or chaining:
$isValid = $Is->Domain('google.com')->Exists();
$isAvailable = $Is->Domain('google.com')->IsAvailable();
You can only chain method calls if they return an object!
This is because you can only call methods on objects.
The problem with your code is that the methods return a non object value, either true or false. And the problem is not in any way solved better by chaining methods. You should use that where its applicable. Like chaining many setters, NOT getters which the methods you want to use essentially is.
var_dump($Is->Domain->Available('google.com')); // false
//is the same as
$res = $Is->Domain;
$res = $res->Available('google.com'));
var_dump($res);
So you see the first res is a boolean true or false, and you can not call a method on that.
edit
This might be a "solution". Not a good solution though since this is better without chaining.
class Domain
{
public $domain;
function setDomain($domain) {
$this->domain = $domain;
return $this;
}
function isDomain($domain = null) {
if (is_string($domain)) {
$this->setDomain($domain);
}
$result = gethostbynamel($this->domain) !== false;
return new Result($this, $result);
}
function isValid() {
$result = (bool) preg_match('', $this->domain);
return new Result($this, $result)
}
}
class Result
{
public $result;
public $object;
public function __construct($object, $result)
{
$this->result = $result;
$this->object = $object;
}
public function __call($method, $arguments)
{
if (is_object($this->result)) {
return call_user_func_array(array($this->result, $method), $arguments);
}
if (!$this->result) {
return $this;
}
return call_user_func_array(array($this->object, $method), $arguments);
}
}
$domain = new Domain();
var_dump($domain->isValid('google.com')->isAvailable()->result);
/edit
This will solve your problem above.
var_dump($Is->Domain('validandfreedomain.com') && $Is_Domain->Available('validandfreedomain.com')); // true
If you desperately want to chain a method for this problem you could make it more like this.
class Domain
{
public $domain;
function setDomain($domain) {
$this->domain = $domain;
return $this;
}
function isAvailable() {
return gethostbynamel($this->domain) !== false;
}
function isValid() {
return (bool) preg_match('', $this->domain);
}
}
$domain = new Domain();
$result = $domain->setDomain('validandfreedomain.com')->isValid() && $domain->isAvailable();
It is possible, if your function returns an object, you can call its method, and so on (see method chaining). The only limitation is - as far as a I know - is that you cannot chain calls from an object created by new ( new Object()->method1()->method2() ).
As for your example, I see no point in using either the dynamic class, or method chaining stuff.
public static function __get($value)
does not work, and even if it did, it so happens that I already need the magic __get getter for instance properties in the same class.
This probably is a yes or no question, so, it is possible?
No, it is not possible.
Quoting the manual page of __get :
Member overloading only works in
object context. These magic methods
will not be triggered in static
context. Therefore these methods can
not be declared static.
In PHP 5.3, __callStatic has been added ; but there is no __getStatic nor __setStatic yet ; even if the idea of having/coding them often comes back on the php internals# mailling-list.
There is even a Request for Comments: Static classes for PHP
But, still, not implemented (yet ? )
Maybe someone still need this:
static public function __callStatic($method, $args) {
if (preg_match('/^([gs]et)([A-Z])(.*)$/', $method, $match)) {
$reflector = new \ReflectionClass(__CLASS__);
$property = strtolower($match[2]). $match[3];
if ($reflector->hasProperty($property)) {
$property = $reflector->getProperty($property);
switch($match[1]) {
case 'get': return $property->getValue();
case 'set': return $property->setValue($args[0]);
}
} else throw new InvalidArgumentException("Property {$property} doesn't exist");
}
}
Very nice mbrzuchalski. But it seems to only work on public variables. Just change your switch to this to allow it to access private/protected ones:
switch($match[1]) {
case 'get': return self::${$property->name};
case 'set': return self::${$property->name} = $args[0];
}
And you'd probably want to change the if statement to limit the variables that are accessible, or else it would defeat the purpose of having them be private or protected.
if ($reflector->hasProperty($property) && in_array($property, array("allowedBVariable1", "allowedVariable2"))) {...)
So for example, I have a class designed to pull various data for me out of a remote server using an ssh pear module, and I want it to make certain assumptions about the target directory based on what server it's being asked to look at. A tweaked version of mbrzuchalski's method is perfect for that.
static public function __callStatic($method, $args) {
if (preg_match('/^([gs]et)([A-Z])(.*)$/', $method, $match)) {
$reflector = new \ReflectionClass(__CLASS__);
$property = strtolower($match[2]). $match[3];
if ($reflector->hasProperty($property)) {
if ($property == "server") {
$property = $reflector->getProperty($property);
switch($match[1]) {
case 'set':
self::${$property->name} = $args[0];
if ($args[0] == "server1") self::$targetDir = "/mnt/source/";
elseif($args[0] == "server2") self::$targetDir = "/source/";
else self::$targetDir = "/";
case 'get': return self::${$property->name};
}
} else throw new InvalidArgumentException("Property {$property} is not publicly accessible.");
} else throw new InvalidArgumentException("Property {$property} doesn't exist.");
}
}
try this:
class nameClass{
private static $_sData = [];
private static $object = null;
private $_oData = [];
public function __construct($data=[]){
$this->_oData = $data;
}
public static function setData($data=[]){
self::$_sData = $data;
}
public static function Data(){
if( empty( self::$object ) ){
self::$object = new self( self::$_sData );
}
return self::$object;
}
public function __get($key) {
if( isset($this->_oData[$key] ){
return $this->_oData[$key];
}
}
public function __set($key, $value) {
$this->_oData[$key] = $value;
}
}
nameClass::setData([
'data1'=>'val1',
'data2'=>'val2',
'data3'=>'val3',
'datan'=>'valn'
]);
nameClass::Data()->data1 = 'newValue';
echo(nameClass::Data()->data1);
echo(nameClass::Data()->data2);
Combining __callStatic and call_user_func or call_user_func_array can give access to static properties in PHP class
Example:
class myClass {
private static $instance;
public function __construct() {
if (!self::$instance) {
self::$instance = $this;
}
return self::$instance;
}
public static function __callStatic($method, $args) {
if (!self::$instance) {
new self();
}
if (substr($method, 0, 1) == '$') {
$method = substr($method, 1);
}
if ($method == 'instance') {
return self::$instance;
} elseif ($method == 'not_exist') {
echo "Not implemented\n";
}
}
public function myFunc() {
echo "myFunc()\n";
}
}
// Getting $instance
$instance = call_user_func('myClass::$instance');
$instance->myFunc();
// Access to undeclared
call_user_func('myClass::$not_exist');
Also, you can get static properties accessing them like member properties, using __get():
class ClassName {
private static $data = 'smth';
function __get($field){
if (isset($this->$field)){
return $this->$field;
}
if(isset(self::$$field)){
return self::$$field; // here you can get value of static property
}
return NULL;
}
}
$obj = new ClassName();
echo $obj->data; // "smth"