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

Reduce use of undeclared properties (php8.x errors) , use trait to track entities #27146

Closed

Conversation

eileenmcnaughton
Copy link
Contributor

Overview

Reduce use of undeclared properties (php8.x errors) , use trait to track entities

Before

Undefined property errors on useDisplayName

After

Still errors - but the genesis of an approach

Technical Details

A lot of the undeclared properties on this form & similar are just tracking values of various relevant entities. This adds some functionality for that tracking onto a trait & uses that. This reduces the need
to pass around values whose only importance is that they have been loaded from the database & would require
another db call to access them from elsewhere.

I've kept the change to use this pretty small but there are a bunch of places where properties that may or may not exist are set just to hold the values on the form 'somewhere'

I'm thinking these get functions could have the @api flag, allowing / encouraging their use from outside of core

Comments

@seamuslee001

@civibot
Copy link

civibot bot commented Aug 24, 2023

Thank you for contributing to CiviCRM! ❤️ We will need to test and review the PR. 👷

Introduction for new contributors
  • If this is your first PR, an admin will greenlight automated testing with the command ok to test or add to whitelist.
  • A series of tests will automatically run. You can see the results at the bottom of this page (if there are any problems, it will include a link to see what went wrong).
  • A demo site will be built where anyone can try out a version of CiviCRM that includes your changes.
  • If this process needs to be repeated, an admin will issue the command test this please to rerun tests and build a new demo site.
  • Before this PR can be merged, it needs to be reviewed. Please keep in mind that reviewers are volunteers, and their response time can vary from a few hours to a few weeks depending on their availability and their knowledge of this particular part of CiviCRM.
  • A great way to speed up this process is to "trade reviews" with someone - find an open PR that you feel able to review, and leave a comment like "I'm reviewing this now, could you please review mine?" (include a link to yours). You don't have to wait for a response to get started (and you don't have to stop at one!) the more you review, the faster this process goes for everyone 😄
  • To ensure that you are credited properly in the final release notes, please add yourself to contributor-key.yml
  • For more information about contributing, see CONTRIBUTING.md.
Quick links for reviewers

@civibot civibot bot added the master label Aug 24, 2023
@eileenmcnaughton eileenmcnaughton force-pushed the entity_trait branch 5 times, most recently from 4d80053 to 89887f6 Compare August 25, 2023 01:08
* @return mixed
*/
public function getEntityValue(string $entity, int $id, string $key) {
if (!isset($this->entities[$entity][$id]) || !array_key_exists($key, $this->entities[$entity][$id])) {
Copy link
Contributor

Choose a reason for hiding this comment

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

so I suppose $identifier in L34 may not be an actual int but here we are assuming it will be an int right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hmm yeah - I think I go back & forth on whether we should only support ids or whether we should support string indentifiers too...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok - all ints now

Copy link
Member

Choose a reason for hiding this comment

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

Actually I think it's quite useful to be able to look up stuff by name as well as id.

*/
public function getParticipantValue(string $fieldName, ?int $id = NULL) {
$id = $id ?? $this->getParticipantID();
return $this->getEntityValue('Participant', $id, $fieldName);
Copy link
Contributor

Choose a reason for hiding this comment

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

@eileenmcnaughton this doesn't seem to be exactly the same as the original as the original could find the value in participant_$fieldname if it wasn't present in $fieldName` right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@seamuslee001 true - but the actual usage of this function means that isn't an issue -

image

*/
private $entities = [];

public function setEntity($entity, $identifier, $values): void {
Copy link
Member

Choose a reason for hiding this comment

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

Some type hints would help. I assume $entity is a string and $values is an array?

@colemanw
Copy link
Member

We at CiviCRM sure do enjoy reinventing wheels! Not that this isn't a nice shiny wheel or that we shouldn't use it. But it might be worthwhile to take stock of all the other wheels on the shelf and see if there's anything we can do about our excess inventory...

Method Fetches Formatting Custom Data Caching Access
CRM_Core_DAO::getFieldValue 1 DAO field value none never static var public static
CRM_Admin_Form::retrieveValues 1 DAO/APIv4 record none or APIv4 never class var protected in-class
BAO::retrieve 1 DAO record none never none public static
CRM_Core_DAO::commonRetrieve 1 DAO record none never none public static
This new trait 1 APIv4 record APIv4 always class var public in-class

A lot of the undeclared properties on this form & similar are just tracking
values of various relevant entities. This adds some functionality for that tracking
onto a trait & uses that. This reduces the need
to pass around values whose only importance is that they have
been loaded from the database & would require
another db call to access them from elsewhere.

Note I have swapped out the interaction with _contributorEmail & _contributorDisplayName
in getStatusMessage and send email receipts - these are the last 2 functions
called in the submit routine & the only reason the receipt function needs to set it is
to pass them to getStatusMessage. There are a couple of earlier interactions which are
odd so I ignored them for now.

The loop for sending out receipts is pretty messed up - so the first step is
getting the inputs & outputs for that loop clarified - but this
property is bang in the middle
@eileenmcnaughton
Copy link
Contributor Author

@colemanw yeah good point but the others store one entity per class - ie not contact + event + participant & use different property names on every form + I can't standardise prperties across multiple forms cos they have inconsisten visibility & naming & also those that don't use apiv4 don't support related contact emails (I expect we would also want to add support to load pseudoconstants to)

$select = ['email_primary.email', 'email_primary.on_hold', '*', 'custom.*'];
}
else {
$select = ['*', 'custom.*'];
Copy link
Member

Choose a reason for hiding this comment

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

I'm 👎🏻 on loading custom data by default. This class is a lazy loader so could fetch it if you request the value of a custom field but I think loading the "kitchen sink" with potentially dozens of joins on custom tables is a waste of cpu & memory(and invites getting bitten by that max join limit in SQL).

@colemanw
Copy link
Member

Question: is it really necessary for a typical form to track > 1 type of entity? Because CRM_Core_Form::getDefaultEntity() exists so we could just rely on that for the name of the api entity.

@eileenmcnaughton
Copy link
Contributor Author

@colemanw yep - because forms have, for example,

  • a contact for the contribution
  • a contact for the membership (may or may not be the same)
  • a membership
  • a contribution
  • a price set
  • a contribution page

Currently all of these are being tracked & accessed in a weird & wonderful array of ways & there is no 'right' way to get any of the values internally or from an extension - so the goal will be to be able to retrieve any of these - ideally

$this->getContactValue('email_primary.email')
$this->getContactValue('email_primary.email', $this->getMembershipContactID())
$this->getContributionValue('tax_amount`);
$this->getEntityValue('PriceField', 'label', 4)

etc

Note the second one - in unit tests we have a similar class that accepts a string that the form interprets eg.

$this->getContactValue('email_primary.email', 'membership')

@colemanw
Copy link
Member

colemanw commented Sep 2, 2023

Ok @eileenmcnaughton I had a long think about this & I was able to take your idea here and generalize it a bit more: #27257
I think that will keep it similar enough to this PR to meet your needs, but flexible enough to meet a lot of other use-cases as well.

@eileenmcnaughton
Copy link
Contributor Author

@colemanw I had a go at using it & it replaced the need for some of this but I wound up having to write other stuff to use it - I will put it up separately

@eileenmcnaughton
Copy link
Contributor Author

Closing this in favour of the new trait + the merge-ready PR

@eileenmcnaughton eileenmcnaughton deleted the entity_trait branch September 5, 2023 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants