0

I have a single PHP class, which can communicate with an API we are developing. Rather than using different API versions and letting the user manually updating the PHP class script, I would like to make this process automatic.

This update has to be done on the fly, when an update has been found. This is my idea:

  • The user's program tries to access the API via the PHP class we provide
  • The PHP class checks for an update
  • If there is an update, the class would download the new version of the class, and would like the new version of the class to handle the API request.

This obviously means that the PHP class needs write permissions to the file the class exists in, so it can simply overwrite the class with the new version.

But how can the old class now execute the requested API request through the new class version? In a perfect world, I'm looking for a way to do this without the use of eval(), as this function is blocked on many hosts.

To elaborate:

$myApi = new MyApi;
$myApi->registerCustomer($customerData);

The registerCustomer() function would do something like this:

if (classNotUpToDate) {
    downloadNewClass();
    registerCustomerthroughNewClass()
} else {
    registerCustomerDo();
}

Now the only way I can think of to do this:

  • Download the new class version into a variable
  • In that variable, replace class MyApi {... with class MyApiUpdate {..
  • Include the current file again, loading the new, updated class
  • Create an instance of the new class: $myApiUpdate = new MyApiUpdate;
  • Invoke the registerCustomer() function: $myApiUpdate->registerCustomer($customerData);
  • Overwrite the current file contents with the new class code (without replacements)

Is this the only way to achieve what I want without either creating a new file or using eval()? I don't think this is a very handsome method, so I'm looking for a cleaner way to achieve this.

Basaa
  • 1,615
  • 4
  • 20
  • 41
  • 1
    Rather than having a self-updating class, maybe you could have an update manager that will mediate the procedure because once a class has been loaded, it doesn't matter if you modify the file or not, it can't be re-loaded until the next request or unless you use another framework. – apokryfos Aug 09 '16 at 14:03
  • 1
    Overwriting a core program construct such as a class to facilitate a protocol update procedure is - bad. Why? Because you can run into million problems. However, since you're already here - have you ever used `composer`? – Mjh Aug 09 '16 at 14:04
  • Sadly we are limited to a 1-file library. So we can't have multiple files, and we can't use any libraries like Composer. – Basaa Aug 09 '16 at 14:05
  • Are we talking about CLI or web? – walther Aug 09 '16 at 14:07
  • We're talking about web. – Basaa Aug 09 '16 at 14:07
  • Well, if you can't use a package manager but are able to overwrite a file on the fly... I wouldn't want to be in your shoes, good luck with your project. – Mjh Aug 09 '16 at 14:11
  • The overwriting part shouldn't be an issue, as PHP loads into memory before executing. – Basaa Aug 09 '16 at 14:12
  • What if the updated class changes some of *its* API and your code calling methods on that class is now API-incompatible with the API adapter? Hmmmmmm… – deceze Aug 09 '16 at 14:30

2 Answers2

6

I would consider this approach to be rather profoundly unsafe, and therefore do not recommend it.

Instead, I recommend that your API should require an "API version" indicator be sent with each request: the client informs the server what API version it's using, and the server, in like manner, informs the client what version is responding to it.

Make the versions as "upward compatible" as possible. A newer version of the API can talk to an older version, and, because the two parties self-identify, you know exactly what requests each one can and cannot use. If you invent a new API-call that supersedes an older one, leave both of them in the implementation, even if you "stub out" one of them or have the implementation of the older call invoke the code of the newer one.

Different versions of the API can, if necessary and appropriate, be implemented using subclasses.

You should, as a matter of course, have test-suites for each API version, and you should verify that all of the older tests continue to run correctly against newer versions. That is to say, that there is no "regression."

But, no, no "automatic on-the-fly updates," and no writeable files. As they say: "Not only 'no,' but, 'hell, no!'"

Mike Robinson
  • 8,490
  • 5
  • 28
  • 41
  • Thanks for your answer. This ofcourse would be the correct way of doing things. Sadly though, in my project, it's simply not possible. The problem is that it's not 'just' an API. It's an entire integrated system with control panels, back-ends and complicated algorithms that change constantly. We *cannot* have *any* outdated API requests. – Basaa Aug 09 '16 at 14:19
  • Hmmm... "a prior version" doesn't necessarily mean "outdated." If the two parties communicate with one another exactly what version they are using, and if both parties verify that each is compatible with the other, it should work. – Mike Robinson Aug 09 '16 at 14:27
  • A prior version class is outdated if the software that uses it **requires** new features that didn't exist in the prior version of the API. – Basaa Aug 09 '16 at 14:28
  • 1
    If you need "live updates," then I would suggest creating an initial-handshake call in which the two parties, so to speak, exchange their calling-cards. The server might reply, "I have an update for you." (All other API-calls would refuse to talk to the older version.) The client must now issue a "please send it to me" request. The host replies with a *cryptographically signed* file, which the client verifies and then installs. (Ideally, it would do so by invoking some privileged command that runs under a user-id that can modify the normally-read-only file. – Mike Robinson Aug 09 '16 at 14:28
  • Also notice that by putting this in "an initial handshake," the client can update the PHP, having never `require()`d it yet. ### Yes, and by exchanging version-numbers, the two parties can immediately recognize that an incompatibility exists. They will decline to talk to one another (except for the "initial handshake" and version-update sequence.) – Mike Robinson Aug 09 '16 at 14:30
  • Good luck! Sounds like an interesting project. – Mike Robinson Aug 09 '16 at 14:30
  • Instead of versioning the API, you may also include *capability declarations* in the handshake, i.e. where both parties declare what they can do. Makes it more flexible to implement different clients, if that's a concern. – deceze Aug 09 '16 at 14:42
0

It can be done in simple steps, Below is working POC of the same. Below are the requirements

  1. Server there script/class check for new version and updated code
  2. version.txt file in server should have 1.1 or version higher than 1.0 mentioned in class
  3. MyClass.txt file in server should have the same version that's defined in version.txt file in server.

Example self updating class:

<?php

class MyClass
{
    private $version;

    public function __construct()
    {
        $this->version = 1.0;
    }

    public function checkForUpdates()
    {
        // Get the latest version number from a remote file
        $latestVersion = file_get_contents('https://www.yourwebsite.com/version.txt');

        // Compare the latest version to the current version
        if ($latestVersion > $this->version) {
            // Update the class code
            $this->update();
        }
    }

    private function update()
    {
        // Get the updated code from a remote file
        $updatedCode = file_get_contents('https://www.yourwebsite.com/MyClass.txt');

        // Overwrite the current class code with the updated code
        file_put_contents(__FILE__, $updatedCode);

        // Refresh the class definition
        require_once __FILE__;
    }
}

$obj = new MyClass();
$obj->checkForUpdates();

version.txt code

1.1

MyClass.txt code

<?php

class MyClass
{
    private $version;
    private $author;

    public function __construct()
    {
        $this->version = 1.1;
        $this->author = "Kapil";
    }

    public function checkForUpdates()
    {
        // Get the latest version number from a remote file
        $latestVersion = file_get_contents('https://www.yourwebsite.com/version.txt');

        // Compare the latest version to the current version
        if ($latestVersion > $this->version) {
            // Update the class code
            $this->update();
        }
    }

    private function update()
    {
        // Get the updated code from a remote file
        $updatedCode = file_get_contents('https://www.yourwebsite.com/MyClass.txt');

        // Overwrite the current class code with the updated code
        file_put_contents(__FILE__, $updatedCode);

        // Refresh the class definition
        require_once __FILE__;
    }
}
Kapil Yadav
  • 650
  • 1
  • 5
  • 29