Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #39 Fixes #40 remove laminas-loader dependency #95

Merged
merged 14 commits into from
Jul 30, 2020

Conversation

samsonasik
Copy link
Member

@samsonasik samsonasik commented Jul 23, 2020

Q A
New Feature Yes
BC Break No

Description

Re-create and modify zendframework/zend-mail#186 by @acelaya

Fixes #39
Fixes #40

Original PR description:

This removes the dependency on the zendframework/zend-load package, as suggested in #185

The HeaderLoader class has been removed and replaced by a simple class map in the Headers class.

However, I have also removed the getPluginClassLoader and setPluginClassLoader methods.

Actual changes

  • Bumps minimum supported PHP version to 7.1
  • Introduces Laminas\Mail\Header\HeaderLocatorInterface, defining how to map header names to classes.
  • Introduces Laminas\Mail\Header\HeaderLocator as a default implementation of that interface.
  • Modifies Laminas\Mail\Headers as follows:
    • Marks setPluginClassLoader() and getPluginClassLoader() as deprecated.
    • Adds a $headerLocator property and accompanying setHeaderLocator() and getHeaderLocator() methods.
    • Adds a private method, resolveHeaderClass() that:
      • if a non-null $pluginClassLoader property is available, uses that to locate the header class.
      • otherwise, uses the return value of getHeaderLocator() to locate the header class.

This approach provides a forwards-compatible approach to removing laminas-loader in version 3.0.

src/Headers.php Outdated Show resolved Hide resolved
@samsonasik
Copy link
Member Author

travis build green 🎉

@glensc
Copy link
Contributor

glensc commented Jul 24, 2020

@samsonasik please copy the description from original PR, so wouldn't have to click dozen of links to get needed information. for example, I'd like to know why this is BC break.

@samsonasik
Copy link
Member Author

@glensc updated, thanks ;)

@glensc
Copy link
Contributor

glensc commented Jul 24, 2020

@samsonasik info why this is BC break still missing.

@samsonasik
Copy link
Member Author

The removing of public method is BC break, that is in the description.

@glensc
Copy link
Contributor

glensc commented Jul 24, 2020

@samsonasik write it so then

@samsonasik
Copy link
Member Author

that is in the description that I copied

@glensc
Copy link
Contributor

glensc commented Jul 25, 2020

@samsonasik seems you didn't get my message saying that copy text from the original issue, so wouldn't have to click dozen of links to get the needed information. for example, I'd like to know why this is BC break. now that info is spread around in comments in several issues/pull requests, not in compact form for current and future readers can find in one place.

src/Headers.php Outdated
Comment on lines 121 to 145
/**
* Set an alternate implementation for the PluginClassLoader
*
* @param PluginClassLocator $pluginClassLoader
* @return Headers
*/
public function setPluginClassLoader(PluginClassLocator $pluginClassLoader)
{
$this->pluginClassLoader = $pluginClassLoader;
return $this;
}

