PHP How to get proper case class name - php

class Google_Model_SomeThing { ... }
$className = 'google_MODEL_something';
I can create instance of class base on $className. But what if I would like to get proper case class name from $className without creating instance?
I expect something like this:
echo func('google_MODEL_something'); // Google_Model_SomeThing
or
$className = 'google_MODEL_something';
echo $className::class; // Google_Model_SomeThing

Reflection is most likely a better option, but here is an alternative:
You could use get_declared_classes after calling class_exists($wrongCaseName) and find the class name in the array of declared classes.
Example:
$wrongCaseName = 'Some\classy\THIng';
class_exists($wrongCaseName); //so it gets autoloaded if not already done
$classes = get_declared_classes();
$map = array_combine(array_map('strtolower',$classes),$classes);
$proper = $map[strtolower($wrongCaseName)];
Performance
The reflection-based method, noted by l00k, is significantly faster, by about 3 times. I ran this version vs the reflection version on 500 different classes with randomly generated names & no code within them. The reflection-method took about 0.015 seconds to get the proper case for 500 classes, and my get_declared_classes method took about 0.050 seconds for 500 classes.
More details on my personal website. Tested on PHP 7.2.19 on my localhost server on my laptop.

I found code below working, but is it the simplest solution?
$className = 'google_MODEL_something';
$reflection = new ReflectionClass($className);
echo $reflection->getName(); // Google_Model_SomeThing

Well I would suggest to create an instance and then get the className using get_class().
$bar = new $classname();
$caseClassName = get_class($bar);

Related

How to tell if a class is an internal class or a user class?

