afragen / wp-dependency-installer

A lightweight class to add to WordPress plugins/themes to automatically install plugin dependencies.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Backward compatibilities notices

Raruto opened this issue · comments

In the case of multiple "running" instances, I think it would make sense to notificate in some way the possible presence of incompatibilities, eg:

if ( 
  class_exists( 'WP_Dependency_Installer' ) && 
  WP_Dependency_Installer::instance()->version <= '2.0'
) {
  WP_Dependency_Installer::instance()->notices[] = [ 
   'message' => 'Another plugin is using a previous version of this library, this may cause some incompatibility issues, if you find some errors...'
  ];
}

There's no way to know which version would be loaded first. That's why we use semver and it's up to users to composer update in their code.

There's no way to know which version would be loaded first. That's why we use semver and it's up to users to composer update in their code.

I was thinking in adding something like the follow:

<?php
/**
 * WP Dependency Installer
 *
 * A lightweight class to add to WordPress plugins or themes to automatically install
 * required plugin dependencies. Uses a JSON config file to declare plugin dependencies.
 * It can install a plugin from w.org, GitHub, Bitbucket, GitLab, Gitea or direct URL.
 *
 * @package   WP_Dependency_Installer
 * @author    Andy Fragen, Matt Gibbs, Raruto
 * @license   MIT
 * @link      https://github.com/afragen/wp-dependency-installer
 */

/**
 * Exit if called directly.
 */
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Check if this library is already used by another plugin or theme.
 */ 
if ( class_exists( 'WP_Dependency_Installer' ) ) {

  // Get running library instance.
  $wpdi = WP_Dependency_Installer::instance();

  // Do some version compare checks.
  if ( version_compare( $wpdi->version, '1.0.0', '<' ) ) {

   // Warn somehow for outdated versions.
    $wpdi->notices[] = [ 
     'message' => 'Another plugin is using a previous version of this library, this may cause some incompatibility issues, if you find some errors...'
    ];

  }
}

/**
 * Proceed as usual.
 */
if ( ! class_exists( 'WP_Dependency_Installer' ) ) {
  class WP_Dependency_Installer {
    // ...
  }
}

Otherwise I think it could be somewhat difficult to verify this type of incopatibility (potentially any plugin or theme can use this library, and it should be verified from time to time in each folder)

The version info is only in composer.json

The version info is only in composer.json

Well obviously that public property ( or function ) would only read it from that file *

* and other WPDI libraries that don't expose it would be considered incompatible...

Here it is a proof of concept:

/**
 * Check if this library is already used by another plugin or theme.
 */ 
if ( class_exists( 'WP_Dependency_Installer' ) ) {

  // Get running library instance.
  $wpdi = WP_Dependency_Installer::instance();

  // Do some version compare checks.
  if ( version_compare( $wpdi->get_version(), '1.0.0', '<' ) ) {

   // Warn somehow for outdated versions.
    $wpdi->notices[] = [ 
     'message' => 'Another plugin is using a previous version of this library, this may cause some incompatibility issues, if you find some errors...'
    ];

  }
}

/**
 * Proceed as usual.
 */
if ( ! class_exists( 'WP_Dependency_Installer' ) ) {
  class WP_Dependency_Installer {

    /**
     * Retrieve current library semver.
     *
     * @return string self::version
     */
    public function get_version() {
      if ( ! isset( self::version ) ) {
        $config = $this->json_file_decode( __DIR__ . '/composer.json' );
        self::version = empty( $config ) ? false : $config['version'];
      }

      return self::version;
    }

    /**
     * Decode json config data from a file.
     *
     * @param string json file path.
     *
     * @return bool|array $config
     */
    public function json_file_decode( $json_file ) {
      $config = [];
      if ( file_exists( $json_file ) ) {
        $config = file_get_contents( $json_file );
        $config = json_decode( $config, true );
      }

      return $config;
    }

  }
}

Isn’t the whole point of a composer library to make it available and have the dev using it maintain their code. Yes, sometimes that means composer update. I honestly do not want to go down this rabbit hole.

Isn’t the whole point of a composer library to make it available and have the dev using it maintain their code. Yes, sometimes that means composer update. I honestly do not want to go down this rabbit hole.

ok, thanks (I just wanted your opinion about it).

Instead, do you think it might still make sense to add this helper function? (to have it checked by third-party plugin developers)

/**
 * Retrieve current library semver.
 *
 * @return string self::version
 */
public function get_version() {
  if ( ! isset( self::version ) ) {
    $config = $this->json_file_decode( __DIR__ . '/composer.json' );
    self::version = empty( $config ) ? false : $config['version'];
  }

   return self::version;
}

An example use case:

/**
 * Group Plugin Installer
 *
 * @author  Andy Fragen
 * @license MIT
 * @link    https://github.com/afragen/group-plugin-installer
 * @package group-plugin-installer
 */

require_once __DIR__ . '/vendor/autoload.php';

$wpdi = WP_Dependency_Installer::instance();

if ( version_compare( $wpdi->get_version(), '1.0.0', '<' ) ) {
  error_log( "Warn somehow for incompatibility plugin." );
}

$wpdi->run( __DIR__ ); // Proceed here at your own risk.

PS perhaps, it would be safer to define a PHP Constant?

I'm not aware of a single composer library that does this.

I'm not aware of a single composer library that does this.

I just took one randomly: php-graph-sdk/src/Facebook/Facebook.php#L56

Otherwise, how could other developers be relatively (and safely) sure of which version they are calling?


PS perhaps, it would be safer to define a PHP Constant?

That's what I was referring to:

