php override vendor package class method - php

What is the proper way to change a package installed through composer in the vendor folder.
A: copy the package directory to the project source (so it is not changed by composer anymore) and use it from there.
B: Extend the class.
I would think that B is the proper way, but i see two problems with it.
1. The extended class may not be compatible with an updated baseclass from the vendor directory. (after "composer update")
2. How do I manipulate a method way down in the class chain.
$classA = new ClassA();
$classB = $classA->getsomeOtherClassInstance();
$classB->methodToChange();
I.e. I would have to change class A and B to make sure that the extended version of B is called.

The extended class may not be compatible with an updated baseclass from the vendor directory. (after "composer update")
The class should always be backwards compatible if you choose the correct version number in your composer.json file. Take a look at https://getcomposer.org/doc/articles/versions.md (Keep in mind this only applies for code repositories that promise backwards compatibility, but most big names do.)
Also, if you want you can choose a concrete version like for example '3.7.7' which would prevent composer update from updating your code, will always use that version. But better to do the above.
How do I manipulate a method way down in the class chain.
If it is a private method then you were not meant to change it, better to look for its caller and override that method and class instead. Also, take a look at Decorator design pattern it allows you to change behavior of methods dynamically on the fly.

Related

PHP: Using a namespaced class within several other namespaces at the same time

I'm developing two WordPress plugins (exclusively for internal use) that both use the same set of PHP classes. The classes are identical except for their namespaces, and at the moment I've just copied the files from one plugin to the other, changed the namespace, and everything works fine.
Soon though I'm going to be developing more similar plugins that will also need these files, so I'd love to put them in a git submodule and just add them to any project that needs them. The problem is that multiple plugins needing them may be used on the same site, so if I move the classes to a submodule and give them their own namespace, then all of the plugins that use them would be trying to load the exact same classnames from the exact same namespace.
Is there a way to "reset" a class's namespace or re-name it somehow when it gets loaded, without editing the original file?
At the moment I have:
SUBMODULE:
namespace Submodule;
someClass {}
PLUGIN A:
namespace PluginA;
use \Submodule as PluginA_Submodule;
$classA = new PluginA_Submodule\someClass();
$classA->someMethod();
PLUGIN B:
namespace PluginB;
use \Submodule as PluginB_Submodule;
$classB = new PluginB_Submodule\someClass();
$classB->someMethod();
The site is loading, but only the class that loads first gets used -- i.e. Both plugins run someMethod() from plugin A.
Ideally what I want is for each project to use its own version of the files containing the exact same namespace and class names, and I suppose I should mention that each of the plugins are using Composer's autoloader to load them, so I had to add the Submodule namespace and the submodule directory to the autoloader array (in composer.json) and dump-autoload to get this far. This is the part that may actually be the problem, but I'm not sure it would work if I wrote my own autoloader anyway...
Given that the classes are identical I normally wouldn't worry if the plugins all use the same one, but if I make a change to the submodule I might not immediately be able to update it in every plugin on every website, and obviously it'd be a problem if an older plugin is using a newer version of the class from a different plugin.
One thought is that I could potentially not give the submodule classes any namespace at all, if there's a way for them to inherit the current namespace or something when each plugin uses them. Not sure how the autoloading would work but I could just manually load those classes if needed.
Anyway, hope this makes sense and thanks for any help.

Loading and using PHP class without namespace and without using 'use'

