PHP: What options are there to refactor double used classname? - php

On a web project there are two classes that have the same name. This was never an issue until now, because the two classes where never used at the same time / in the same script.
Now we require to use both classes in one script, and therefor got ourselves an "Cannot redeclare class" fatal error.
My question is: What options are there to resolve this issue?
The one possible solution would be to rename one of the classes in question, but it is something I would very much like to avoid - one of the classes is part of a third-party software that should not be modified at this level, to remain updateable.
I know there are namespaces - are they a valid option to this problem? I have never used namespaces until now.
Assuming we would put one of the classes into a namespace: Would this resolve the issue? Also, what measures would we need to take to access the now-namespaced class?

Namespaces would surely solve your problem.
For example I got multiple classes named 'core', but because they're all classes of a different namespace it doesn't give any conflict at all.
This does mean you have to go over all your code and refer to the namespaced class with the correct path.
$item = new doubleClass();
would become
$item = new \my_namespace\doubleClass();
Also make sure that your other scripts don't get namespaced otherwise it wouldn't be able to find the non namespaced class anymore.

How Namespace work:
consider we have 2 classes named User in file structure
/Package1/User.php
with content
<?php
namespace Package1;
class user {...}
and
/Package2/User.php
with content
<?php
namespace Package2;
class user {...}
and now for some reason we decide to use both in some class UserManager in:
/Package3/UserManager.php
with content:
<?php
namespace /Package3;
use package1/User as User1;
use package2/User as User2;
class UserManager {
public function __construct(User1 $user1, User2 $user2) {...}
...
}

Related

Laravel 5 Class extend

I'm a beginner to L5. I read the documentation about extending classes but i didn't find any information about where to put the file in which i extend the class.
**I have to extend Str.php class. I have read that in L4 it had to be done by putting that file under App/folder but i didn't find that folder in L5.
So please can you tell me how can i do that?
This is the information i have now:
First, you must find where the class file is. We will be extending the Str class, which is under vendor/laravel/framework/src/Illuminate/Support. Note that you can also find this class under the aliases key in app/config/ app.php.
Now create a new folder called lib under app/folder. This folder will hold our class extensions. Because the Str class is grouped under the folder Support, it is suggested that you create a new folder named Support under lib too.
Now create a new file named Str.php under app/lib/Support, which you've just created:
But this is for L4
That's more of a general PHP question and there are two parts: 1) How to extend a class and 2) where to put files.
1) Extending classes isn't something Laravel or anyone else provides. That's right there in the language:
class A {}
class B extends A {}
As long as class A exists and is available, then class B can extend from it.
2) Where the files are is also important here. If you're defining class B and want to extend class A, the php runtime needs to know where to find A. Usually class A isn't defined in the same file as class B.
There are many ways to do this. You could require or include class A when you define class B. That would look something like:
a.php
class A {}
b.php
require "a.php";
class B extends A {}
Now with a lot of files like in the Laravel framework or any worthy library, you're going to have a lot of files to include and have to keep track of how to include those files. That's no fun. So, instead of doing that, PHP has provided a way to autoload the classes. That is, if you define classes in a predictable way, PHP can figure out what classes you're talking about without you having to use require or include statements.
There are also many ways of autoloading php files. In Laravel (and many, many other projects), the composer autoloader is used.
This means that files have to be placed in a pre-defined way in order for the composer autoloader to find them. By reading about the composer autoloader and then digging into the code to see how Laravel's classes are autoloaded, you'll be able to figure out how that happens.
Despite the intricate detail of Peter's answer, I figured I'd write something much more concrete: it doesn't matter.
If you check composer.json, you'll see that we are autoloading everything that is placed inside the app directory anyway. Hence, the choice is really yours. All that matters is that you maintain a sensible and readable structure. For example, you could place it in app/Lib, and namespace all your classes App\Lib (if App is your base namespace of course, which can be changed with php artisan app:name). Of course, you could also have a folder like Helpers/Lib for your extended classes, and keep some form of helpers.php with global helper functions in Helpers.
Why would you do this? Well, you might want to have an easy way to call your new Strfunctions, so instead of having to do Str::yourNewMethod($argument) everywhere, you could add a helper function yourMethod($argument) to easier call the function (if you intend to use it extensively).
One thing you have to remember though, as mentioned by Peter, is that the class you are extending won't automagically be found. It will, however, be autoloaded. Hence, to reference it you have to remember to namespace it, such as in the example below.
<?php namespace App\Helpers\Lib
Class Str extends \Illuminate\Support\Str {}
Also remember to namespace correctly when you call your own class.

Laravel namespace issue

