What is our standard for naming interfaces and abstract classes?

As I've been doing a bit of refactoring in order to write tests for our AssetProvider I've been curious about how we want to name these interfaces I'm creating. It's worth keeping in mind that we have a good bit more flexibility now than we did previously in our naming because we can safely rename classes with aliases.

Interfaces

PSR-2 Bylaws

So the default to jump to here might be the PSR-2 naming conventions. These are not part of PSR-2. They are convention for PHP-FIG produced code. The long and short of it is:

  • Suffix an interface with Interface.

Let's see a code sample in this style:

class WebpackAsset implements AssetInterface {
    public function __construct(RequestInterface $interface, CacheBusterInterface $cacheBuster) {}

    // implementation
}

Or another one.

class WebpackAssetProvider {
    public function __construct(
        RequestInterface $request,
        CacheBusterInterface $cacheBuster,
        AddonProviderInterface $addonProvider,
        ConfigurationInterface $configuration,
        LocaleInterface $locale
    ) {}

    // implementation
}

Mapping

In the end here our mapping from interface -> implementation would probably look something like this the way I tend to see things named:

  • RequestInterface -> Request (Gdn_Request) & Fixtures\Request
  • CacheBusterInterface -> CacheBuster
  • AddonProviderInterface -> AddonProvider/AddonManager
  • ConfigurationInterface -> Configuration/Gdn_Configuration
  • LocaleInterface -> Locale/Gdn_Locale

Pros

  • You can't mess up the names.
  • You don't have to think about naming very much.
  • When declaring implements things look pretty nice.

Cons

  • These names are clunky. The don't really make sense at the time of usage. When I take the request, my code should feel and read like it's using the actual request. There is an interface here for testing purposes but it still feels weird.
  • Our IDE (and the PHP runtime) both already enforce that we don't new up an interface. In this regard the naming is less useful. We also are trying to use new and use DI instead.

Laravel Style

Laravel does things a bit differently. They use the simplest possible naming for their interfaces and group them under a Contracts namespace.

I would propose that we use their Contracts where possible (increases) or organize our own similarly. In our case a few contracts we could define would be

  • Contracts\Configuration -> GlobalConfiguration, MockConfiguration, SomeSpecificConfiguration.
  • Contracts\Request -> Gdn_Request, Fixtures\Request
  • Contracts\Addon -> Vanilla\Addon
  • Contracts\AddonProvider -> Vanilla\AddonManager
  • Contracts\CacheBuster -> DeploymentCacheBuster
  • Contracts\TranslationProvider -> Gdn_Locale

Traits

PSR-2 Style

Suffix a trait with Trait.

Eg.

  • AliasProviderTrait
  • FormattableTextTrait
  • PermissionsTranslationsTrait
  • PrunableTrait
  • DummyBreadCrumbTrait
  • MiddlewareAwareTrait

Laravel Style

Name traits imperatively.

Eg.

  • ProvidesAliases
  • FormatsText
  • TranslatesPermissions
  • PrunesRecords
  • ProvidesDummyBreadcrumbs
  • ContainsMiddleware

In my opinion these are more straightforwards especially when reading their usage.

use TranslatesPermissions; vs use PermissionsTranslationsTrait;

Summary

I just felt like throwing this out there and am hoping you guys could chime in a bit.

Comments

  • I want us to go the suffix/prefix way:

    SomeInterface
    SomeTrait
    AbstractClass
    

    That's what I've seen in most composer packages and fig code I've seen. Laravel's method is a little too 🤢

  • I get that feeling with all of the suffixed and prefixed names. It just feels verbose. Kind of reminds me of systems Hungarian notation.

    I mean we wouldn’t name a variable for an int array “intarrSomeVar” and the language doesn’t even allow us to give it a type! We don’t write protected function protMyFunc, but we have no issues with interface MyInterface or trait MyTrait.

  • I'm not a fan of unnecessarily long names, but I would prefer them over going against a broadly-used convention. From what I've seen, the longer, more "redundant" names are popular convention. For example, Symfony, Zend and CakePHP all use suffix-based interface and trait naming with abstract typically being a prefix (e.g. AbstractAuthenticationClass). Laravel is the odd one out.

    For what it's worth core PHP doesn't adhere to these conventions. Predefined interfaces, such as ArrayAccess and Serializable do not contain an "Interface" suffix.

  • Unknown
    edited November 2018

    For example, Symfony, Zend and CakePHP all use suffix-based interface and trait naming with abstract typically being a prefix (e.g. AbstractAuthenticationClass). Laravel is the odd one out.

    At the same time Laravel is more popular than all 3 combined, going by github metrics.

    • Zend - 5660
    • Laravel - 46937
    • CakePHP - 7692
    • Symfony - 18963

    By packagist standards it's not as straightforward, because Symfony has so many individual packages that have far more downloads than their full framework (yaml, translations, routing, polyfill, etc).

    • Zend - 4 543 774
    • Laravel - 50 143 082
    • CakePHP - 3 410 910
    • Symfony - 36 312 785

    At the same time if we are trying to go by what the PHP community uses, when it comes time to replace a component of our framework we should be giving more consideration to using pieces from Symfony or Laravel.

    AbstractAuthenticationClass

    Redundant much?

    abstract class AbstractAuthenticationClass implements AuthenticatorInterface {}
    
  • If we were a Laravel project, we would definitely want to adhere to Laravel's naming conventions. But we're a PHP project. Everyone outside Laravel has adopted a particular naming convention. Unless we were going to become a Laravel project sometime in the near future, it doesn't make sense to me to go to the Laravel naming conventions. I don't dispute the popularity of Laravel, but I don't think we should ignore (nearly) every other PHP project in the world, just because of that popularity.

  • I'm not R&D anymore but using namespaces as a way to get rid of the redundancy and as way of grouping things is pretty clever IMO.

    I also think that suffixing and prefixing stuff comes from an era where namespaces were non existing or mostly not utilized. So this is a wildly known standard for sure but is it the best one?

    Interesting read about this subject btw: https://www.alainschlesser.com/interface-naming-conventions/

  • I also think that suffixing and prefixing stuff comes from an era where namespaces were non existing or mostly not utilized.

    I agree 100%. And from the era when IDE was not that smart as they are our days.

  • From the article linked:

    If you’re going with the PHP-FIG suffix, you’ll have a ThingInterface and code that relies upon that interface. Because, introducing a ThingInterface later on while all your code is programmed against a Thing would mean major refactoring. This is probably the reason why it is considered a “brainless trend” right now to just provide an interface for everything.

    However, that’s where my naming recommendation provides a very nice perk: If your naming does not reflect the difference between interfaces and classes, your code doesn’t need to care!


  • However, that’s where my naming recommendation provides a very nice perk: If your naming does not reflect the difference between interfaces and classes, your code doesn’t need to care!

    Maybe I'm misunderstanding, but wasn't the naming convention you were originally proposing also differentiating between a class and an interface? If we're grouping interfaces under a Contracts namespace, isn't that just another way of calling anything under Contracts an interface? Maybe you wouldn't need to rename the unqualified (short) name, but you would be changing the fully-qualified name by moving it out of Contracts, which I am assuming you would have to do if going from an interface to a class.

  • If they are named well they don't need to be in a particular namespace, although I'll admit I had not read that article before writing this post.