Is there a way in PHP to tell (programmatically, obviously) if a given class is an internal class (such as DateTime) or a user class (class MyClass)?
In case you wonder (and I'm sure you do), this is because ReflectionClass::newInstanceWithoutConstructor() throws an exception when used on internal classes, and as I'm writing a library to deep-copy objects, it must skip these internal classes.
Yes, I could just catch the ReflectionException, but this exception is thrown for other reasons as well (such as a non-existing class), and is not thrown for all system classes. so it's not exactly fulfilling my needs.
A cleaner solution than using shell_exec whould be to use reflection:
$reflection = new ReflectionClass('SomeClass');
if($reflection->isUserDefined()) {
// 'SomeClass' is not an PHP internal
}
Instead of an string ('SomeClass') you can also pass an object. For more information lookup Reflection and
ReflectionClass::isUserDefined() in the PHP Manual
Interesting question, one way I can think is by checking the namespace, for example all of your classes would be defined under namespace MyApp and then check:
if(class_exists('\\DateTime')){
continue;
}
Kind of ugly, I know.
Food for thought, based on Дамян Станчев's suggestion:
You could just run a PHP interpreter via shell_exec() that will spew out get_declared_classes(). Capture the output of that, and you should have a "clean" list of system classes.
Extending Mogria's answer, this one should work just fine (don't give me credit for this though, as it was Mogria's answer that got it right ;-)):
function getUserDefinedClasses() {
return array_filter(get_declared_classes(),
function ($class) {
$reflectionClass = new ReflectionClass($class);
return $reflectionClass->isUserDefined();
});
}
You should be able to imitate the reflection behaviour by extending the class you're trying to copy, and overriding the __construct function:
<?php
class MyClass extends ExtendingClass {
public function __construct() {
/* Override default constructor */
}
}
?>
Which could essentially be made dynamic by using eval:
<?php
function newInstanceWithoutConstructor($class) {
$className = $class . "Extended" . rand(0, 99999999);
while (class_exists($className)) {
$className = $class . "Extended" . rand(0, 99999999);
}
eval("class " . $className . " extends " . $class . " { public function __construct() { } }");
return new $className();
}
$newInstance = newInstanceWithoutConstructor("DateTime");
?>
HOWEVER: Using eval can be useful in this case, but also reveals a rather large security-hole if anything user submitted can be submitted in any way to change the contents of $class. If you understand these limitations, and security implications, you should be able to use this.
Can't you use get_declared_classes() in the beginning of your script, store the data in an array and then do an array_diff() with the stored data and the response from get_declared_classes() and check if the class you're checking is in the difference using in_array() ?
This example prints out all the classes with your classes seeming to be at the end of the list. Maybe this can help.
What about storing calling get_declared_classes() data before any autoloading/include/require is made and later checking class name in this storage?

Dynamically creating classes

I have been using active record for quite a while, and I wanted a little change of scenery, some developer friends suggested looking into ORM, all of the ORM projects I have looked at require a separate class extending the ORM class.
My question is: is there any way to dynamically create a class using PHP from within a function without eval?
This is what I have:
<?php
class Constructor
{
function new_class($class)
{
$myself = get_called_class();
eval("class {$class} extends {$myself} { }");
}
function say_hi()
{
$class = get_called_class();
echo "Hi, {$class}!";
}
}
$constructor = new Constructor;
$constructor->new_class("Greeter");
$greeter = new Greeter;
$greeter->say_hi(); // Hi, Greeter!
But, my client informs me that eval is blocked on his environment due to him being on shared hosting.
You probably don't want to do that. But as a workaround, you could use the same approach as via eval(), but once you have constructed the string which you would feed to eval you just write it out as a file and include it again.
Something like this:
function my_eval($str)
{
$filename = uniqid().'.tmp';
file_put_contents ($filename, $str);
include $filename;
unlink ($filename);
}
I've written this from memory and not tested it, but I think it should do the trick. Only caveat I'd see right now is that you'd still essentially be doing the same as eval(), and this variant wouldn't allow you to create variables in the same scope as the calling context (although you could use $GLOBALS[] to get around that for global scope variables).

Differences in these 3 way to instantiate an object dynamically?

Out of curiosity, what's the difference (if any, e.g. performance) of creating instances in PHP using one of the following way?
class MyClass { }
// Direct
$name = 'MyClass';
$instance = new $name;
// Using ReflectionClass
$reflector = new ReflectionClass('MyClass');
$instance = $reflector->newInstance();
// Really don't know if it's going to work
$instance = call_user_func(array('MyClass', '__construct'));
Direct is "the normal way"
Using ReflectionClass is what you would do if you your program had to figure out classes etc on the fly - no need to do this in most cases. It will typically be a bit more resource hungry and slower (perhaps not noticably)
Not sure about the 3rd one - falls into the KISS principle - Since "Direct" works, I've never got into such a twisted situation as to even come up with that 3rd approach.

A potential PHP memory hog or not?

Hey, i'm in need of advice. Is this little function i wrote "good" or is it going to be a resource hog?
It's used in the following way:
$login = load_class('LoginClass');
or
load_class('LoginClassTwo', false); // for singletons and stuff
$loginTwo = LoginClassTwo::getInstance();
Here's the function
function load_class($class, $instantiate = TRUE){
static $obj = array(); // holds the instancec of the classes
$l = strtolower($class); // the name of the file is the name of the class but lowercased
if (isSet($obj[$l])) { // Do we have an instance?
return $obj[$l];
}
$file = 'classess/' . $l . '.class.php';
if (file_exists($file) && is_readable($file)) { // Can we read the file?
include $file;
if ($instantiate == FALSE) { // Do we need to instantiate?
$obj[$l] = TRUE;
} else {
$obj[$l] = new $class;
}
return $obj[$l];
}
return FALSE; }
I'm concerned that this method is ineffective and it's going to consume too much memory or am i wrong? And is there a better practice for this?
This is a common pattern, known as a registry, or service locator.
There can be an issue with a global registry of objects, in that these objects are not reclaimed until the script ends. If one of those objects uses a lot of memory, then there you go. However, in it self, this isn't a problem, memory wise.
You should consider which objects you want to hold on globally though. It's a generally accepted truthism that global objects contribute to the overall complexity and coupling of a program. Maybe you could pass some of them as parameters in the constructor, rather than addressing them globally? That entirely depends on the use case of course.
Lastly - php has a feature called autoload, whereby it will load a class from file, if it's not already defined. You should hook in to this instead of putting the logic in your registry.
I don't see any memory hog kind of thing in your code.. If you experiance any such thing. it is probably the class you are loading
PHP has a native __autoload function for classes. it is run any time you attempt to create a new object from a class that does not yet exist. the function will try to include a class file using the name of the class as the class file. this is why many projects use one file per class. this way you never need to manually load a class again, and classes never get loaded unless needed. See the following link.
http://www.php.net/manual/en/language.oop5.autoload.php

"__class magic method" (mediating references to class names by a custom code)

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

Categories