I have a laravel 4.1 application, and I've created a folder in my app folder to store most of the logic.
/app/Acme/Models/
/app/Acme/Repositories/
these are the two main folders.
In my composer.json I have this in the auto load, and done a dump run.
"psr-4" : {
"Acme\\" : "app/Acme"
}
However I am getting, what I think are silly issues. For example my Acme/Models/Task.php has the following
<?php
namespace Acme\Models;
class Task extends \Eloquent {
public function job()
{
return $this->belongsTo('Job');
}
}
however when I run this, I get an error
Fatal error: Class 'Task' not found in vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php on line 780
In my Job.php I have the same namespace at the top of the file....
Must I manually import/use object which are in the same name space?
use Acme/Models/Job as Job; ? it seems like such a duplicate...
And in my Repositories folder when I set a namespace of namespace Acme/Repositories;, must I use items like
use Acme\Models\Job as Job;
I is, a bit lost!
Namespaces are relative so you do not need to add use to directly reference classes within the same namespace.
The error you are getting is because you need to fully qualify relationships to namespaced models so eloquent knows where to find them eg
$this->belongsTo('\Acme\Models\Jobs');
In the case of your repository namespace, you will need to add a use statement in your file as you suggested, or reference the fully qualified namespace eg new \Acme\Models\Job();
On a side note, I know PHPStorm (and I'm sure other IDEs) will inject the namespaces for you which is super useful and saves you having to write use or the full namespace out every time you reference a class - worth checking out.
Edit: Sorry, I didn't read the question properly first time around - updated my answer.

Php class declaration inside class method with same names and PSR-0 namespaces, error

I have create my own MVC framework which is PSR-0 compliant atm. and uses php-ActiveRecord as ORM.
I have found an issue where I from a controller called User, which exists in the namespace TapMVC\Application, tries to call an ActiveRecord model ALSO called User but exists in the namespace TapMVC\Db like so:
namespace TapMVC\Application;
use TapMVC\Db;
class User extends Controller {
function index() {
print_r(Db\User::find('all'));
}
}
This gives the following error:
Cannot redeclare class TapMVC\Application\User in
/path/to/project/app/Controllers/user.php on line 12
Where line 12 is the prototype/declaration of the User-controller.
Why can't i do this? I thought that if your classes where i different namespaces and had a namespace prefix on instantiation it would be ok to have the same name? Also it looks like the data-model is declared before the controller through autoloading (since its the user-controller declaration which triggers the error), so why is there a conflict when the data-model is in namespace TapMVC\Db and not TapMVC\Application where the controllers are?
What i can see, that even though you define different namespaces, PHP will declare the object under the same namespace as the one in the active file, but i am not sure.
Hope someone can help so i dont need to name my data models like so: (ProjectName-prefix)User and edit the database tables in order to have a User-model and a User-controller.
I found the error! :-D
The issue lied in my autoloading functions for controllers, models etc. since i didn't take care of namespaces. So when I tried to load a controller or model named the same, the spl_autoload_register function would start off by checking for controllers, and since my functions ignored the classes namespaces it would i both cases of: TapMVC\Application\User and TapMVC\Db\User find the User-controller class, stop the autoload list, and try to declare it.
This all resolved when i took care of the namespaces and made sure that controllers/models only get loaded as long as they are in the correct namespace. If not the function dose nothing and the spl_autoload_register function continues down the list of autoload functions.
The question was more framework specific than imagined and boiled down to autoloading which i didn't think was the problem... for some reason, and for that i am sorry. Just happy know that I found the error.

functions accessible by multiple php classes (Symfony controllers)

I have a couple functions that are used in multiple controllers in my Symfony project. Instead of copy and pasting them constantly, I'd like to have a separate php file (say functions.php) to store the functions.
I've tried the following:
1) include and require the php file with the functions, but it won't let me use $this->getDoctrine().
I've looked at the following posts for help, but it didn't solve the issue:
Symfony2: get Doctrine in a generic PHP class
Symfony2 Use Doctrine in Service Container
2) I've tried making it a class and having the functions as methods then doing:
$f = new Functions();
$variable = $f->myMethod();
But it returns:
The file was found but the class was not in it, the class name or namespace probably has a typo.
Thanks for any help. I really appreciate it.
UPDATE:
The generic class is Symfony/src/WikiRoster/MainBundle/Controller/DBFunctions.php
I just need to be able to use $this->getDoctrine() or the like somehow in there now. I've tried using the services.yml as suggested in the link above, but no cigar. Thanks!
The file was found but the class was not in it, the class name or
namespace probably has a typo.
This issue happens quite often actually. As the error mentions, there are 2 cases explaining why this error is returned:
If the namespace of your file is not the same as the path of your file
If the name of your file is not the same as the name you are using in your class
Example
Let's say you would like to use a GeneralClass in a different class like that:
use Acme\YourBundle\General\GenericClass
For this to work, GeneralClass needs to be in Acme/YourBundle/General/, and you need to:
use the correct namespace: namespace Acme\YourBundle\General
name your class with the same name: class GenericClass
You would have:
namespace Acme\YourBundle\General;
class GenericClass
{
public function __construct()
{
// some cool stuff
}
}

How does the keyword "use" work in PHP and can I import classes with it?

