This question about syntax/syntax capabilities in PHP. Take for example, using variables to store class names when declaring objects:
$className= 'myClass';
$obj = new $className;
I was wondering if there were some way to do the same with constants. Something along the lines of:
define('CLASS_NAME','myClass');
$obj = new {CLASS_NAME};
This doesn't work. Obviously I could just use a variable as an intermediary step, but I was mostly just wondering for edification purposes whether this was a formatting issue or if this is just not possible to do directly in this way.
You can't do this with a sensible expression in PHP, as u_mulder says.
You can do it in one line with reflection.
$obj = (new ReflectionClass(CLASS_NAME))->newInstance();
Any parameters need to go into the newInstance call.
This is a silly idea, not least because having variable behaviour depending on class names in strings probably means bad architecture. Don't do it. But you asked if it is possible, and yes, it is.
As class name (e.g. MyClass) can not be distinguished from constant name MyClass - your approach will not work.
You can do:
const CLASS_NAME = '\MyClass';
$className = CLASS_NAME;
$object = new $className;
With php >=8.0 you can do the following:
final class SomeService
{
private const HANDLER_CLASS = CustomHandler::class;
private HandlerInterface $handler;
public function process(): void
{
$this->handler = new (self::HANDLER_CLASS)();
}
}
Related
Hey I have a class like this
class derp{
public $$ini;
public static function regIni($ini){
derp::$$ini = 'test';
}
}
And then in another file I have
core::regIni('register');
And then somewhere else I use
core::$register;
This produces an error
public $$ini
is not valid code but if I leave it without I can not set
core::$$ini
How do I fix this?
Please note that $$ini is a variable variable meaning the value of $ini is actuely the variable name so $ini = 'registry' then $$ini actualy means $registry.
Why not use access methods for setting and getting class data?
class derp {
protected static $_data = array();
public static function regIni($ini, $value) {
derp::$_data[$ini] = $value;
}
public static function getIni($ini, $default = NULL) {
return isset(derp::$_data[$ini]) ? derp::$_data[$ini] : $default;
}
}
So, this is not an overly helpful answer, as I can just conclude that it's currently not possible.
You cannot define a new ReflectionProperty("derp", "static_prop") for example and attach it. It's really for introspection only.
$c = new ReflectionClass("derp"); and $derp->setStaticPropertyValue("p", 123); is not working either. The properties need to be predefined still.
And lastly, neither can the runkit_* functions help with this task. They are intended for changing methods mainly.
Same for classkit.
I'm not aware of other such PECL extensions, but that wouldn't be useful as general solution anyway. So for current PHP versions you cannot add static class properties after the parsing stage.
take a look at magic methods __set and __get
Mario said:
It's not doable. Static properties can
only be defined at the parsing stage.
Neither ReflectionProperty or
ReflectionClass::setStaticPropertyValue,
nor runkit_* functions are currently
capable (intended) to create static
class properties. Sorry
Geuss i'l settle for a work around then.
Made an array $ini and loaded the values into there derp:$ini['base']['key']
Thanks for the help,
Robin
You can redirect calls to some properties/functions by using __get, __call.
Is there a way to do it for classes?
I would like to convert all mentions of some_class_name in the code to, MY_VERSION_some_class_name (not only for one class, it's a pattern).
This would be easy if methods / properties were the target of this renaming policy.
Can you think of a way to do it for classes in PHP?
Edit: I need this for referencing to different variants of classes in different situations. I need one class name to be resolved to different variants of this class, depending on a condition known at runtime, and persistent through the whole session.
Thanks
p.s.
If you are curious why I want to do this, look at Maintaining variants of an application
You can't convert all mentions of some_class_name in the code to another class. However, you can use variables as class names:
$className = "MyClass";
$obj = new $className;
$className::myMethod();
All you have to do is change the variable and you will be using a different class. If you have to do this for a lot of classes, you might want to create some sort of factory for it.
$factory = System::getFactory();
$obj = $factory->getAuthObj();
Basically, the System class would return a different object based on what class needed to be used for that particular run time.
Aiden's untested approach: variable variables and generating a string:
<?php
$dog = "12";
function getDogClass($myNum)
{
global $dog;
// Do something dynamic here;
$dog = "Dog_" . $myNum;
return "dog";
}
class Dog_13rc1
{
function __construct()
{
printf("Woof!");
}
}
class Dog_12
{
function __construct()
{
printf("Grrrr");
}
}
$myObj = new ${getDogClass('13rc1')}();
?>
As long as the call to getDogClass() returns a string of the name of a variable in scope then you are good.
The output is woof.
This means the only refactoring you need is to find/replace occurences of the class name with that call.
But this is grim. and probably slow. Also, a maintenance/bug-tracking nightmare type of hack.
The magic function you want is __autoload:
function __autoload($class_name) {
// include your special class in here
include $class_name . '.php';
}
Read more here: http://php.net/manual/en/language.oop5.autoload.php
How will I use an instance of an object that is initially loaded throughout the whole site?
I want $myinstance to be used everywhere.
$myinstance = new TestClass();
Thanks!
What you are looking for is called the singleton pattern.
If you are deeply into OOP architecture, and want to do things like Unit Testing in the future: Singletons are regarded as an imperfect approach and not "pure" in the sense of OOP. I asked a question on the issue once, and got pretty good results with other, better patterns. A lot of good reading.
If you just want to get started with something, and need your DB class available everywhere, just use a Singleton.
You just need to declare your variable in global scope (for example, in the begginning of your whole code), and when you want to use it inside a function, use the "global" statement. See http://php.net/global.
I'm not 100% sure I got what you want to do... but I'll try to answer anyway.
I think you can save it to a session variable, using the serialize/unserialize functions to save/retrieve your class instance. Probably you'd code TestClass as a singleton, but that really depends on what you're trying to do.
For instance:
if (!isset($_SESSION["my_class_session_var"])) // The user is visiting for the 1st time
{
$test = new TestClass();
// Do whatever you need to initialise $test...
$_SESSION["my_class_session_var"] = serialize($test);
}
else // Session variable already set. Retrieve it
{
$test = unserialize($_SESSION['my_class_session_var']);
}
There is a design pattern called Singleton. In short:
Change __construct and __clone to private, so calling new TestClass() will end up with Error!
Now make a class that will create new instance of your object or return existing one...
Example:
abstract class Singleton
{
final private function __construct()
{
if(isset(static::$instance)) {
throw new Exception(get_called_class()." already exists.");
}
}
final private function __clone()
{
throw new Exception(get_called_class()." cannot be cloned.");
}
final public static function instance()
{
return isset(static::$instance) ? static::$instance : static::$instance = new static;
}
}
Then try to extend this class and define static $instance variable
class TestClass extends Singleton
{
static protected $instance;
// ...
}
Now try this:
echo get_class($myinstance = TestClass::instance();
echo get_class($mysecondinstance = TestClass::instance());
Done
What I would like to do is something like this:
$method_result = new Obj()->method();
Instead of having to do:
$obj = new Obj();
$method_result = $obj->method();
The result doesn't actually matter to me in my specific case. But, is there a way to do this?
The feature you have asked for is available from PHP 5.4. Here is the list of new features in PHP 5.4:
https://php-legacy-docs.zend.com/manual/php5/en/migration54.new-features
And the relevant part from the new features list:
Class member access on instantiation has been added, e.g. (new Foo)->bar().
You cannot do what you are asking ; but you can "cheat", using the fact that, in PHP, you can have a function that has the same name as a class ; those names won't conflict.
So, if you declared a class like this :
class Test {
public function __construct($param) {
$this->_var = $param;
}
public function myMethod() {
return $this->_var * 2;
}
protected $_var;
}
You can then declare a function that returns an instance of that class -- and has exactly the same name as the class :
function Test($param) {
return new Test($param);
}
And now, it becomes possible to use a one-liner, like you asked -- only thing is you are calling the function, thus not using new :
$a = Test(10)->myMethod();
var_dump($a);
And it works : here, I'm getting :
int 20
as output.
And, better, you can put some phpdoc on your function :
/**
* #return Test
*/
function Test($param) {
return new Test($param);
}
This way, you'll even have hints in your IDE -- at least, with Eclipse PDT 2.x ; see the screeshot :
Edit 2010-11-30 : Just for information, a new RFC has been submitted, a few days ago, that proposes to add this feature to one of the future versions of PHP.
See : Request for Comments: Instance and method call/property access
So, maybe doing things like these will be possible in PHP 5.4 or another future version :
(new foo())->bar()
(new $foo())->bar
(new $bar->y)->x
(new foo)[0]
You can do it more universally by defining an identity function:
function identity($x) {
return $x;
}
identity(new Obj)->method();
That way you don't need to define a function for each class.
How about:
$obj = new Obj(); $method_result = $obj->method(); // ?
:P
No, this is not possible.
You need to assign the instance to a variable before you can call any of it's methods.
If you really wan't to do this you could use a factory as ropstah suggests:
class ObjFactory{
public static function newObj(){
return new Obj();
}
}
ObjFactory::newObj()->method();
Simply we can do this
$method_result = (new Obj())->method();
You could use a static factory method to produce the object:
ObjectFactory::NewObj()->method();
I, too, was looking for a one-liner to accomplish this as part of a single expression for converting dates from one format to another. I like doing this in a single line of code because it is a single logical operation. So, this is a little cryptic, but it lets you instantiate and use a date object within a single line:
$newDateString = ($d = new DateTime('2011-08-30') ? $d->format('F d, Y') : '');
Another way to one-line the conversion of date strings from one format to another is to use a helper function to manage the OO parts of the code:
function convertDate($oldDateString,$newDateFormatString) {
$d = new DateTime($oldDateString);
return $d->format($newDateFormatString);
}
$myNewDate = convertDate($myOldDate,'F d, Y');
I think the object oriented approach is cool and necessary, but it can sometimes be tedious, requiring too many steps to accomplish simple operations.
I see this is quite old as questions go but here is something I think should be mentioned:
The special class method called "__call()" can be used to create new items inside of a class. You use it like this:
<?php
class test
{
function __call($func,$args)
{
echo "I am here - $func\n";
}
}
$a = new test();
$a->new( "My new class" );
?>
Output should be:
I am here - new
Thus, you can fool PHP into making a "new" command inside of your top level class (or any class really) and put your include command in to the __call() function to include the class that you have asked for. Of course, you would probably want to test $func to make sure it is a "new" command that was sent to the __call() command and (of course) you could have other commands also because of how __call() works.
example:
class Vendor_ClassName_Helper {
CONST FIRST_OPTION = 1;
CONST SECOND_OPTION = 2;
public function __construct($option, $otherArgument) {
}
}
client code:
$obj = new Vendor_ClassName_Helper(Vendor_ClassName_Helper::FIRST_OPTION, $var);
Any good ways to avoid the long lines (and this is a rather short example)? Maybe other ways to implement the same?
I think clarity is better than short code. You can try to think of different words of expressing the same or different form. For your example, it doesn't seem very bad as Omega pointed out, and his method of splitting declaration on multiple lines is good as well.
Here's another trick: Depending on what your option constants do, you may want to employ a factory method instead of the new-keyword.
For example,
class Example {
private function __construct() { }
public static method createA(..) {
//configure for mode A
$cls = new self;
return $cls;
}
public static method createB(..) {
//configure for mode B
$cls = new self;
return $cls;
}
}
$x = Example::createA();
I avoid long lines and improve readability in most languages by breaking up the parameters into their own kind of block...
$obj = new Vendor_ClassName_Helper(
Vendor_ClassName_Helper::FIRST_OPTION,
$var
);
But two options doesn't always warrant it in my opinion. Static constants unfortunately can't really be changed and you do of course want them to remain descriptive.
What you have here isn't so bad :)
If you're passing a constant to the constructor, it would suggest that you should create subclasses instead:
class Vendor_ClassName_Helper {
public function __construct($otherArgument) {
}
}
class Vendor_ClassName_Helper_First extends Vendor_ClassName_Helper {
}
class Vendor_ClassName_Helper_Second extends Vendor_ClassName_Helper {
}
without using shorter name for class or constant's names (and making your code impossible to understand, which is something you definitly don't want), no, I don't think there is a way -- at least, not in PHP < 5.3
PHP 5.3 adds namespaces to PHP ; with those, you might be able to come to something shorter / better ; but it means using PHP 5.3, which is not proposed by many hosting companies (5.3.0 was release at the end of june this year, so it might be a while before it's available averywhere...)
For more informations about namespaces in PHP (and to cite only a couple of links) :
the manual
some articles on sitepoint
Migrating OOP Libraries and Frameworks to PHP 5.3 might interest you too
I think there isn't a better way (there isn't a dynamic way):
class LongClassName
{
const B = 3;
}
class LCN
{
const B = LongClassName::B;
}
echo LCN::B;