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

EZP-24800 Support allowed classes limitation for object relation fields #2022

Merged
merged 2 commits into from Jul 31, 2017
Merged

Conversation

slaci
Copy link
Contributor

@slaci slaci commented Jun 18, 2017

This PR adds allowed classes to single object relation field. @andrerom suggested this PR, which does the same for legacy:
ezsystems/ezpublish-legacy#1201

This feature requires changes in 3 bundles, so I have 3 PRs. The other 2:

I made the storage of the settings in xml in the dataText5 field. It is compatible with the old way, but it drops that schema on the next save of the content type. So if someone has stuff in dataInt1 and dataInt2 cause of an upgrade, then those will work, but when someone saves the content type, then they will be emptied and converted to xml into dataText5.

I didnt include the type and object_class deprecated xml tags in this xml.

I ran test with this command: bin/phpunit vendor/ezsystems/ezpublish-kernel/ --testsuite "eZ\Publish\Core\FieldType" --filter "RelationTest"

<div class="ez-fielddefinition-setting-name">{{ 'fielddefinition.allowed-content-types.label'|trans|desc("Allowed content types:")}}</div>
<div class="ez-fielddefinition-setting-value">
{% if settings.selectionContentTypes %}
{# TODO display content type name #}
Copy link
Contributor

@andrerom andrerom Jun 18, 2017

Choose a reason for hiding this comment

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

note to self; ideally we need a multi get api end point where you can get content types by identifiers id*, then a twig function or otherwise can get them easily as a followup to this. Role editing has same need and more.

*as stated later below this should actually have been by id to avoid risk of being out of date on content type identifier changes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes that would be very useful (not just here, everywhere). I just copied the content to this new block without changes anyway.

Copy link
Contributor

@andrerom andrerom left a comment

Choose a reason for hiding this comment

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

Looks great, what I'm missing is integration test coverage for the added feature. However besides inline comment did not see further examples of that when checking quickly. Maybe @alongosz knows.

@@ -109,7 +113,7 @@ public function getValidatorSchema()
*/
public function getValidFieldSettings()
{
return array('selectionMethod' => 0, 'selectionRoot' => '1');
return array('selectionMethod' => 0, 'selectionRoot' => '1', 'selectionContentTypes' => array());
Copy link
Contributor

Choose a reason for hiding this comment

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

Like in RelationListIntegrationTest.php this can have a valid value.

And here and other places where you add arrays, use PHP 5.4+ format [].

Copy link
Contributor Author

@slaci slaci Jun 19, 2017

Choose a reason for hiding this comment

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

And here and other places where you add arrays, use PHP 5.4+ format [].

I used the old syntax cause it is used everywhere in the file, well every file I touched for these changes... didnt want to mix the style. If I fix in the whole file then the diff becomes messy, if I fix only for added lines then the source will be ugly I think.

I'm missing is integration test coverage for the added feature

I don't know how could I test this. On PHP level there is no validation for this feature. Only the js constraints the selectable content types. I guess the validate() method could check if the selected content is really in an expected content type, but relation list does'nt do this either.
On the js side, its the universal discovery's feature to constraint the stuff, so that should have the tests for that part, not this field.

Copy link
Member

Choose a reason for hiding this comment

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

I used the old syntax cause it is used everywhere in the file, well every file I touched for these changes... didnt want to mix the style. If I fix in the whole file then the diff becomes messy, if I fix only for added lines then the source will be ugly I think.

Well, we need our code to evolve (a.k.a not making a revolution), so we decided that for modified and new code we'll use short array syntax. So if we modified a big multidimensional array, we'd change the whole thing. Could be as a separate commit. However we don't change unrelated and unmodified blocks so diff won't get too messy. Look at this as a compromise :)

if (!empty($fieldSettings['selectionContentTypes'])) {
foreach ($fieldSettings['selectionContentTypes'] as $typeIdentifier) {
$allowedClass = $doc->createElement('allowed-class');
$allowedClass->setAttribute('contentclass-identifier', $typeIdentifier);
Copy link
Contributor

Choose a reason for hiding this comment

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

Another note to self: This is actually a bit of a problem. We don't guarantee identifier to stay constant over time. And have no way or intention to have to rewrite data for all fields or field definitions on such changes. So ideally we should store ID here, and as before expose identifer to user in API. But I guess that would need to be in addition to identifier in storage format for BC reasons, and I'm aware this is mistake from the past and not yours :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The transition should be easy as I did with the other fields (dataInt1 and dataInt2). We just need a new tag for the XML. Set the old tag's data as default if present (and delete it), and overwrite with the new tag's content if thats there. So later all xml's would only have the new tag.

The converter is a service so it should be easy to inject the content type service into it to resolve the ids. Just so many queries... content type service really should cache 😆 Or that multiple endpoint would be useful there.

Copy link
Member

@alongosz alongosz left a comment

Choose a reason for hiding this comment

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

Looks great, what I'm missing is integration test coverage for the added feature.

What could be done for integration tests, besides checking for valid/invalid field settings (which is already done) is to create Relation field limited to e.g. blog_post and try to create relation using different content type to check if it throws exception (assuming I understood how it should work :-))

New feature exposed in Twig template should be tested using Behat on repository-forms, but AFAIK we don't have scenarios for content type at all (or do we...?)

Besides, one small PhpDoc nitpick below:

* <constraints/>
* <selection_type value="0"/>
* <contentobject-placement/>
* </related-objects>
Copy link
Member

Choose a reason for hiding this comment

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

Please wrap the whole code example (starting with the first <?xml) in <code></code> tags so it looks good on doc preview in IDE (at least in PhpStorm) :)

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 I added the <code></code>. I added it for RelationList too, thats where I copied the original stuff from anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

is to create Relation field limited to e.g. blog_post and try to create relation using different content type to check if it throws exception

It won't throw anything. On PHP level it creates anything happily. But this is the RelationList's behavior too. The setting is only affecting the JS validation atm.

Copy link
Contributor

@andrerom andrerom left a comment

Choose a reason for hiding this comment

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

From my side +1, the additional integration tests is missing for both fields so that is maybe for us to figure out and my comments on usage of identifier is follow mistake from relation list so we can deal with that later as separate effort.

Copy link
Member

@alongosz alongosz left a comment

Choose a reason for hiding this comment

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

ok, +1 then :)