I have a file with a class Resp. The path is:
C:\xampp\htdocs\One\Classes\Resp.php
And I have an index.php file in this directory:
C:\xampp\htdocs\Two\Http\index.php
In this index.php file I want to instantiate a class Resp.
$a = new Resp();
I know I can use require or include keywords to include the file with a class:
require("One\Classes\Resp.php"); // I've set the include_path correctly already ";C:\xampp\htdocs". It works.
$a = new Resp();
But I want to import classes without using require or include. I'm trying to understand how use keyword works. I tried theses steps but nothing works:
use One\Classes\Resp;
use xampp\htdocs\One\Classes\Resp;
use htdocs\One\Classes\Resp;
use One\Classes;
use htdocs\One\Classes; /* nothing works */
$a = new Resp();
It says:
Fatal error: Class 'One\Classes\Resp' not found in C:\xampp\htdocs\Two\Http\index.php
How does the keyword use work? Can I use it to import classes?
No, you can not import a class with the use keyword. You have to use include/require statement. Even if you use a PHP auto loader, still autoloader will have to use either include or require internally.
The Purpose of use keyword:
Consider a case where you have two classes with the same name; you'll find it strange, but when you are working with a big MVC structure, it happens. So if you have two classes with the same name, put them in different namespaces. Now consider when your auto loader is loading both classes (does by require), and you are about to use object of class. In this case, the compiler will get confused which class object to load among two. To help the compiler make a decision, you can use the use statement so that it can make a decision which one is going to be used on.
Nowadays major frameworks do use include or require via composer and psr
1) composer
2) PSR-4 autoloader
Going through them may help you further.
You can also use an alias to address an exact class. Suppose you've got two classes with the same name, say Mailer with two different namespaces:
namespace SMTP;
class Mailer{}
and
namespace Mailgun;
class Mailer{}
And if you want to use both Mailer classes at the same time then you can use an alias.
use SMTP\Mailer as SMTPMailer;
use Mailgun\Mailer as MailgunMailer;
Later in your code if you want to access those class objects then you can do the following:
$smtp_mailer = new SMTPMailer;
$mailgun_mailer = new MailgunMailer;
It will reference the original class.
Some may get confused that then of there are not Similar class names then there is no use of use keyword. Well, you can use __autoload($class) function which will be called automatically when use statement gets executed with the class to be used as an argument and this can help you to load the class at run-time on the fly as and when needed.
Refer this answer to know more about class autoloader.
use doesn't include anything. It just imports the specified namespace (or class) to the current scope
If you want the classes to be autoloaded - read about autoloading
Don’t overthink what a Namespace is.
Namespace is basically just a Class prefix (like directory in Operating System) to ensure the Class path uniqueness.
Also just to make things clear, the use statement is not doing anything only aliasing your Namespaces so you can use shortcuts or include Classes with the same name but different Namespace in the same file.
E.g:
// You can do this at the top of your Class
use Symfony\Component\Debug\Debug;
if ($_SERVER['APP_DEBUG']) {
// So you can utilize the Debug class it in an elegant way
Debug::enable();
// Instead of this ugly one
// \Symfony\Component\Debug\Debug::enable();
}
If you want to know how PHP Namespaces and autoloading (the old way as well as the new way with Composer) works, you can read the blog post I just wrote on this topic: https://enterprise-level-php.com/2017/12/25/the-magic-behind-autoloading-php-files-using-composer.html
You'll have to include/require the class anyway, otherwise PHP won't know about the namespace.
You don't necessary have to do it in the same file though. You can do it in a bootstrap file for example. (or use an autoloader, but that's not the topic actually)
The issue is most likely you will need to use an auto loader that will take the name of the class (break by '\' in this case) and map it to a directory structure.
You can check out this article on the autoloading functionality of PHP. There are many implementations of this type of functionality in frameworks already.
I've actually implemented one before. Here's a link.
I agree with Green, Symfony needs namespace, so why not use them ?
This is how an example controller class starts:
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class WelcomeController extends Controller { ... }
Can I use it to import classes?
You can't do it like that besides the examples above. You can also use the keyword use inside classes to import traits, like this:
trait Stuff {
private $baz = 'baz';
public function bar() {
return $this->baz;
}
}
class Cls {
use Stuff; // import traits like this
}
$foo = new Cls;
echo $foo->bar(); // spits out 'baz'
The use keyword is for aliasing in PHP and it does not import the classes. This really helps
1) When you have classes with same name in different namespaces
2) Avoid using really long class name over and over again.
Using the keyword "use" is for shortening namespace literals. You can use both with aliasing and without it. Without aliasing you must use last part of full namespace.
<?php
use foo\bar\lastPart;
$obj=new lastPart\AnyClass(); //If there's not the line above, a fatal error will be encountered.
?>
Namespace is use to define the path to a specific file containing a class e.g.
namespace album/className;
class className{
//enter class properties and methods here
}
You can then include this specific class into another php file by using the keyword "use" like this:
use album/className;
class album extends classname {
//enter class properties and methods
}
NOTE: Do not use the path to the file containing the class to be implements, extends of use to instantiate an object but only use the namespace.

Categories