5

Noting these related questions (1 2 3) and these two wiki pages (1 2) and the psr-0 standard, I remain a bit lost about current best practice in CiviCRM extensions!

So if I have an extenstion org.goodfolk.magichtml

Would I put my classes in

org.goodfolk.magichtml/Civi/MagicHtml/

So in org.goodfolk.magichtml/Civi/MagicHtml/HtmlFlash.php I would have the following:

<?php namespace org.goodfolk.magichtml/MagicHtml; class HtmlFlash { public function squirmLeft() {}; public function squirmRight() {}; }

Is that correct?

or can it be simplified to:

org.goodfolk.magichtml/Civi/HtmlFlash.php:

<?php namespace org.goodfolk.magichtml; class HtmlFlash { public function squirmLeft() {}; public function squirmRight() {}; }

2
  • note I deliberately put multiword class and extension in the example as I'd like to clarity capitalization and underscore patterns too Dec 27, 2016 at 1:45
  • and proper handling html/HTML/Html (which also relates to SQL, API, PDF etc etc etc) Dec 27, 2016 at 1:58

2 Answers 2

5

The conventional approach

Generally, the safe play is to make a file like <myextension>/CRM/MyExtension/MyClass.php:

<?php
class CRM_MyExtension_MyClass { ...}

(Be sure to match the capitalization of files and classes.)

Most documentation and tooling is based on CRM_* conventions, and a handful of things in core (such as civicrm_payment_processor_type.class_name) make quirky assumptions which only work with CRM_* classes.

How it works: In civix-based extensions, the default hook_civicrm_config adds your extension to the include_path. The classloader CRM_Core_ClassLoader searches the include_path automatically for CRM_* classes.

Critique: In older PHP projects, it was common to automatically link the class-loading with the include_path, but in large codebases this can lead to a large number of file-system checks. Now-a-days many frameworks will reduce lookups by pre-generating some kind of map (namespaces<=>directories or classes<=>files).

The PSR-4 approach (Civi v4.7+)

PSR-4 is a more contemporary technique in which you explicitly map a directory to a namespace. Many PHP developers have been exposed to PSR-4 through composer.json. Civi v4.7 adds CRM_Extension_ClassLoader; with this, you can use info.xml to declare similar mappings:

<extension key="..." type="module">
  ...
  <classloader>
    <psr4 prefix="My\Namespace\" path="src" />
  </classloader>
  ...
</extension>

In this case, the file <myextension>/src/Foo.php would look like:

<?php
namespace My\Namespace;
class Foo { ... }

In terms of lookup performance and caching, this should be better than CRM_Core_ClassLoader. However, it doesn't have full parity with CRM_* in terms of documentation / tooling / testing.

Custom class loader

You should be able to register your own class-loaders -- e.g. just call spl_autoload_register(...) in your module's main PHP file (perhaps in hook_civicrm_config). If you do this, I suggest following a convention like PSR-0 or PSR-4.

Compared to the PSR-4/Civi v4.7 approach above:

  • Pro: Should be compatible with any version of Civi
  • Con: Requires more lines of code
  • Con: Has the same issue of negligible documentation / tooling / testing

PHPUnit Base Classes

There is an issue if you want to write a custom base-class for PHPUnit tests within an extension -- which is the topic of Does class autoloading not work in standalone phpunit testing?

2
  • Thanks Tim, so since I'm on 4.6 currently path of least resistance is The conventional approach then? There's no standard backport example for 4.6 laying about? Dec 28, 2016 at 5:55
  • sorry I know I could nut this out but if i do my own home grown I will exacerbate the "negligible documentation / tooling / testing" problem! Dec 28, 2016 at 6:15
0

In general, when developing extensions I would take a look at the existing ones authored by the core team or by heavily active contributors. To give some importance on my suggestion below I just checked widely used cms independent extensions available and landed at https://github.com/dlobo/org.civicrm.module.cividiscount.

Extensions that I wrote and most I know hold classes under <tld.domain.ext>/CRM/ExtensionName/ and follow php's naming conventions there. API methods are located under api/v3/ and named in api manner (classes reflecting api, methods reflecting actions).

2
  • Thanks @Neilo - I'm just wondering about best practice since php 5.3 became standard and introduced namespacing. (see the comments here that imply a new pattern but aren't clear on if it applies to extensions or not. Dec 28, 2016 at 3:03
  • Jesus, sorry! Reading the "When to use" column in the doc you linked I dare to say I'd know what to use in my extensions. But my suggestions wouldn't be more than hardly educated guesses. A question for more advanced people.
    – nielo
    Dec 29, 2016 at 22:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.