Skip to content

Class Scanner - Recommend general adoption#415

Merged
totten merged 4 commits intomasterfrom
scan-classes
Oct 7, 2025
Merged

Class Scanner - Recommend general adoption#415
totten merged 4 commits intomasterfrom
scan-classes

Conversation

@totten
Copy link
Copy Markdown
Owner

@totten totten commented Oct 4, 2025

Overview

The class-scanner allows you to create new PHP files with services and listeners -- without needing to hand-edit or re-generate the main *.php files. This encourages modular development, and it makes it easier to copy/borrow/move snippets of code. The PR encourages adoption with main two revisions:

  1. civix generate:module: On CiviCRM v5.51+, enable scan-classes@1 by default for new extensions.
  2. civix upgrade: Prompt the developer to enable the scanner. Include the known counter-indicators (i.e. exceptions where you may not want it) and alternatives (e.g. custom scanner).

This is generally an outgrowth of work on civicrm/civicrm-core#33371, which will eventually require class-scanning for Civi/Api4. It aligns with @ufundo's argument (which I mostly agree with) that class-scanning is a good general practice. It specifically expands on @colemanw's #412 (and borrows some ideas from that discussion), but this is a bit more comprehensive and more nuanced. In my local system, phpunit is happier. (We'll see if Jenkins agrees.)

Technical Details

There's not much to see in the civix generate:module. Most of the action comes in civix upgrade.

Here is a preview of civix upgrade on mosaico, which should be fairly typical:

Screenshot 2025-10-03 at 9 28 47 PM

Or here's an example with a more difficult extension (where its <compatibility> is older and it has legacy custom-searches):

Screenshot 2025-10-03 at 9 27 59 PM

Based on anecdotal reading of universe, I believe there are some common cases where extensions have undeclared dependencies. I've added heuristics to detect two of them:

  • Extensions which extend CRM_Civirules* but don't declare a full dependency on org.civicoop.civirules.
  • Extensions which extend CRM_Contact_Form_Search_Custom_Base but don't declare a full dependency on legacycustomsearches.

If it finds these clues, then it switches its default recommendation (scan-classes@1 => hook_scanClasses). It's OK to add more heuristics.

Of course, it would be even better if we find a design that supports a standard scanner (with little/no configuration) while still supporting these. I look forward to talking more with @jaapjansma @ufundo etal in a couple weeks about that.

@colemanw
Copy link
Copy Markdown
Contributor

colemanw commented Oct 4, 2025

@totten If it finds these clues, then it switches its default recommendation

In those cases my typical recommendation would be to declare the dependency. Unless there's a compelling reason not to, that's orders of magnitude simpler than writing and maintaining your own class scanner.

@totten
Copy link
Copy Markdown
Owner Author

totten commented Oct 6, 2025

(@colemanw) In those cases my typical recommendation would be to declare the dependency. Unless there's a compelling reason not to, that's orders of magnitude simpler than writing and maintaining your own class scanner.

So I originally drafted prose trying to describe both options. I dropped the comments about declaring the dependency because the prose was getting long -- and because the custom scanner looked like the simpler recommendation. 😄 These things are subjective. Here's the angle I was looking at:

  • Adding the dependency really depends on the design of the specific extensions:
    • Ex: com.fountaintribe.financialsummaries looks like it really depends on legacycustomsearches. To my eye, it looks useless without it. (This is a faulty omission of a hard dependency.)
    • Ex: dataprocessor has elements that can use legacycustomsearches. To my eye, it looks like an optional aside. (This is a valid omission of a soft dependency.)
    • N.B. This requires reviewing the extension more closely and making a correct judgment.
  • Adding hook_scanClasses (with Civi/Api4 in the template) is safe either way. If you're trying to apply the update without thinking much, then it's the better bet.
    • N.B. The actual code in the template is pretty simple (a call to scanFolders(...Civi/Api4...)), and it should substantively do the needful for the target group. And while it has a few SLOC... it's still a lot cleaner than managing manual registrations.

Of course, to your point -- if someone defines any kind of custom scanning rules, then they could get in trouble later... e.g. if forget about this and starting adding code outside of their scanned-folder.

Perhaps the missing element here... data. I tried to get some real data..

CiviRules: Consumers
  • requiresTgt: Does the extension declare a <requires> for org.civicoop.civirules in info.xml?
  • extendsTgt: How many lines of code have indicators like extends CRM_Civirules?