Is it possible to create a package for a class without a namespace, and instantiate it in my scripts without using the language construct use?
eg:
<?php
require_once 'vendor/autoload.php';
// use \Something_MyClass; (I don't want to use this 'use')
$mc = new MyClass(); // instead of $mc = new Something\MyClass()
?>
Copy of my comment below, just to clarify:
"This is because I am still using php 5.2. For legacy reasons I can't upgrade php to 5.3 or beyond. I can't use namespacing, but I want to use composer for autoloading and dependency manageament."
It's not possible.
Even if you register with spl_autoload_register a function that, upon new MyClass(), will include a file with class Something_MyClass, that same line will fail, because you're trying to instantiate a class that doesn't exist.
Your best course of action is to upgrade your PHP to 5.3 (although the lowest supported version as of now is 7.1, and I highly recommend going there). You could, of course, create your own package manager (as YvesLeBorg suggested in comments), but that will get you even deeper into legacy, and make it even harder to maintain and upgrade PHP in the future, not to mention potential bugs and extra maintenance overhead.
You can perfectly use PHP 5.2 syntax in your source code and combine it with Composer, even with Composer PSR-0 autoloading. Composer does not enforce any rules regarding the PHP version.
The only thing that you cannot work around is that Composer itself requires PHP 5.3.2 to run. But everything else is up to you.
In more details, your only option for "namespaced classes" with PHP 5.2 is to use the now dreaded Under_Score_Class_Name variant, which can be autoloaded with PSR-0. You'll always have to use the full classname (use and proper namespaces would allow you to use shorter names). That example classname must reside in the path /Under/Score/Class/Name.php and would be autoloaded with "autoload": {"psr-0": {"Under": "/"}}. You have no way to shorten that path with PSR-0 (one of the reasons PSR-4 was invented), it must always contain every word between underscores as a folder level, starting with the first word. The only thing you can change is that Composer does not require you to use a folder in the main directory of the library. You could use the path src/Under/Score/Class/Name.php and autoload with "psr-0": {"Under_": "src/"}.
Running Composer with a PHP version 5.3.2 or greater probably boils down to a) installing it and then b) explicitly point to this version when running Composer, like /usr/local/bin/php7.2.23/php composer.phar install.
In case you don't want to use long paths, simply using class names in the root namespace is allowed - note however that they must not duplicate existing class names from PHP itself. Without a common class name prefix like Under_ from the example above, your autoloading could be "psr-0": {"": "src/"}. Note however that this tells the Composer autoloader that every class may be found in your library. Composer will keep track of where it found something and not try again anywhere else, but it will still affect performance. So it is best to go with the common prefix.

How to define a private class path with PHP Composer?

Is it possible to define a separate PSR-0/4 path for classes/interfaces that can only be loaded by the package itself (not other packages that include it)?
The idea being it would stop other packages from mistakenly using classes that should be private to the package.
I realise that they can always include the file manually and the class loader is global. The goal is not to prevent them from using the class, but rather make it clear that they shouldn't be accessing it directly now or if the API of the package changes.
Use case:
Let say there is a Person class in the package (already released and used). When a new version of the package arrives we no longer want them to invoke new Person, but instead use a PersonFactory (as it has to setup some other stuff with the person that the caller does not need to worry about).
Yes, you could document this in the change log. However an IDE and static analysers would not be able to report on this. The bug would only be discovered when the improperly initialised Person is given to a provider and the program crashes or throws an exception at runtime.
Based on Alexander's response, this seems the most reasonable:
https://repl.it/repls/GrowlingInconsequentialFanworms
I can't see how this would be possible, because the autoloader loads them into their defined namespaces on their first usage. Once in a namespace, anything can use it by accessing that namespace.
PHP namespace to not have any way of limiting what other namespaces can access them, so your answer is probably no.
What you could do though is put your private classes in a namespace that tells developers they are private:
use yourpackage\private\SomeClass;
It won't stop them, but it could make it clear they should not do that.
I don't think it's possible. spl_autoload_register() takes only $class as argument. You can't register autoload only for a part of application. Moreover there is no way to know which class invokes or creates object of the other classes. Even if you wrote your own autoloader you won't get all information that you need.
If you look for securing your code look at PHP extensions.
Am not sure if this is what you talking about but the php keywords private and protected should help a bit when the classes and interfaces are in a namespace

Can individual Composer dependencies be suppressed from (auto)loading?

