80

Can I declare a method in an object as both a static and non-static method with the same name that calls the static method?

I want to create a class that has a static method "send" and a non-static method that calls the static function. For example:

class test {
    private $text;
    public static function instance() {
        return new test();
    }

    public function setText($text) {
        $this->text = $text;
        return $this;
    }

    public function send() {
        self::send($this->text);
    }

    public static function send($text) {
        // send something
    }
}

I want to be able to call the function on these two was

test::send("Hello World!");

and

test::instance()->setText("Hello World")->send();

is it possible?

yivi
  • 42,438
  • 18
  • 116
  • 138
alphanyx
  • 1,647
  • 2
  • 13
  • 18
  • 5
    Why would you want to do that if I may ask? – PeeHaa Jul 04 '12 at 15:08
  • [could static members use nonstatic members and vice versa?](http://stackoverflow.com/questions/2204128/could-static-members-use-nonstatic-members-and-vice-versa) – dan-lee Jul 04 '12 at 15:11
  • The answer is no btw, but I'm really curious as to why you think you want to do that. – PeeHaa Jul 04 '12 at 15:11
  • The problem here is you can't have function overloading like this. PHP doesn't support same function/method names even with different parameters – Marcus Recck Jul 04 '12 at 15:13
  • Why do you need a static function? – Jonathan Egerton Jul 04 '12 at 15:15
  • i want to user a object to define more detailled informations instead of inserting all details as a parameter to the static call.. but the call is also needed for simple message outputs with only one parameter – alphanyx Jul 04 '12 at 15:57
  • 56
    Lots of people keen to dismiss this as a bad question, just because they personally can't think of a use for it. Here's an example: I needed this because I wanted to provide some functions for event handling. I wanted to offer SomeClass::on('some_event',$handler_fn) which would be applied to every instance of SomeClass, and also $instance->on('some_event',$handler_fn) which would apply only to the specific instance. So, good question, and thanks to @lonesomeday for a correct answer without any ignorant judgements. – Daniel Howard Jun 25 '14 at 11:08
  • 1
    [Decorators!](https://sourcemaking.com/design_patterns/decorator/php) – Timo Huovinen Oct 02 '15 at 13:16
  • 4
    A Lot of people think only PHP, maybe trying out some other languages like ruby and python would be beneficial ! – g13013 Feb 09 '16 at 09:18
  • have a look a complete example here http://undolog.com/how-to-create-a-method-both-as-static-and-as-instance/ – Undolog Oct 06 '16 at 08:53

7 Answers7

99

You can do this, but it's a bit tricky. You have to do it with overloading: the __call and __callStatic magic methods.

class test {
    private $text;
    public static function instance() {
        return new test();
    }

    public function setText($text) {
        $this->text = $text;
        return $this;
    }

    public function sendObject() {
        self::send($this->text);
    }

    public static function sendText($text) {
        // send something
    }

    public function __call($name, $arguments) {
        if ($name === 'send') {
            call_user_func(array($this, 'sendObject'));
        }
    }

    public static function __callStatic($name, $arguments) {
        if ($name === 'send') {
            call_user_func(array('test', 'sendText'), $arguments[0]);
        }
    }
}

This isn't an ideal solution, as it makes your code harder to follow, but it will work, provided you have PHP >= 5.3.

Bora
  • 10,529
  • 5
  • 43
  • 73
lonesomeday
  • 233,373
  • 50
  • 316
  • 318
  • 35
    This makes my eyes b̢̗̫͕l͓̫͈e҉͍̖͙ḙ̣̭̦̫̞͟d̼. I won't downvote it, because you've warned him that this kind of coding will not ease his life, and it is helpful. But still : – Madara's Ghost Jul 04 '12 at 15:22
  • 14
    @lonesomeday Up voting just because you have the patience to write out the code which pretty much has no tangible use! yet answers the question :-) – Nicholas King Jul 04 '12 at 15:26
  • 4
    @Truth Yes. It should be "you can do this, but you ***really*** shouldn't." – lonesomeday Jul 04 '12 at 15:26
  • thanks for the answer.. its true that this solution is not easy to follow for another person.. so i wont use it.. but its the solution for my problem so thanks a lot – alphanyx Jul 04 '12 at 15:59
  • 2
    You all say it's a bad idea, but wouldn't it be useful and easier to do something like this if you have a function you want to be able to use as a member in order to get access to the member variables and another one with the same purpose but in which you could use your own arguments? Sure, you could just use the instance of the function to get the variables, but imagine they were private. It should be useful for for example maths, it would be of use in situations where an instance of the class wasn't needed and when one was. No confusion and faster coding. Otherwise one could use comments. :D – TrisT May 08 '17 at 06:50
3

I would make a hidden class as the constructor and return that hidden class inside the parent class that has static methods equal to the hidden class methods:

// Parent class

class Hook {

    protected static $hooks = [];

    public function __construct() {
        return new __Hook();
    }

    public static function on($event, $fn) {
        self::$hooks[$event][] = $fn;
    }

}


// Hidden class

class __Hook {

    protected $hooks = [];

    public function on($event, $fn) {
        $this->hooks[$event][] = $fn;
    }

}

To call it statically:

Hook::on("click", function() {});

To call it dynamically:

$hook = new Hook;
$hook->on("click", function() {});
Taufik Nurrohman
  • 3,329
  • 24
  • 39
  • 2
    Your hidden class has absolut no effect, it calls always the static method.(Tested in php version 5.3.0 - 7.4.1)@see: https://3v4l.org/HIBKR – Radon8472 Jan 21 '20 at 09:54
2

No you can't have two methods with the same name. You could do basicly the same thing by renaming one of the methods. Renaming test::send("Hello World!"); to test::sendMessage("Hello World!"); would work. I would just create the a single send method with an optional text argument that changes how the method functions.

public function send($text = false) {
    if (!$text) {
        $text = $this -> text;
    }

    // Send something
}

I courious as to why you need the static function at all.

Jordan Shute
  • 169
  • 3
0

I agree that this should be avoided at all costs but there are some cases where it might be useful.

In most cases it will just make your code unreadable and unmanageable.

Believe me, I have been down that path.

Here is an example with a use case scenario where it might still be practical.

I am extending CakePHP 3.0's File class as my default file handling class.

I wanted a to put in a static mime type guesser.

In some cases I have a filename instead of an actual file and some assumptions need to be made in this case. ( if the file exists, try to get the mime from it else use extention of filename provided)

Other times if I actually instantiated an object the default mime() method should work but if it fails the filename needs to be extracted from the object and the static method should be called instead.

To avoid confusion my aim was to get the mime type by calling the same method:

Static:

NS\File::type('path/to/file.txt')

As object

$f = new NS\File('path/to/file.txt');
$f->type();

Here is my example extended class:

<?php

namespace NS;

class File extends \Cake\Utility\File
{

    public function __call($method, $args) {
        return call_user_func_array([get_called_class(), 'obj'.ucfirst($method)], $args);
    }
    public static function __callStatic($method, $args) {
        return call_user_func_array([get_called_class(), 'static'.ucfirst($method)], $args);
    }

    public function objType($filename=null){
        $mime = false;
        if(!$filename){
            $mime = $this->mime();
            $filename = $this->path;
        }
        if(!$mime){
            $mime = static::getMime($filename);
        }
        return $mime;
    }

    public static function staticType($filename=null){
        return static::getMime($filename);
    }

    public static function getMime($filename = null)
    {
        $mimes = [
            'txt' => 'text/plain',
            'htm' => 'text/html',
            'html' => 'text/html',
            'php' => 'text/html',
            'ctp' => 'text/html',
            'twig' => 'text/html',
            'css' => 'text/css',
            'js' => 'application/javascript',
            'json' => 'application/json',
            'xml' => 'application/xml',
            'swf' => 'application/x-shockwave-flash',
            'flv' => 'video/x-flv',
            // images
            'png' => 'image/png',
            'jpe' => 'image/jpeg',
            'jpeg' => 'image/jpeg',
            'jpg' => 'image/jpeg',
            'gif' => 'image/gif',
            'bmp' => 'image/bmp',
            'ico' => 'image/vnd.microsoft.icon',
            'tiff' => 'image/tiff',
            'tif' => 'image/tiff',
            'svg' => 'image/svg+xml',
            'svgz' => 'image/svg+xml',
            // archives
            'zip' => 'application/zip',
            'rar' => 'application/x-rar-compressed',
            'exe' => 'application/x-msdownload',
            'msi' => 'application/x-msdownload',
            'cab' => 'application/vnd.ms-cab-compressed',
            // audio/video
            'mp3' => 'audio/mpeg',
            'qt' => 'video/quicktime',
            'mov' => 'video/quicktime',
            // adobe
            'pdf' => 'application/pdf',
            'psd' => 'image/vnd.adobe.photoshop',
            'ai' => 'application/postscript',
            'eps' => 'application/postscript',
            'ps' => 'application/postscript',
            // ms office
            'doc' => 'application/msword',
            'rtf' => 'application/rtf',
            'xls' => 'application/vnd.ms-excel',
            'ppt' => 'application/vnd.ms-powerpoint',
            // open office
            'odt' => 'application/vnd.oasis.opendocument.text',
            'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
        ];
        $e = explode('.', $filename);
        $ext = strtolower(array_pop($e));
        if (array_key_exists($ext, $mimes)) {
            $mime = $mimes[$ext];
        } elseif (function_exists('finfo_open') && is_file($filename)) {
            $finfo = finfo_open(FILEINFO_MIME);
            $mime = finfo_file($finfo, $filename);
            finfo_close($finfo);
        } else {
            $mime = 'application/octet-stream';
        }
        return $mime;
    }
}
Dieter Gribnitz
  • 5,062
  • 2
  • 41
  • 38
0

In php, you can set/asign a class method with method visibility (Public, Private, Protected) also class properties, which can declare the restric distribution of class method or class properties, like those can access outside of the class or not..

And for calling purpose we get two approch,

  1. Static (self::)
  2. Non Static ($this->)

Lets take a class Foo have some Methods And Properties.. Which have visibilites and calling approch

    <?php

    class Foo {

     public const WELCOME ='This is WELCOME Non Static Constant For Foo Class';
    public string $text='This is A Text Non Static Foo Class Properties';
    public static string $texter='This is A Texter Foo Static Class Properties';
    private string $ptext='This is a private string Non Static properties of Class Foo';
      
    
    public static function Bar()
    {
        echo "Static Method Bar is calling\n";
    }
    
    public function Baz()
    {
        echo "Non Static Method Baz is calling \n";
    }
    
    
    protected function Another()
    {
        echo "Non Static Method Another is calling \n";
    }
    
    private function Again()
    {
        echo "Non Static Private Method Again is calling \n";
    }
    
    protected static function AnotherOne()
    {
        echo "Non Static Method Another is calling \n";
    }
    
    private static function AgainOne()
    {
        echo "Non Static Private Method Again is calling \n";
    }
    
    
    public static function bypass()
    {
        return self::AgainOne();
    }
    
    public function getPText()
    {
        return $this->ptext;
    }
    
    
    
}
?>

Now Test This Class

<?php

//Non Static Call By Creating an $app instance of Foo Class..
$app = new Foo();
 echo $app->WELCOME;        // Undefined property: Foo::$WELCOME
 echo $app->text;           // This is A Text Non Static Foo Class Properties
 echo $app->texter;         // Accessing static property Foo::$texter as non static
 echo $app->Bar();          // Static Method Bar is calling
 echo $app->Baz();          // Non Static Method Baz is calling 
 echo $app->Another();      // Uncaught Error: Call to protected method Foo::Another() from global scope
 echo $app->Again();        // Uncaught Error: Call to private method Foo::Again() from global scope
 echo $app->AnotherOne();   // Uncaught Error: Call to protected method Foo::AnotherOne() from global scope
 echo $app->AgainOne();     // Uncaught Error: Call to private method Foo::AgainOne() from global scope
 echo $app->bypass();       // Non Static Private Method Again is calling 
 echo $app->ptext;          // Uncaught Error: Cannot access private property Foo::$ptext
 echo $app->getPText();     // This is a private string Non Static properties of Class Foo 

//Static Call
 echo Foo::WELCOME;         // This is WELCOME Non Static Constant For Foo Class
 echo Foo::text;            // Uncaught Error: Undefined constant Foo::text
 echo Foo::texter;          // Uncaught Error: Undefined constant Foo::texter
 echo Foo::Bar();           // Static Method Bar is calling
 echo Foo::Baz();           // Uncaught Error: Non-static method Foo::Baz() cannot be called statically
 echo Foo::Another();       // Uncaught Error: Call to protected method Foo::Another() from global scope
 echo Foo::Again();         // Uncaught Error: Call to private method Foo::Again() from global scope 
 echo Foo::AnotherOne();    // Uncaught Error: Call to protected method Foo::AnotherOne() from global scope
 echo Foo::AgainOne();      // Uncaught Error: Call to private method Foo::AgainOne() from global scope
 echo Foo::bypass();        // Non Static Private Method Again is calling 
 
 ?>

See In Action here.

AloneCoder
  • 1
  • 1
  • 4
  • Please add some explanation to your answer such that others can learn from it. What does the code do? How does it answer the given question? – Nico Haase Oct 16 '21 at 20:07
  • Any static class methods can be call non statically .. But Non static methods can't call statically.. Like Posts::create(); can also call via $post = new Posts(); $post->create(); Thus it is possible manipulate staic methods with non staic , It's may be some php hack or Php feature. Also for declaration, ```class xyz { public static function abc(){} // Static Method, public function abc(){} // non static method }``` – AloneCoder Oct 17 '21 at 11:58
  • Please add all explanation **to your answer** by editing it – Nico Haase Oct 17 '21 at 17:34
  • Thanks, Nicco Haase, I update my answare ... – AloneCoder Oct 17 '21 at 20:32
0

Another option with remove E_STRICT error log is like that:

<?php 
error_reporting(E_ALL ^ E_STRICT);
class userData {

    private static $id = 33;
    public function staticCall() {
      
        if(isset($this)) {
            //object
            echo 'object:';
        } else {
            //static //made STRICT ERROR
            echo 'static:';
        }
        return self::$id;
    }
    
}
  
  $obj = new userData();
  print_r($obj->staticCall()); // object:33
  print_r(userData::staticCall()); // static:33
  

?>

Kamil Dąbrowski
  • 984
  • 11
  • 17
-1

Sorry for bumping an old thread, but I would like to expand on @lonesomeday 's answer. (Thanks @lonesomeday for the initial code sample.)

I was also experimenting with this as well, but did not want to call the methods as he called them in the original post. Instead I have the following, which seems to work:

    class Emailer {

    private $recipient;

    public function to( $recipient )
    {
        $this->recipient = $recipient;
        return $this;
    }

    public function sendNonStatic()
    {
        self::mailer( $this->recipient );
    }

    public static function sendStatic( $recipient )
    {
        self::mailer( $recipient );
    }

    public function __call( $name, $arguments )
    {
        if ( $name === 'send' ) {
            call_user_func( array( $this, 'sendNonStatic' ) );
        }
    }

    public static function mailer( $recipient )
    {
        // send()
        echo $recipient . '<br>';
    }

    public static function __callStatic( $name, $arguments )
    {
        if ( $name === 'send' ) {
            call_user_func( array( 'Emailer', 'sendStatic' ), $arguments[0] );
        }
    }
}

Emailer::send( 'foo@foo.foo' );

$Emailer = new Emailer;
$Emailer->to( 'bar@bar.bar' );
$Emailer->send();
Bung
  • 259
  • 2
  • 7
  • Just avoid all this and have one non static method `send()` and one static methiod `sentTo( $asddr ). Cleaner, faster (both to make and to run, more logical and simpler) – FrancescoMM Jul 07 '23 at 08:51