requiresTgt"              "extendsTgt"    "path"                                   "origin"
-------------------------- --------------- ---------------------------------------- ---------------
[]                         1               "cdntaxreceipts_civirules"               "https://lab.civicrm.org/extensions/cdntaxreceipts_civirules.git"
[]                         1               "nextcloud"                              "https://lab.civicrm.org/extensions/nextcloud.git"
[]                         1               "org.civicoop.pdfapi"                    "https://lab.civicrm.org/extensions/pdfapi.git"
[]                         1               "org.civicoop.smsapi"                    "https://lab.civicrm.org/extensions/smsapi.git"
[]                         1               "postonchannel"                          "https://lab.civicrm.org/extensions/postonchannel.git"
[]                         2               "chasse"                                 "https://lab.civicrm.org/extensions/chasse.git"
[]                         2               "chatbot"                                "https://github.com/3sd/civicrm-chatbot.git"
[]                         2               "civicrm-sms-conversation"               "https://github.com/3sd/civicrm-sms-conversation.git"
[]                         2               "civirulesextras"                        "https://lab.civicrm.org/extensions/civirulesextras.git"
[]                         2               "de.systopia.sqltasks"                   "https://github.com/systopia/de.systopia.sqltasks.git"
[]                         2               "expenses"                               "https://lab.civicrm.org/extensions/expenses.git"
[]                         2               "org.civicoop.groupprotect"              "https://github.com/CiviCooP/org.civicoop.groupprotect.git"
[]                         3               "civipoints"                             "https://lab.civicrm.org/extensions/civipoints.git"
[]                         4               "portalusermanagement"                   "https://lab.civicrm.org/extensions/portalusermanagement.git"
[]                         7               "org.civicoop.emailapi"                  "https://lab.civicrm.org/extensions/emailapi.git"
[]                         14              "uk.co.vedaconsulting.mautic"            "https://github.com/veda-consulting-company/uk.co.vedaconsulting.mautic.git"
["org.civicoop.civirules"] 1               "ncn-civi-zoom"                          "https://lab.civicrm.org/extensions/ncn-civi-zoom.git"
["org.civicoop.civirules"] 1               "org.civicoop.civiruleslogger"           "https://github.com/CiviCooP/org.civicoop.civiruleslogger.git"
["org.civicoop.civirules"] 2               "CiviFlow"                               "https://lab.civicrm.org/extensions/CiviFlow.git"
["org.civicoop.civirules"] 2               "nz.co.fuzion.zapier"                    "https://lab.civicrm.org/extensions/zapier.git"
["org.civicoop.civirules"] 3               "com.megaphonetech.fastactionlinks"      "https://lab.civicrm.org/extensions/fastactionlinks.git"
["org.civicoop.civirules"] 4               "cmsuser"                                "https://lab.civicrm.org/extensions/cmsuser.git"
["org.civicoop.civirules"] 12              "au.com.agileware.zoomzoom"              "https://github.com/agileware/au.com.agileware.zoomzoom.git"
Legacy Custom Search: Consumers
  • requiresTgt: Does the extension declare a <requires> for legacycustomsearch in info.xml?
  • extendsTgt: How many lines of code have indicators like extends CRM_Contact_Form_Search_Custom?
"requiresTgt"   "extendsTgt"    "path"                                   "origin"
--------------- --------------- ---------------------------------------- ---------------
[]              1               "civipoints"                             "https://lab.civicrm.org/extensions/civipoints.git"
[]              1               "com.aghstrategies.usermover"            "https://github.com/aghstrategies/com.aghstrategies.usermover.git"
[]              1               "com.skvare.membershipextra"             "https://github.com/Skvare/com.skvare.membershipextra.git"
[]              1               "coop.symbiotic.timetrack"               "https://lab.civicrm.org/extensions/timetrack.git"
[]              1               "dataprocessor"                          "https://lab.civicrm.org/extensions/dataprocessor.git"
[]              1               "de.systopia.remoteevent"                "https://github.com/systopia/de.systopia.remoteevent.git"
[]              1               "de.systopia.segmentation"               "https://github.com/systopia/de.systopia.segmentation.git"
[]              1               "eventmanagelocations"                   "https://lab.civicrm.org/extensions/eventmanagelocations.git"
[]              1               "org.project60.sepa"                     "https://github.com/Project60/org.project60.sepa.git"
[]              1               "viewdetailedlog"                        "https://lab.civicrm.org/extensions/viewdetailedlog.git"
[]              2               "uk.co.vedaconsulting.gdpr"              "https://lab.civicrm.org/extensions/gdpr.git"
[]              6               "com.fountaintribe.financialsummaries"   "https://github.com/sgladstone/com.fountaintribe.financialsummaries.git"

Alas, those numbers aren't very clear -- we do see multiple examples of both faulty omissions and valid omissions, but there's no statistic on them. My gut read of the list is that faulty omissions are probably more common than valid omissions. (But not enough that we can turn on blinders.)

So maybe the current draft is too simplistic (i.e. being quiet about the possibility of faulty omissions).


To get more bite-sized prompts, I'm thinking to split out two distinct upgrade-steps:

  1. Dependency Cleanup: Look for common clues of undeclared dependencies. If found, prompt the user.
    • "This extension has the following signs of undeclared dependencies... Should we add x.y.z to info.xml?"
  2. Class Scanner: Basically what's here, but maybe with some re-wording. (But at this point, we can assume that info.xml is accurate -- which should keep the question simpler.)

@colemanw
Copy link
Copy Markdown
Contributor

colemanw commented Oct 6, 2025

@totten I'm thinking to split out two distinct upgrade-steps:

That sounds like a good plan!

@totten
Copy link
Copy Markdown
Owner Author

totten commented Oct 7, 2025

New screenshots - these are for an extension with an undeclared dependency on CiviRules:

  1. Clarify the dependency

    Screenshot 2025-10-06 at 5 23 22 PM
  2. Enable class scanning

    Screenshot 2025-10-06 at 5 23 38 PM

@colemanw
Copy link
Copy Markdown
Contributor

colemanw commented Oct 7, 2025

@totten this looks good!

Nitpick: the first dependency check is a bit verbose for a noop. I found myself reading "[INFO] Found no evidence..." twice to assure myself that "no evidence" is actually a good thing.

Had it been me, I probably would have suppressed the entire message if no problems are found. But I know you like giving feedback. Maybe it could at least be shortened to something unambiguously ✅ like "no problems found"

@totten
Copy link
Copy Markdown
Owner Author

totten commented Oct 7, 2025

@colemanw Yeah, I agree that screen is a bit ambiguous. Latest push should be a little quieter and little less ambiguous.

@colemanw
Copy link
Copy Markdown
Contributor

colemanw commented Oct 7, 2025

Looks ready to merge @totten

@totten totten merged commit c216d10 into master Oct 7, 2025
1 check passed
@totten totten deleted the scan-classes branch October 7, 2025 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants