Overriding 2 functions in vendor package class - php

I'm currently trying to retrieve the SMTP Queue-ID when using the Laravel (5.6) Mail class.
I have copied the file vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php to /app/OverriddenAbstractSmtpTransport.php and made an alias in config/app.php, made my changes:
1:
on line#395 I added return in front of the line, so we obtain the output
2:
line#492 replaced with $message->queue_ids[] = $this->streamMessage($message);
So I can access queue_ids from the message property in the Illuminate\Mail\Events\MessageSent-event
Now this works, but I don't think it's a very safe approach to modifying the vendor class, as it might cause a breaking change when running security updates.
Is there a simpler/better/safer solution to this ?

Copying the whole class is risky - if any updates are done to the vendor class in a newer version, they'll never make it into your copy. A safer way is to extend the original class and overwrite those 2 functions. There is still a risk of some changes being done to those functions in vendor class, but it's much lower now. Another option would be to extend the original class and add new methods - they will have access to all public and protected properties/methods of the original class and that could be enough to get you what you need.
Whatever version you choose, you'll need to later register the new class as a new driver/transport for Swift. Check the following snippet for an example: https://gist.github.com/maxwellimpact/46ded5c553f68946d13d

Related

What is the best practice for using two namespaces in a single file for backwards compatibility?