I have a project containing, amongst others, the following composer.json dependencies:
"propel/propel1": "dev-master"`,
"halleck45/phpmetrics": "dev-master"
I recently did a composer update and found that a new version of a library required by PhpMetrics, called Hoa, introduces a new class \EngineException to emulate a new PHP7 class. Unfortunately Propel 1 also defines \EngineException, and so a conflict results.
The correct fix for this would be to upgrade to Propel 2, which uses namespaces. However this is still in alpha and is subject to BC breaks, so is not really workable for me.
My present fix is to lock Hoa to a specific version that does not have the new class:
"hoa/core": "2.15.04.*"
That's not a bad solution, but it isn't entirely satisfying to lock a library to an old version.
In the Hoa code, the only way for the new class not to be loaded is to be running PHP 7, which is again not feasible. However, it also occurs to me that Hoa only needs to be required when PhpMetrics runs. This is a stand-alone code analysis tool and only sits in the root of the project for convenience; the rest of the project does not use this library.
Thus, it would be great if I could call something in Composer to ask that this class is not (auto)loaded, or perhaps something to do the same in the composer.json. It is being needlessly loaded at present - I don't know whether it is being autoloaded incorrectly or whether it is being required manually by Composer.
It may help to know that Hoa classes have been added by Composer to the auto-generated autoload_psr4.php script. As far as I can understand the docs, this means it is autoloaded, and there is nothing in my project that would require any of the Hoa classes.
Fixed by https://github.com/hoaproject/Core/commit/8ed00fe9345c4f8b2679a256926d6d24994ea842.
The new exception architecture introduced in PHP7 [1] has been totally
redesigned [2]. This patch updates the retro-compatibility classes
according to this new architecture. Consequently, the BaseException
class has been removed, along with EngineException and
ParseException. While these latters could be implemented (not as
is), we prefer to, so far, only implement the Throwable interface.
Let see if we can implement (still for the retro-compatibility) the
Error, TypeError and ParseError class.
[1]: https://wiki.php.net/rfc/engine_exceptions_for_php7
[2]: rfc/throwable-interface
I was curious, so I looked it up. Hoa indeed has a broken approach, having the file Core.php always included, by a "file" autoload in composer which in turn includes Consistency.php. The latter defines your class.
You could raise an issue with the developers at Hoa, to use class_exists to check for the method rather than the currection version check they are using. This could cause the propel autoloader to load its own. Another way would be to define their autoloading correctly, but they prefer to load manually as it seems.

How does the PHP 'use' keyword work (in Symfony 2)?

In Symfony 2, all requests are getting routed through app_dev.php (or app.php).
The first few lines look like:
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Debug\Debug;
...
In theory, I get that this just imports the specified namespace (or class) to the current scope. What I don't understand is how PHP maps this to a file. For example, the Debug class is located in:
vendor/symfony/symfony/src/Symfony/Component/Debug/Debug.php
How does PHP know to look in vendor/symfony/symfony/src/?
I'm probably misunderstanding what is happening, but any clarification would be appreciated.
Knowing which file a class lives in isn't the job of the language, it's the job of the autoloader.
All the use keyword does is this instance is create an alias:
use Symfony\Component\HttpFoundation\Request;
This is saying in the following script, when I refer to Request I really mean Symfony\Component\HttpFoundation\Request. If I use Request object in some way (by either creating an instance or calling a static method on it) then the autoloader will go and try to load the file that class is defined in if it hasn't been loaded already.
The inner workings of the autoloader, though, is different from project to project. There has been a move to standardize autoloader behavior à la PSR-4, but there's nothing in the language saying that you have to adhere to that standard. You could, for instance, have a scheme where all class files reside in a single directory and your autoloader just loads all classes from there, regardless of what namespace they're in.
That scheme would suffer from the fact that you could only have a single class of every name, but there's nothing stopping you from doing it that way.
To answer your question: "How does it know that "Symfony\Component\Debug\Debug" is a valid namespace?"
It doesn't, because it's not actually going out and trying to load that class at this point. If in any random PHP script you put something like this at the top:
use \Non\Existant\ObjectClass;
But don't ever actually use ObjectClass anywhere, you will get no errors. If you try to say new ObjectClass, though, you will get a class not found error.
Simplistically speaking, you have to load all the files beforehand into memory for PHP. PHP does not inherently have any standards to where files are located, the rules for loading files has to be done by your code.
When PHP attempts to load a file, it will check its memory if the class definition exists, if not it will attempt to autoload the file that may contain your class definition; see PHP: Autoloading Classes and spl_autoload_register
Some common autoloading strategies have been defined by the PHP Framework Interop Group:
PSR-0 (Autoloading standard)
PSR-4 (Improved Autoloading standard)
In this case, autoloading is done by Composer, all you need to do is include vendor/autoload.php in any of your scripts and vendor code downloaded via composer can be autoloaded. You can manually add classes into the composer autoloader as well. See: Composer Autoloading

Categories