if ( ! class_exists( 'WP_Dependency_Installer' ) ) {
  class WP_Dependency_Installer {

    /**
     * Private constructor.
     */
    private function __construct() {
      // The json trick would only serve to avoid manually
      // updating it within several different places.
      if ( ! defined( 'WPDI_VERSION' ) ) {
        $config = self::json_file_decode( __DIR__ . '/composer.json' );
        define( 'WPDI_VERSION', empty( $config ) ? false : $config['version'] )
      }
    }
  }

}
/**
 * Group Plugin Installer
 *
 * @author  Andy Fragen
 * @license MIT
 * @link    https://github.com/afragen/group-plugin-installer
 * @package group-plugin-installer
 */

require_once __DIR__ . '/vendor/autoload.php';

$wpdi = WP_Dependency_Installer::instance();

if ( ! defined( 'WPDI_VERSION' ) || version_compare( WPDI_VERSION, '1.0.0', '<' ) ) {
  error_log( "Warn somehow for incompatibility plugin." );
}

$wpdi->run( __DIR__ ); // Proceed here at your own risk.

BTW I don't want to convince you to add useless code ( if you think it's useless ).

I just took one randomly: php-graph-sdk/src/Facebook/Facebook.php#L56

I'm flattered you're comparing me to Facebook.

Otherwise, how could other developers be relatively (and safely) sure of which version they are calling?

Developers call the version referenced in their composer.json file. I can't police that. I can push the version to 3 and call it a breaking change, but I'm not certain anything breaks, you just wouldn't have some new features.

I'm flattered you're comparing me to Facebook.

No, that's just an exaggerated ego 😆 !


Developers call the version referenced in their composer.json file. I can't police that.

Ok, at the most a conscientious developer could use some of https://www.php.net/manual/en/reflectionclass.getfilename.php to get version from the composer.json of active / running class ( what i mean is that reading your own composer.json version is useless )

Maybe later when i have some time I'll try to add a code snippet to the wiki

@afragen in glorious memory of IE + JS (good old days?)

/**
 * Group Plugin Installer
 *
 * @author  Andy Fragen
 * @license MIT
 * @link    https://github.com/afragen/group-plugin-installer
 * @package group-plugin-installer
 */

require_once __DIR__ . '/vendor/autoload.php';

// Sanity check for WPDI v3.0.0
if ( ! method_exists('WP_Dependency_Installer', 'json_file_decode') ) {
   error_log( "Warn somehow for incompatibility plugin." );
}

WP_Dependency_Installer::instance( __DIR__ )->run(); // Proceed here at your own risk.

Later I add it to the wiki:

if ( ! method_exists( 'WP_Dependency_Installer', 'json_file_decode' ) ) {
  add_action(
    'admin_notices',
    function() {
      $class   = 'notice notice-error is-dismissible';
      $label   = __( 'Group Plugin Installer', 'group-plugin-installer' );
      $file    = ( new ReflectionClass( 'WP_Dependency_Installer' ) )->getFilename();
      $message = __( 'Another theme or plugin is using a previous version of the WPDI library, please update this file and try again:', 'group-plugin-installer' );
      printf( '<div class="%1$s"><p><strong>[%2$s]</strong> %3$s</p><pre>%4$s</pre></div>', esc_attr( $class ), esc_html( $label ), ( $message ), esc_html( $file ) );
    },
    1
  );
  return false;
}

What are you trying to fix here?

As far as I've tested using either of the current calling functions works.

// version 2
WP_Dependency_Installer::instance()->run(__DIR__);

WP_Dependency_Installer::instance()->register($config);
WP_Dependency_Installer::instance()->run(__DIR__);

// version 3
WP_Dependency_Installer::instance(__DIR__)->run();

WP_Dependency_Installer::instance(__DIR__)->register($config);
WP_Dependency_Installer::instance(__DIR__)->run();

Version 3 can handle both of those variants.

What are you trying to fix here?

Nothing, but we can't always predict everything ...

For example, what if v2 is loaded before v3? ( e.g. another plugin with alphabetical precedence )

In this way I am at least sure that my plugin runs with compatible code ( i.e. v3 only ).

json_file_decode() is a private function and isn't called directly anywhere. If v2 loads before we will have issues with parameter passing but that problem is one of the dev loading v2 and not v3.

As it is a breaking change that's why I thought v3.0.0 was better.

json_file_decode() is a private function and isn't called directly anywhere.

Could we put it back public? it's quite handy in the case you want to define a different folder for wp-dependencies.json ( wiki/FAQs#securing-your-wp-dependenciesjson )

If v2 loads before we will have issues with parameter passing but that problem is one of the dev loading v2 and not v3.

True, but the problem would occur first on your site ... ( this could be just a sanity check for your deployed site / theme / plugin <-- not to be included in this library )

As it is a breaking change that's why I thought v3.0.0 was better.

I agree.

I'll put back to public. Does that make a call like the following.

WP_Dependency_Installer::instance(__DIR__)->json_file_decode( <path to JSON file> )->register( $config )->run();

or

$config = WP_Dependency_Installer::instance(__DIR__)->json_file_decode( <path to JSON file> );
WP_Dependency_Installer::instance(__DIR__)->register($config)->run();

This is the only way to return the $config array (otherwise you should add a state variable inside the class)

$config = WP_Dependency_Installer::instance( __DIR__ )->json_file_decode( <path to JSON file> );
WP_Dependency_Installer::instance( __DIR__ )->register( $config )->run();

In the following way, it means that the json_file_decode function must necessarily return the $this variable ( otherwise it wouldn't be chainable ):

WP_Dependency_Installer::instance(__DIR__)->json_file_decode( <path to JSON file> )->register( $config )->run();

That's kinda what I thought, but had to ask.