I need to refactor a PHP project where the vendor has undergone a re-brand. The project currently uses the namespace OldCompany, and I need to change this to NewCompany, however I've realized I need to keep the old namespace for backwards compatibility, in the cases where existing users are using try {} catch (/OldCompany/Exception $e) {}... If I simply change the namespace to NewCompany, I will break their integration if they upgrade SDK versions straight up. After reading the PHP Namespace docs, I tried the method outlined in Example #3, and modified all of my files like this:
<?
namespace NewCompany{ /* no namespace-specific code needed */ };
namespace OldCompany{ /* no namespace-specific code needed */ };
namespace {
/* global namespace code. code that applies to both namespaces? */
require_once('file1.php');
require_once('file2.php');
/* classes and functions within the global namespace */
}
The above throws a PHP Fatal Exception and can't find the NewCompany namespace.
I definitely do not want to duplicate code as per Example #2 of the docs, since there isn't namespace-specific code.
What is the best way to preserve the existing namespace of OldCompany for existing users while refactoring a re-brand to `NewCompany' for new users? Should I be looking for a different solution to this problem?
Thanks in advance :)
namespace NewCompany{ /* no namespace-specific code needed */ };
namespace OldCompany{ /* no namespace-specific code needed */ };
This is setting namespaces. But surely your issue is old vendor namespace has changed to a new one? This means you need to import (use) the new namespace instead of the old one?
Maybe I've misunderstood you, but are you confused about the difference between setting and importing namespaces? If the vendor has changed to a new namespace then you need to import the new one, rather than the old one. But this has nothing to do with setting namespaces.
I definitely do not want to duplicate code as per Example #2 of the
docs, since there isn't namespace-specific code.
If there's no namespace specific code then what problem are you trying to resolve?
I need to keep the old namespace for backwards
compatibility, in the cases where existing users are using
try {} catch (/OldCompany/Exception $e) {}.
Surely whatever namespace they have in their end won't affect your side of things? So you could update all your code's namespaces and not worry about what they use? They just call your endpoint or whatever as normal?
Perhaps be more specific about that if it's a real issue somehow.
It sounds to me like you just need to update your import statements for the vendor's new namespace.
Something else to consider is refactor how you manage vendors.
I presume you are not using a pre-made framework, such as Symfony (they have predetermined ways of managing vendors and things).
The fact you are considering refactoring throughout your code rather than a single config file (or whatever) makes me think your code has a design flaw. As it seems you are changing code (namespace) within your class files based on a 3rd party company (vendor) changing their name. And where possible your code should be entirely abstract from such changes to this degree.
I suggest considering abstracting things out into centralised places whenever it makes sense. This allows the one centralised thing to be altered and changes just automatically ripple down to all your code without any need for a huge refactor.
You could make your own generic names for your vendors so whatever they call themselves doesn't matter in your code.
E.g. vendor FunkyJoesEmailer in your app will just be Emailer. Then whichever emailer library you decide to use now and in the future will be in the same Emailer DIR and namespaces won't change, always be Whatever\Emailer.
Then in some file high up the load chain you'd have some wrapper class (or service or container like thing) which would load FunkyJoesEmailer in whatever namespace that is in via your generic name, such as $this->Emailer. So in your code you'd call on $this->Emailer which would return an instance of whatever emailer (vendor) you are using.
If you ever needed to do a namespace change or even entirely swap out the Emailer vendor you use, the change is in one place and would ripple down in your code because it's still $this->Emailer.
While this approach doesn't resolve your having to change everything now, it does mean you only ever have to change it this once. Then in the future can just replace vendors or let their renaming happen within their code and your path (namespace) to it remains the same.

PHP 4 class autoloader

I'm working in PHP 4 with classes but there's not __autoload function, so I have problems to load my classes because they are intertwined.
I have a class Ship and a class Movement. The class Ship contains an Movement object and the class Movement contains an Ship object.
So when I do the require Ship, the class is read and throws error when reach the new Movement and conversely.
Some solution? ;)
PHP 4 is really, really old and not-supported. The best option is to move to PHP 5.
If you can't, create a bootstrap file, which requires all class definitions (in the correct order in case of inheritance); make sure the class definition files contains only class definition (and not executable code like $obj = new Movement) and require this file in each file you are actually running in your application.
The point is, the class definition of Movement is not needed before the new Movement statement, and if this statement is in some Ship's method (even if it's in the constructor), you can safely load the Ship.php, then Movement.php, then run the code and it will work.
Also, make sure to load all class definitions before starting the session, if you are using sessions and serialize objects in it.

Is it possible to make PHPStorm include necessary files automatically?

I have a project with lot of files. Normally each file contains one class definition. Currently when I need an instance of object I use loader, which includes necessary file and instantiates it. Such approach though doesn't allow IntelliSense to work properly. And I prefer more readable new MyObject() than $loader->load("MyObject").
I use PHPStorm IDE. Is it possible to configure it to add necessary require_once("some_file.php") when I use appropriate class type?
Solution 1:
Switch to using the autoload feature of PHP (5+) and then use new MyObject():
http://www.php.net/manual/en/function.spl-autoload-register.php
Solution 2:
Use a live template defined like this:
/** #var $CLASSNAME$ $VARNAME$ **/
$VARNAME$ = $loader->load("$CLASSNAME$");
You can then choose the class name and the vriable name each time you use the livetemplate.
The feature to add use statements automatically will be available in PhpStorm 5.0.
If it's a specific project you could always create a template wich includes all you're crap! Ten when you do file-new it will give you a file based on that template.

Converting from CodeIgniter 1.7.3 to 2.0+

Hello and thanks for reading.
I'll get straight to the point: I have a website project that I've been building using CodeIgniter 1.7.3, which I have thoroughly enjoyed using, however I have been contemplating upgrading to CI 2.0+.
I tried a straight copy, just moved my folders for controllers, models and views over to a CI 2.0 framework, but I got a 500 server error when I tried to view my pages.
After doing some investigation I discovered that all of your controllers must now use "CI_Controller" as their parent class. Also I noticed that if you want to include a constructor in your controller class that it must use the syntax "function __construct()" as its name and of the parent class. It seems that CI 2.0+ no longer supports using a constructor that has the same name as the class name, e.g. "class Blogs extends CI_controller{ function Blogs(){parent::__construct();}}" is no longer supported?
I've been reading the CI Change Log, but all I see are bug fixes, and new features, nothing about compatibility issues with older versions of CI?
Does anyone else know of any other secret little pitfalls?
Thanks,
H
CI 2.x removed all compatibility with PHP4 and also updated a number of standards to be compliant with PHP 5.3 going forward. One of these is the constructor issue you have encountered. As of PHP 5.3, the function ClassName() is no longer the constructor for a class, it is simply another function. You must explicitly declare a __construct function to perform any tasks that need to be done when a new instance of the class is created. Given this, you should see it no longer makes sense to call parent::ClassName() in your child constructor as that function would no longer be the parent's constructor.
Another pitfall I recently had to work out is how the $_GET array is now handled. In the 1.x versions, you could use query strings to pass back extra information and still use URI segments to route to controllers and functions. This is especially useful for AJAX calls where you may not always know all the parameters being sent to and from the server in a particular request. In the 2.x versions, the config.php file contains a new option, $config['allow_get_array']. This must be set to TRUE if you want to use query strings otherwise the input class clears out the $_GET array as part of CI's initialization routine on each request.
Something which isn't a pitfall but you may find useful is the new options in config/autoload.php that allows you to add new application directories to your project. If you work on a number of different projects with CI and want to keep any useful libraries you write in a single location, you can now add that location to $autoload['packages']. CI expects any path in this array to contain the sub-directories "controllers", "models", "libraries" and "helpers". It won't complain if you don't have those directories but you will at least need them for anything you intend to load, i.e. libraries would live in /libraries as with the main application folder.
Have you read the official guide for upgrading from 1.7.x to 2.x ?
so in short
Update Models and Controllers to
extend CI_Model and CI_Controller
Update Parent Constructor calls
class Wow extends CI_Controller {
function __construct()
{
parent::__construct();
//your stuff
}
function index()
{
// just for example
}
}

php extending classes

I'm using a salesforce class called SforceEnterpriseClient. I've referenced that class many places in my application. I want to extend that class to give it the ability to return a single array from a 1 row recordset, right now the record set is about 3 levels deep. There's a few other things I want to do with it as well. I can handle all that.
Everything I've read about classes says that when I extend a class, I need to call the new one as such:
class MySF extends SforceEnterpriseClient {};
$mySforceConnection = new $MySF;
That means in all of my existing code I have to find/replace.
Is it possible to overwrite the parent with the child so I don't have to play the find/replace game?
class SforceEnterpriseClient extends SforceEnterpriseClient {};
$mySforceConnection = new $SforceEnterpriseClient ;
You can probably play some classloading tricks with the magic __autoload() function and removing references to the salesforce file ie. require, require_once, include, include_once; But in the interest of readability and maintainability, you should probably take the long route here and modify all your references to use the subclass.
How about this, in the source file for the class, rename the class (and most likely the constructor as well) then extend the class using something like
class SforceEnterpriseClient extends renamedClass {};
Then rename the file and create a new file with the old name and include the renamed file. Put the code for your extended version in the new file. The final result is that every file that was using the original will see the new version without having to track them all down.
About the only major issue would be what happens when a new version of the class becomes available.
Unfortunately, that would be the only way to do so. You cannot reverse inhertiance. Sorry and good luck!
Kyle
Maybe you do not need to extend the class in this scenario. You extend a class when you want to add new functionality or change existing functionality AND keep the original class intact. Usually this is the way to go. But, if you need to make a change to an existing class and then update all references to the class to refer to the new class why not simply change the class code itself? That way the references would not have to change.
Also have a look at the factory pattern. Normally you should keep class creation and business logic separate.
So when you come across a problem like this, you only have to go and change the factory...
$sfEnterpriseClient = Factory::getSFEnterpriseClient($params);
that way you can avoid 'new' constructs in your business logic, and makes your code more manageable

Categories