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

NEW: Static validation for relationships. #9874

Conversation

mfendeksilverstripe
Copy link
Contributor

@mfendeksilverstripe mfendeksilverstripe commented Feb 26, 2021

Issue #10008

Static validation for relationships

This is a new tool for static validation of relations. Very helpful if you are keen on keeping your relationship setup correct at all times. Especially useful for large projects with lot of models and relations.

Details

  • configurable (narrow down class inspection by namespace, class or/and relation names)
  • this feature is disabled by default and needs to be hooked up in order to work
  • two hooks are available: either unit tests or dev/build

@madmatt
Copy link
Member

madmatt commented Feb 26, 2021

This seems really interesting - could we also add Documentation to the TODO. Also, if this is going into framework, my immediate thought is that it should be on by default (e.g. an explicit step in existing dev/build practices) and that it can be turned off if required (in practice by specifying disallowed_relationships for ones you're okay with not existing).

@sminnee
Copy link
Member

sminnee commented Feb 26, 2021

Is there any reason we wouldn't enable this in dev/build by default? At least as a warning?

@mfendeksilverstripe
Copy link
Contributor Author

mfendeksilverstripe commented Mar 8, 2021

@sminnee I would enable this by default in an ideal world but the reality is that many modules do not have properly setup relationships (back relation is either ambiguous or missing). This would force bespoke developers to fix configuration of modules they don't value that much. The biggest value we've got out of this tool was to validate the code we maintain.

Other reason for having this configurable is that some project may not care too much about relationship configuration. The main benefit is probably on larger projects where having properly defined relations allows:

  • more readable code / better navigation within the code and understanding in general
  • data reports relation functionality for example if you're looking into understanding where a certain model is referenced

@madmatt I'll review the configuration and check if I can provide a setup which can be enabled by default. The challenge here is to set it up in a way that bespoke code is covered but module code is not. This line is a bit blurry because for example you may want to target SiteTree as there may be bespoke extension on it.

@sminnee
Copy link
Member

sminnee commented Mar 9, 2021

Another alternative would be to only throw a warning by default, so it shows up for those cases but doesn't break dev/build...

@mfendeksilverstripe
Copy link
Contributor Author

Yeah, I think that's reasonable - warning for the dev/build and if you want to have a strict validation you can hook it up to your unit tests.

@mfendeksilverstripe mfendeksilverstripe force-pushed the feature/static-relations-validation branch 2 times, most recently from f19d0cf to c0871ee Compare March 23, 2021 02:37
@mfendeksilverstripe mfendeksilverstripe force-pushed the feature/static-relations-validation branch from c0871ee to b7b7c09 Compare March 23, 2021 18:46
@mfendeksilverstripe
Copy link
Contributor Author

@madmatt @sminnee I've pushed up new changes:

  • unit tests
  • docs
  • cleaner configuration API
  • hooked up to dev/build

Please review.

Copy link
Contributor

@maxime-rainville maxime-rainville left a comment

Choose a reason for hiding this comment

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

@silverstripe/core-team Before we do a detailed peer review on this, I just want to make sure we have an agreement in principle that this is a good idea worth merging.

I for one am in favour of shipping this feature as a warning that people can ignore. Maybe some projects can opt-in to fail their builds if this is something they care deeply about.

src/ORM/Validation/RelationValidationService.php Outdated Show resolved Hide resolved
@mfendeksilverstripe mfendeksilverstripe force-pushed the feature/static-relations-validation branch from db3a158 to ce576ca Compare April 6, 2021 21:49
@mfendeksilverstripe
Copy link
Contributor Author

mfendeksilverstripe commented Apr 6, 2021

@maxime-rainville Moved the code under Dev namespace.

@emteknetnz
Copy link
Member

emteknetnz commented Jul 4, 2021

I like what this tool does, we just need to think about how this is triggered so that it's provides value and not just annoyance within peoples workflow

Currently it's enabled by default on

        if (static::class === \Page::class) {
            RelationValidationService::singleton()->devBuildCheck();
        }

Which means it's limited only regular app pages, though means that regular dataobjects in custom code aren't checked which will probably be the bulk of custom object for most projects. If find this pretty strange.

Some people will find this tool useful, for instance I can see it being great new projects where there's a strong focus on maintaining standards

For others it think this just unnecessary noise, such as dev being assigned to serveral years old project to do a sprints worth of work on a single feature. They're not going to get any value from being told there's a bunch of missing belongsTo relationships for parts of the system they're not working on.

I'd also expect that the deployment log on the majority of projects will have a bunch of warnings triggered, which is just going to be annoying noise. Myself coming mainly from a maintenance perspective I think in most cases this tool will be perceived as a negative if it's on by default

What I think would be useful is:

  1. disabled by default so as not to annoy people on existing projects who probably don't want this feature
  2. make this opt-in via:
  • a URL/CLI param to trigger as a one-off e.g. validate=1
  • project level config so that it's always on - this way it will show in deployment logs
  • a dev task provides a exit code so that it's failable within a CI context
  1. clear, prominent docs for how to enable this
  2. clear docs on how to limit this to the My/App/Namespace, because we really don't want to running on the vendor folder
  3. ensure that this also works for module development as well as bespoke development

@mfendeksilverstripe
Copy link
Contributor Author

Hey @emteknetnz , thanks for feedback. Some clarifications:

Which means it's limited only regular app pages, though means that regular dataobjects in custom code aren't checked which will probably be the bulk of custom object for most projects. If find this pretty strange.

The check condition is limited to the Page class only because we want to run it just once but the validation is not limited to page class, this is just how it's triggered. The service has it's own configuration and will check any classes available in the project (provided the configuration allows it).

The rest of the suggestions are very reasonable. I can definitely see how this can produce a lot of uneeded noise on older projects but it's really useful for new ones.

Setting the right defaults is the key. Happy to make adjustments as required 👍

@emteknetnz
Copy link
Member

emteknetnz commented Jul 5, 2021

this is just how it's triggered

Ah ok, I see how this works now.

Possible the trigger point being in requireDefaultRecords is a bit odd . It may make sense to move the trigger point to a class that implements flushable - I think flush makes more sense rather than dev/build as $belongTo relationships won't create an database columns, though you may have a different opinion.

@mfendeksilverstripe mfendeksilverstripe force-pushed the feature/static-relations-validation branch from 07aff3a to cd3ecd8 Compare October 20, 2021 19:21
@mfendeksilverstripe
Copy link
Contributor Author

This is ready for a review @emteknetnz .

Copy link
Member

@emteknetnz emteknetnz left a comment

Choose a reason for hiding this comment

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

Good contribution. Needs to be opt-in configurable though.

The CLI output on dev/build flush is a little suboptimal, on my local it looks like this:

 * SilverStripe\ElementalFileBlock\Block\FileBlock
 * SilverStripe\ElementalBannerBlock\Block\BannerBlock
 * Page
 * BlocksPage
 * SilverStripe\ErrorPage\ErrorPage
  * RelationValidationService : 2 issues found (listed below)
  * Page / MyFile : Back relation not found or ambiguous (needs class.relation format)
  * BlocksPage / ElementalArea : Back relation not found or ambiguous (needs class.relation format)
  + 404 error page refreshed
  * RelationValidationService : 2 issues found (listed below)
  * Page / MyFile : Back relation not found or ambiguous (needs class.relation format)
  * BlocksPage / ElementalArea : Back relation not found or ambiguous (needs class.relation format)
  + 500 error page refreshed
 * SilverStripe\CMS\Model\RedirectorPage
 * SilverStripe\CMS\Model\VirtualPage

The RelationValidationService is kind of jammed in the middle of ErrorPage being built and it's indentation doesn't seem right

src/Dev/Validation/RelationValidationService.php Outdated Show resolved Hide resolved
src/Dev/Validation/RelationValidationService.php Outdated Show resolved Hide resolved
public function inspectClasses(array $classes): array
{
self::reset();
$this->ignoreConfig = true;
Copy link
Member

Choose a reason for hiding this comment

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

should $this->ignoreConfig = false; be called after calling $this->validateClasses($classes);` ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It needs to be called before the validation as configuration potentially narrows down the scope of the covered classes. This is meant to be used for debugging / figuring out configuration.

Copy link
Member

Choose a reason for hiding this comment

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

Understand that, I'm just wondering if there's a need to revert it to false afterwards, or if because it's just a "one-off run" per flush request it can just stay set to true

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reset to previous state of this flag is covered by self::reset() so there is no need to set it back.

src/Dev/Validation/RelationValidationService.php Outdated Show resolved Hide resolved
@mfendeksilverstripe
Copy link
Contributor Author

@emteknetnz I've pushed up changes as requested:

  • fixed a few typos
  • some linting fixes
  • feature is now opt-in
  • error messages are now formatted as errors

I'm not sure how to improve the dev/build output though. I can see that the validation runs twice during dev/build as well:

Screen Shot 2022-01-24 at 9 43 15 AM

The output looks correct when running flush via dev/tasks though:

Screen Shot 2022-01-24 at 9 42 53 AM

Copy link
Member

@emteknetnz emteknetnz left a comment

Choose a reason for hiding this comment

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

Cheers. Still need to default the config to off + document

Is there anyway we can get the output to not be done seemingly at the same time as the Error pages being created when running dev/build flush ?

I've debugged this and the reasons it's showing here on dev/build flush before each of the 404 + 500 pages is:

ErrorPage::requireDefaultRecords()
ErrorPage::requireDefaultRecordFixture()
ErrorPage::writeStaticPage()
Director::test()

Presumably the Director::test() is used to generate HTML output for the error pages using templates etc.

I'm not sure how, though ideally we'd want to defer turning on / running this flushable service until after everything else

public function inspectClasses(array $classes): array
{
self::reset();
$this->ignoreConfig = true;
Copy link
Member

Choose a reason for hiding this comment

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

Understand that, I'm just wondering if there's a need to revert it to false afterwards, or if because it's just a "one-off run" per flush request it can just stay set to true

*
* @var bool
*/
private static $flush_output_enabled = true;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private static $flush_output_enabled = true;
private static $flush_output_enabled = false;

Need to default this to off otherwise it'll be very annoying for the majority of people

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whoops, I left this value there from my testing run.

*/
public function executeValidation(): void
{
if (!$this->config()->get('flush_output_enabled')) {
Copy link
Member

Choose a reason for hiding this comment

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

This config check should be in the flush() method. The idea with the config is that it disables automatic running on every flush. However if we have the config here, we cannot $service = new RelationValidationService(); $service->executeValidation(); which someone might want to do as a one-off for a custom linting setup

*/
protected $errors = [];

public function flushErrors(): void
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public function flushErrors(): void
public function clearErrors(): void

Probably best to avoid the word 'flush' since this is already a 'flusable' class

public static function reset(): void
{
$service = self::singleton();
$service->flushErrors();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
$service->flushErrors();
$service->clearErrors();

@mfendeksilverstripe
Copy link
Contributor Author

I've pushed up new changes @emteknetnz :

  • several of your suggestions (docs update, minor code cleanup)
  • I couldn't find a god way how to execute the validation via Flush so I added a couple of extension point to the dev build process instead

Copy link
Member

@emteknetnz emteknetnz left a comment

Choose a reason for hiding this comment

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

Cool, looking much better. No longer runs by default and output is no longer happening at the same time as Errorpage creation

dev/build flush

...
 * BlocksPage
 * SilverStripe\ErrorPage\ErrorPage
  + 404 error page refreshed
  + 500 error page refreshed
 * SilverStripe\CMS\Model\RedirectorPage
 * SilverStripe\CMS\Model\VirtualPage

 Database build completed!

  ! RelationValidationService : 2 issues found (listed below)
  ! Page / MyFile : Back relation not found or ambiguous (needs class.relation format)
  ! BlocksPage / ElementalArea : Back relation not found or ambiguous (needs class.relation format)

@emteknetnz emteknetnz merged commit 89c87dd into silverstripe:4 Feb 4, 2022
GuySartorelli pushed a commit to GuySartorelli/silverstripe-framework that referenced this pull request Jul 6, 2022
* NEW: Static validation for relationships.

* Unit tests added.

* PR fixes

* PR feedback: Execute validation on flush.

* PR fixes.

* PR fixes.
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.

Static validation for relationships
5 participants