@andrerom
Copy link
Contributor

Status: Waiting on some additional tests on PlatformUI Pr which should be merged at the same time.

Copy link
Member

@bdunogier bdunogier left a comment

Choose a reason for hiding this comment

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

I like the idea, it's a cool contribution.

I'd like the settings / XML serialization process to be made more readable. Once we have figured that out, you have my approval :)

$validationErrors[] = new ValidationError(
"Setting '%setting%' value must be of array type",
null,
[
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: I would put the whole array on one line.

$storageDef->dataInt2 = (int)$fieldDef->fieldTypeConstraints->fieldSettings['selectionRoot'];
$constraints = $doc->createElement('constraints');
if (!empty($fieldSettings['selectionContentTypes'])) {
foreach ($fieldSettings['selectionContentTypes'] as $typeIdentifier) {
Copy link
Member

Choose a reason for hiding this comment

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

The mapping of settings to XML, and vice-versa below, is a bit long for procedural code within that method.

It would be more readable if moved to a private class method. The option above is to move it to a dedicated service/class. Doing so would ease unit testing.

Copy link
Contributor

Choose a reason for hiding this comment

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

@bdunogier Are you suggesting we should do something like $storageDef->dataText5 = $this->getXMLForStorageFieldDefinition($fieldDef->fieldTypeConstraints->fieldSettings);, or something else?

Implied here is that he will need to adopt RelationListConverter as well, part of the point here is to keep them in sync. Could perhaps also extract some of the logic to RelationConverterTrait and reuse, wdyt @slaci ?

Copy link
Member

Choose a reason for hiding this comment

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

I was suggesting something like:

public function toStorageFieldDefinition(FieldDefinition $fieldDef, StorageFieldDefinition $storageDef)
{
    // [...]

    $root->appendChild($this->mapConstraintsToDOM($doc, $fieldSettings, ));
    $root->appendChild($this->mapSelectionTypeToDOM($doc, $fieldSettings));
    $root->appendChild($this->mapDefaultSelectionToDOM($doc, $fieldSettings));
}

private function mapConstraintsToDOM(DOMDocument $doc, array $fieldSettings)
{
    $constraints = $doc->createElement('constraints');

    if (!empty($fieldSettings['selectionContentTypes'])) {
        foreach ($fieldSettings['selectionContentTypes'] as $typeIdentifier) {
            $allowedClass = $doc->createElement('allowed-class');
            $allowedClass->setAttribute('contentclass-identifier', $typeIdentifier);
            $constraints->appendChild($allowedClass);
            unset($allowedClass);
        }
    }

    return $constraints;
}

private function mapSelectionTypeToDOM(DOMDocument $doc, array $fieldSettings)
{
    $selectionType = $doc->createElement('selection_type');
    if (isset($fieldSettings['selectionMethod'])) {
        $selectionType->setAttribute('value', (int)$fieldSettings['selectionMethod']);
    } else {
        $selectionType->setAttribute('value', 0);
    }

    return $selectionType;
}

private function mapDefaultSelectionToDOM(DOMDocument $doc, array $fieldSettings)
{
    $defaultLocation = $doc->createElement('contentobject-placement');
    if (!empty($fieldSettings['selectionRoot'])) {
        $defaultLocation->setAttribute('node-id', (int)$fieldSettings['selectionRoot']);
    }
    
    return $defaultLocation;
}

And the mirror methods for DOM -> storage.
But it's not a hard requirement.

@andrerom
Copy link
Contributor

andrerom commented Jul 31, 2017

We'll see if we can handle the code xml cleanup across existing field type and the new code in followup PR, merging for 1.11, thanks @slaci!

@andrerom andrerom merged commit dded74a into ezsystems:master Jul 31, 2017
masacc pushed a commit to masacc/ezpublish-kernel that referenced this pull request Dec 14, 2017
…ds (ezsystems#2022)

* EZP-24800 Support allowed classes limitation for object relation fields

* EZP-24800 code style fixes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
4 participants