/**
* Return an instance of a PluginClassLocator, lazyload and inject map if necessary
*
* @return PluginClassLocator
*/
public function getPluginClassLoader()
{
if ($this->pluginClassLoader === null) {
$this->pluginClassLoader = new Header\HeaderLoader();
}
return $this->pluginClassLoader;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is the BC break; users calling those methods now will have errors occur.

There are two options:

  • Bump to a new major version. This is not terribly enticing, as new major versions often take time to get traction, and we'd likely want to delay release until there are other BC breaks in place that we want to achieve.
  • Keep the methods, but change them to indicate deprecation, as well as to modify how they work slightly.

If we went the second route:

  • We can remove the parameter typehint on setPluginClassLoader() (type widening is allowed for parameters), and internally check for either a HeaderLoader instance OR a laminas-loader PluginClassLocator. In the second case, we'd raise a deprecation notice.
  • We'd change the return typehint on getPluginClassLoader() to HeaderLoader|PluginClassLocator, and raise a deprecation notice if it is called.

We'd need to ensure that the same property name is used by both these methods and resolveHeaderClass().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@weierophinney I've updated keep setPluginClassLoader() and getPluginClassLoader() with E_USER_DEPRECATION mark and keep property pluginClassLoader

Signed-off-by: Abdul Malik Ikhsan <[email protected]>
Signed-off-by: Abdul Malik Ikhsan <[email protected]>
Signed-off-by: Abdul Malik Ikhsan <[email protected]>
Signed-off-by: Abdul Malik Ikhsan <[email protected]>
Signed-off-by: Abdul Malik Ikhsan <[email protected]>
…PRECATION mark and keep property pluginClassLoader

Signed-off-by: Abdul Malik Ikhsan <[email protected]>
- Adds `@deprecated` and `@todo` annotations to the `$pluginClassLoader`
  property and the `setPluginClassLoader()` and `getPluginClassLoader()`
  methods
- Adds `getHeaderLoader()` and `setHeaderLoader()` methods to allow
  providing alternate implementations.
- Adds tests to ensure that deprecation notices are emitted.

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
@weierophinney
Copy link
Member

@samsonasik I pushed additional changes to your branch. Basically:

  • Added accessors for the header loader. This sort of extensibility is necessary so that users can accommodate custom header types. By providing new accessors, we detach them from the naming of the old implementation.

  • Modified resolveHeaderClass to call on the new getHeaderLoader() to do lazy loading.

  • Reverted a removed test for multi-part header lines.

  • Added tests to ensure that the deprecation notices are being emitted.

  • Added tests for the new accessors.

I think it's ready to ship now!

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
Copy link
Member

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@samsonasik I almost merged this, and then realized there's a bigger BC break we need to address.

Removing the extends PluginClassLoader from HeaderLoader without implementing the public methods it defined is the big BC break here. With it removed, users who previously interacted with the default instance now will have breakage.

What we need to do is implement these methods:

  • public static function addStaticMap($map)
  • public function registerPlugin($shortName, $className) (this is the add() method you defined)
  • public function registerPlugins($map)
  • public function unregisterPlugin($shortName) (this is the remove() method you defined)
  • public function getRegisteredPlugins()
  • public function isLoaded($name) (this is the has() method you defined)
  • public function getClassName($name)
  • public function load($name) (this is the get() method you defined)
  • public function getIterator() (you'll need to implement IteratorAggregate)

We can keep your existing names and mark the old ones as deprecated if you'd like; the main thing is ensuring that the API is 1:1 with what we had previously.

I can likely work on this Wednesday 2020-07-29 if you don't have time.

@glensc
Copy link
Contributor

glensc commented Jul 29, 2020

@weierophinney how do you trigger autoloader then? via autoload.files?

@weierophinney
Copy link
Member

@glensc

I think you should do Symfony-style silent deprecation messages

That's a cool technique; I'd not seen using @ with trigger_error before. I'll update the code accordingly.

how do you trigger autoloader then? via autoload.files?

Yes. Generally, we've created an autoload.php file that checks to see if the class exists, and, if not, it then loads the file defining it. In this case, we'd need to provide both the interface and the implementation.

However, I don't think it's going to happen right now. More below.


@samsonasik and @michalbundyra

I've been thinking on this a ton, and there's no clean way to do this in a BC manner. However, we can make a change that provides forwards compatibility and formally deprecates usage of laminas-loader, so that a v3 can remove it.

Here's how.

  • We leave HeaderLoader unchanged.

  • We create a new interface, HeaderLocatorInterface, that defines a resolveHeaderClass(string $headerName): string method.

  • We create a new class, HeaderLocator, that implements this new interface and provides essentiallly the same API as you have proposed for HeaderLoader, to make it easy to add custom HeaderInterface implementations to the mix.

  • The method setHeaderLocator() is modified to accept the new interface, and the getHeaderLocator() method is modified to return the new interface, lazy-loading the new HeaderLocator implementation when none is provided. They each work with a new $headerLocator property.

  • Internally, resolveHeaderClass():

    • Checks if $pluginClassLoader is null. If not, it uses that to load the requested header.
    • If $pluginClassLoader is null, it calls getHeaderLocator() to retrieve the HeaderLocator instance, and then the value of calling its get() method.

We then document in both the CHANGELOG and the documentation the new interface, implementation, and default behavior, so that users know that they need to start modifying their code if they relied on the PluginClassLocator implementation.

Thoughts?

src/Headers.php Outdated
* @param PluginClassLocator $pluginClassLoader
* @deprecated since 2.12.0
* @todo Remove for version 3.0.0
* @param Header\HeaderLoader|\Laminas\Loader\PluginClassLocator $pluginClassLoader
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Laminas\Loader\PluginClassLocator can be changed with PluginClassLocator as the class re-added to use statement.

@samsonasik
Copy link
Member Author

@weierophinney I think that HeaderLocator will be ok, but that means laminas-loader will only in require-dev? or still in require ?

@weierophinney
Copy link
Member

I think that HeaderLocator will be ok, but that means laminas-loader will only in require-dev? or still in require ?

Still in require until we can remove it in 3.0. Essentially, I'm proposing we change the scope of this PR to make a forwards-compatible change so we can deprecate the usage of laminas-loader, since we have (a) a class that extends a class in that package, and (b) methods that are tied to an interface from that package, and (c) the latter are the default way to manipulate what custom headers can be handled by end-users. The PR would offer another way of doing this that is not tied to an external package, allowing us to deprecate those features, and then remove them in 3.0.

I'm willing to do the work, if you'd like.

@samsonasik
Copy link
Member Author

@weierophinney ok, I will let you continue. Thank you

Restores HeaderLoader from develop branch, and renames newly introduced
version to HeaderLocator.

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
@glensc
Copy link
Contributor

glensc commented Jul 29, 2020

you can use composer "replace" trick to remove "laminas-loader" from your installed vendors:

but this solution is suitable only for applications. don't do this in libraries or your users will hate you.

weierophinney added a commit to samsonasik/laminas-mail that referenced this pull request Jul 29, 2020
Also details update to PHP 7.1.

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
weierophinney added a commit to samsonasik/laminas-mail that referenced this pull request Jul 29, 2020
Also details update to PHP 7.1.

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
Extracts `HeaderLocatorInterface` from `HeaderLocator`. In doing so, I
decided to bump the minimum supported PHP version to 7.1 (which we can
do on new minor versions), as it allows us to specify scalar typehints,
nullable typehints, and void return types, providing a stronger
contract.

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
This will be a forwards-compat patch, and leave the original
`HeaderLoader` in place. As such, we need to remove the
`PluginClassLocator` stub from the test assets.

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
- Reverts `(set|get)PluginClassLoader()` method to original
  implementations, but with the addition of triggering deprecation
  notices. `trigger_error()` is now silenced, per recommendation from
  @glensc.

- Adds `$headerLocator` property.

- Renames `(get|set)HeaderLoader()` methods to `get|setHeaderLocator()`,
  and adds typehints. These now operate on the `$headerLocator`
  property.

- Modifies `resolveHeaderClass()` to check for a non-null
  `$pluginClassLoader` property; if found, it uses that to resolve the
  header class, defaulting to `GenericHeader` if not loaded.

- Updates PHPUnit dependency to 7.5.20, so we can use the various error
  types it provides. This is possible since we now depend on 7.1+.
  However, I did not update to later versions of PHPUnit as they would
  require refactoring tests to add typehints.

- Refactors the unit tests to mirror the changes made.

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
weierophinney added a commit to samsonasik/laminas-mail that referenced this pull request Jul 29, 2020
Also details update to PHP 7.1.

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
@weierophinney
Copy link
Member

@michalbundyra and/or @samsonasik — changes are in place. I've updated the description to better detail what has finally happened, and detailed it in the changelog as well. Tests are green. :)

Comment on lines +18 to +24
public function get(string $name, ?string $default = null): ?string;

public function has(string $name): bool;

public function add(string $name, string $class): void;

public function remove(string $name): void;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are why I lumped the bump in PHP version into the patch: scalar type hints, return type hints, nullable types, and void return types. Essentially it is this patch that necessitates the bump in minimum version, which is exactly what the TSC decision was about in that first meeting. 😄

*/
public function setPluginClassLoader(PluginClassLocator $pluginClassLoader)
{
// Silenced; can be caught in custom error handlers.
@trigger_error(sprintf(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @glensc pointed out, this is how Symfony does deprecation notices. They'll get caught in error handlers, but not if unhandled. This prevents breakage in most use cases, but if you log errors, you'll catch them in your logs.

Comment on lines +325 to +332
if (! $instanceOrFieldName instanceof Header\HeaderInterface && ! is_string($instanceOrFieldName)) {
throw new Exception\InvalidArgumentException(sprintf(
'%s requires a string or %s instance; received %s',
__METHOD__,
Header\HeaderInterface::class,
is_object($instanceOrFieldName) ? get_class($instanceOrFieldName) : gettype($instanceOrFieldName)
));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit was interesting. Because I turned on strict types in this file, I discovered a test that was passing null to this method, which was incorrect. As such, I added this guard (and updated the test!).

Also details update to PHP 7.1.

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
@weierophinney weierophinney merged commit 8e286c0 into laminas:develop Jul 30, 2020
@samsonasik samsonasik deleted the remove-laminas-loader branch July 30, 2020 15:56
@glensc
Copy link
Contributor

glensc commented Jul 31, 2020

@weierophinney can you add a summary to users what they should do to stop using zend-loader?

just upgrade and add composer "replace" to loader package?

assuming just used defaults previously

@weierophinney
Copy link
Member

@glensc By default, if you are NOT calling getPluginClassLoader() or setPluginClassLoader(), Headers will no longer use the laminas-loader functionality. This is spelled out in the changelog:

The value of getHeaderLocator() will now be used as the default mechanism for resolving header names to header classes.

Beyond that, if you're using the laminas-mvc, you cannot really replace the laminas-loader package, as it's used by the MVC (though the usage is largely deprecated). As such, I haven't detailed how to do that; it's a niche use case. But what it does do is open the door for us to remove it from this component in v3, and, if v3 is not released before releases of MVC components that do not use it, we can replace it in the laminas-mvc-skeleton package.

artemii-karkusha pushed a commit to artemii-karkusha/laminas-mail that referenced this pull request May 24, 2023
Signed-off-by: Matthew Weier O'Phinney <[email protected]>
artemii-karkusha pushed a commit to artemii-karkusha/laminas-mail that referenced this pull request May 24, 2023
Also details update to PHP 7.1.

Signed-off-by: Matthew Weier O'Phinney <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consider removing dependency on zend-loader Drop dependency on zendframework/zend-loader
4 participants