As I already stated in #113614: Add centralized token/placeholder substitution to core I do think it makes sense to add a centralised way for providing meta data for various entities. This metadata is needed in several places and would help avoiding the introduction of diverse hooks all adding similar metadata. Personally I'm realized the need of those metadata while developing rules2 (a oo-based, rewritten + improved version of rules), which would heavily rely on it. But also the token subsystem #460320: Standarized, pluggable entity loading (nodes, users, taxonomy, files, comments) and #493030: RDF #1: core RDF module are candidates to rely on it as well as various import/export modules (see fieldtool).

So I decided to start working on such a meta-data system. I'm building upon the patch in #460320: Standarized, pluggable entity loading (nodes, users, taxonomy, files, comments), which introduces hook_entity_info() which is extended in this patch to hold metadata about the entity properties. So the patch is intended to

  • support getting + setting properties
  • be the common base of the token and rdf patches and of course match my rules2 use case
  • support getting properties sanitized for output or language specific (needed for tokens, but also useful other cases)
  • to provide a simple API to use it

I've already started implementing it by reusing metadata and ideas from eaton's token patch. In contrast to the token patch this makes use of callbacks for getting the actual property values, which helps avoiding duplicated code and should improve speed (no hook invocation, no unnecessary files included).

For the API I added a class DrupalEntityPropertyWrapper which uses magic methods to allow simple getting/setting of properties and supports easy chaining: print $node->$author->mail;

So this is a first patch, which isn't ready yet, but I think it's time to get some first feedback on it!

Again: The patch relies upon #460320: Standarized, pluggable entity loading (nodes, users, taxonomy, files, comments). To ease creating the patch stacked on the entity loading patch most code lives .properties includes now. Probably we want to change this later on and e.g. move the whole entity_info hook implementation into a separate include as it's data gets cached nevertheless.

Status:
* Added Node + User properties for testing. Others are missing.
* API docs missing
* Set support missing
* Entity roles support?

Also an open question is whether we should support something I called "entity roles". The idea is that each entity has several roles it can play, which identify the set of properties it has. Obviously all field bundles would be such roles, but I think modules should be able to add further roles like "group", "book" or whatever. Thus the caller has to know and provide the roles in question to use.

@rdf-patch:
I think the the rdf patch would require the "entity roles" feature to be able to build upon this. Then it could simply add it's rdf-property mapping to the metadata and rely upon the API to get the actual data.

@token-patch:
Getting the property values works basically like getting tokens in eaton's patch, except that the chaining is done by the DrupalEntityPropertyWrapper class and not by the callbacks/hook implementation. So it's rather easy to convert the patch to rely upon this system for getting the metadata, but some topics needs to be handled by the token stuff building upon it, like formatting dates and applying default tokens. The "entity roles" feature above would enable token callers which have more knowledge about the entity in question (e.g. node type) to make use of it, so that only *really available* tokens appear in the help.

As consequence of supporting the "chained object" syntax the token/property names may not contain a dash any more. I changed it to work with camelcase thus we would end up with tokens like "node:editUrl" instead of "node:edit-url". If the latter is preferred we could to some funky replacements to convert camelcase to dashes or use underscores internally and replace them with dashes for tokens.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

fago’s picture

Status: Active » Needs work

@speed: I've done a first basic port of the token patch to this system and compared its performance to the original patch by using crell's benchmark script to compare the time needed for doing 50.000 times token replacements for "title: [node:title]".

Original token patch:
Test name: 6.18714213371 seconds

Token entity properties:
Test name: 3.67251515388 seconds

Thus using callbacks should be fine! :)

Damien Tournoud’s picture

Interesting idea. Two questions/remarks at this stage:

  • You should probably save the properties as public properties of the wrapper object when loading them with __get(). That way, you skip the __get() completely the next time.
  • This DrupalEntityPropertyWrapper looks like it could be a potential parent class / interface for the fields themselves (with the properties defined in hook_entity_info() being special cases of "static" fields). How do you see that working?
fago’s picture

You should probably save the properties as public properties of the wrapper object when loading them with __get(). That way, you skip the __get() completely the next time.

An interesting idea!
This woud be nice to speed up subsequent lookups of the same wrapper, which probably is in particular interesting for chained lookups where loading entities is involved. Usually the entity loader has static caching, but in case this cache is bypassed somehow this would help us a lot!
However there would be troubles with properties that match the name of protected class variables. But we could go and prefix them all with "_".

This DrupalEntityPropertyWrapper looks like it could be a potential parent class / interface for the fields themselves (with the properties defined in hook_entity_info() being special cases of "static" fields). How do you see that working?

Hm, I don't think I got this. So if a field would be another instance of DrupalEntityPropertyWrapper, what are the properties one can retrieve there?

Anyway I thought a bit about how to integrate this best with fields:
* We should autogenerate property info of the fields, thus we need the field type to specify it's property type. For core we could go with property types like 'integer', 'decimal', 'boolean', 'date' and 'string' (default) and entity types like the usual 'node', 'user', 'comment' but also 'file'.
* For multiple valued fields we could support arrays of something, for rules I thought of supporting a List notation.

Then as I wrote above, for the token use-case we would have to build something for formating dates (or in general properties) and using default tokens. Thus we could do a separate hook_token_format() where one states different formaters for different property types. But this makes it impossible to add in field api formatters in there as they can't be applied to non-field-api values, so probably best we make it possible to specify formatters per property as well as generic ones per property type (but not for entities).

Thus we could add another class like DrupalEntityPropertyFormatter that is automatically created for non-entity properties. Thus one could use the usual object chaining mechanism to retrieve formatted values, which would be ideal for the token use case. For getting the property value one would have to use get(), but it would be consistent to have to use get() regardless whether the property is an entity or not. Then we could use __toString() to return the default format.

So for fields with multiple values one could use the usual formatters and access the array of values. If we want to support being able to format one of the multiple values alone too, we could implement arrayaccess and return another instance of the DrupalEntityPropertyFormatter. I'm not sure that is necessary though.

fago’s picture

FileSize
14.01 KB

I just implemented the suggested lookup cache, however I noted that writing to $wrapper->$name would kill the __set() functionality, so I implemented it by storing the results in $this->cache - that way we also have no troubles with name collisions with internal class variables.

Note that this caching is per-wrapper and all this cache is gone as soon as one uses another wrapper. That way we don't ran into caching issues (like with token 6.x-1.x), but it helps speeding up subsequent lookups like:

echo $wrapper->exampleNode->author->name;
echo $wrapper->exampleNode->author->mail;

I also added support for iterating over the known properties of a wrapper. Patch attached.

fago’s picture

FileSize
21.85 KB
19.86 KB

Well I worked on integrating token formats into the system, as I described above. I add DrupalPropertyFormatWrapper and a common DrupalPropertyWrapperInterface. Thus one can use the same chaining mechanism to also apply a token format or just the default token format. Adding in field-API formatters shouldn't hard now, but it's missing yet. However with having the formating wrapper it wasn't hard to port the token patch to this system - so the token patch port is completely working now.

Attached is one patch for the property metadata system and another for token. They depend on each other as the property system makes use of the tokenformats in the DrupalPropertyFormatWrapper and token relies on the property system.

Status:
* Added Node + User properties for testing. Others are missing.
* API docs for the hooks are missing
* Set support missing
* Field-API integration is TODO
* Entity roles support is TODO

fago’s picture

FileSize
41.04 KB

re-rolled the patch as the token patch made it in now :)

@entity-bundles & co:
Thinking about that I realized that we don't need to change the property wrappers to support them, we just need to annotate the property metadata with some info when the property is available. First off bundles are obvious candidate here, but I'd suggest to also use 'tags' (instead of the above suggested term "entitiy roles" which is I imo to generic). Thus a tag can be something like 'book' or 'group' *not* reffering the the node type, but to any node-type that is a book or a group. So when the book module deals with nodes, it knows the node in question is a node and thus all book-related properties are available e.g. for token replacements.

We cannot combine 'bundles' and 'tags', as both need the full-namespace. So I'd propose to use two keys 'bundle' and 'tag' to optionally restrict the property visibility.

@Set-Support:
I just implemented this and improved the test to test it.

@field-API integration:
Most times it would suffice to a let field type to specify the property type to use and perhaps a possibility to override default getters/setters. However in some cases one field instance should generate more than one property, e.g. the long text with a separate summary (->body). For those cases we need to let the field module specify the properties.

@patch:
* Still dependent on the entity loading patch
* Updated to work with tokens in core
* Added setting support.

@hooks:
Property metadata is provided in hook_entity_info() invented by the entity loading patch. There an entry 'properties' describes the properties of an entity similar to hook_token_info() before. However it does not deal with non-entities. For that hook_token_format_info() got invented, which is used to add date formats but might also serve to add some formats for decimals, integers or text. Also the system supports additional per-property formats, which is intended to be used by the field API to add its formatters.

Any opinions on that?

fago’s picture

#460320: Standarized, pluggable entity loading (nodes, users, taxonomy, files, comments) is in -> I re-roll the patch asap. Also I'm working on field api support.

Crell’s picture

Subscribe...

fago’s picture

Status: Needs work » Needs review
FileSize
64.37 KB

ok, I rerolled the patch. As the entity loading patch is now in, I changed the previous .properties.inc includes to module.entity.inc includes, holding the whole entity_info() from a module. Thus it gets only included when entity_get_info() rebuilds its cache.

Also I implemented the field API support. It defines two new properties for hook_field_info(): 'property_type' to map to a basic property type and 'property_callbacks' to optionally add an array of callbacks for generating custom property info. That way not only field modules can customize the generated property info for their fields, but also other modules may add in callbacks that adapt the generated info. E.g. the rdf module could use that to provide a default rdf mapping for the properties of fields.

Patch updated:

• Moved hook_entity_info() implementation into .entity.inc files at the entity info gets cached.
• Added field-API support!
• Fixed field_format() to actually work and made use of it.
• Fixed module_load_includes() $name parameter to be actually useful by auto-prefixing the module name.
• Improved token formating so it can be used without any wrapper. Introduced token_format() for that.
• Added tests to test the propertyformatwrapper and field api support.

Changes to the token system:
• Made use of the entitypropertywrapper for generating token values. Thus removed hook_tokens() in favour of 'properties' in hook_entity_info().
• Changed the describing array to use 'label' instead of 'name' for consistency with the rest of hook_entity_info().
• Added a new types parameter to be able to pass in multiple objects of the same type, e.g. two users. Thus it's now possible to get tokens for $sender and $recipient users without the need of implementing a hook.

Status:
• API docs missing.
• Converted node + user module for now. Others are missing.
• Token should be able to make use of entity tags and/or bundles and entity tagging needs a test.

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
53.89 KB

omg, why has the git-mirror different formatted dates in the cvs id?! :(
For now, I just changed the patch to not remove the tokens.inc files.

Status: Needs review » Needs work

The last submitted patch failed testing.

bcn’s picture

Status: Needs work » Needs review
FileSize
64.74 KB

re-roll

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
56.03 KB

Hmm, I don't see why the bot can't install it any more. For me installing still works fine... !?

Anyway, I added some properties for the book module, a "book" entity tag as well as a test making use of it.

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

FileSize
55.93 KB

Forgot to include .entity.inc files in _field_info_collate_types(). What do you think now, bot?

fago’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
56.34 KB

Next try.

Note: hook_entity_info() is also affected by #503550: Translated strings are cached in _info() hooks, but let's fix that in those issue for all affected hooks at once.

fago’s picture

FileSize
67.14 KB

Update:
* Enhanced the token system to be able to work with properties specific to bundles or tagged entities + added a test for that.
* Migrated comment module tokens.

fago’s picture

As I'd say its feature-complete now, let's summarize what the patch does:

• It adds metadata about entity properties in hook_entity_info(), for now getter + setter callbacks may be specified as wells as customized ways for formatting properties, which is used to add in field API formatters.
• Allows specifying per bundle properties and properties per "entity tag". Thus it's possible to benefit from properties available per bundle (e.g. if the node type is known) or per "entity tag", e.g. if it's known that the node will be a book page. Also tags can be assigned to bundles, so the 'book' tag is assigned to all book page types...
Thus that mechanism allows to simple get contextually right properties and turn tokens - which is great for usablity.
• Moved hook_entity_info() implementation into .entity.inc files at the entity info gets cached.
• Automatically adds in properties for fields! Field types can specify their property-type. Additional callbacks for custom properties per field-types are supported, which may be used by field types to better customize their properties and/or by modules to add in default meta-data to field-properties, e.g. rdf module might add a default rdf mapping.
• Fixed field_format() to actually work and made use of it.
• Fixed the optional module_load_includes() $name parameter to be actually useful by auto-prefixing the module name.
• Provides a DrupalPropertyEntityWrapper class and a DrupalPropertyFormatWrapper class to easily get and optionally format properties for entites. It supports chained usage, e.g.: 
 echo $nodeWrapper->book->author->login->since;
• Changes the token system to make use of those wrappers, thus deprecating hook_tokens(). For formatting tokens hook_token_format() got introduced, which is used to define date formats but could be used to add in additional formats for e.g. numbers.
• Improved token formating so it can be used without any wrapper. Introduced hook_token_format_info() + token_format() for that.

Summarizing changes compared to the token system:
• Made use of the DrupalPropertyEntityWrapper for generating token values. Thus removed hook_tokens() in favour of 'properties' in hook_entity_info().
• Getting properties works based on callbacks, which saves us from firing up a hook when getting actual property values and allows to easily re-use callbacks.
• Changed the describing array to use 'label' instead of 'name' for consistency with the rest of hook_entity_info().
• Added a new types parameter to be able to pass in multiple objects of the same type, e.g. two users. Thus it's now possible to get tokens for $sender and $recipient users without the need of implementing a hook.
• Adding in custom tokens for a special use-case, e.g. adding tokens to a bookmarked node works by using an entity tag "bookmarked".

Patch status:
• API docs for the hooks needs to be written/updated
• Node, User, Comment token integration has been migrated, others are missing. The module.tokens.inc files can be removed, but for now I've not done so due to problems with git. I roll a separate patch as soon as needed.
• Book + Field support has been added.
• Added a bunch of tests.

Feedback wanted!

yched’s picture

"customized ways for formatting properties, which is used to add in field API formatters.".
In D7, formatters have settings, and field_format() has a $formatter_settings param. field_format_property() does not accept settings, so it will always use the default settings for the specified formatter. Might be OK, but I don't think we can rely on formatter authors to ensure the default settings make sense as defaults in a 'formatting properties' context.

fago’s picture

Hm, at least "default settings" should make sense and give a reasonable output, right?

But of course having full control would be nice. For that we could introduce a separate build mode like it's done for token and cck6. However this could be added in by a small module too, so the question is whether this should ship with core or it suffices to have just the default-settings? Thus we would have it reasonable working, but it's extensible to get full control. So imo the build-mode should be added by a contrib, but if the general opinion is to ship that with core I'm happy with that too.

fago’s picture

@include-file organisation:

Property definitions can get quite long so I think they should live outside from .module. Anyway we should follow the solution of #557542: Cache module_implements() here once there is one.

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
66.51 KB

Re-rolled.

jpetso’s picture

Heh, I think this is brilliant. Subscribe.

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
66.79 KB

re-rolled.

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
67.74 KB

Fixed the field_test.module to use 'label' not 'name'.

klausi’s picture

Status: Needs review » Needs work

Many minor coding standards issues:
module.inc: @@ -176,11 +176,18 @@
String concatenation should be formatted with a space separating the operators (dot .) and the surrounding terms
$name = isset($name) ? '.'. $name : '';

properties.inc: @@ -0,0 +1,289 @@
Line 23: @see references should be separated by "," followed by a single space and with no trailing punctuation
* @see drupal_get_property_wrapper().

token.inc: @@ -42,8 +42,8 @@
Use an indent of 2 spaces, with no tabs
* and 'mail' is a placeholder available for any 'user'.

comment.module: @@ -2439,3 +2408,69 @@
Line 63: Control statements should have one space between the control keyword and opening parenthesis
switch($name) {

fago’s picture

Status: Needs work » Needs review
FileSize
68.95 KB

fixed. thanks for helping me to spot and fix those issues.

fago’s picture

FileSize
102.44 KB

I discussed that with eaton and he was worried a bit about this would deal with the system-wide tokens. So I converted the system.tokens.inc - it just introduces an entity for the system information, so we can add properties there. Imo this makes sense to be able to deal in the same way with site-wide system settings. This should also fit nice for the RDF stuff, so it can use it to generate site-wide RDF properties.

So I also converted the file tokens and upload module tokens and fixed splitting them up properly (files are not generally attached to nodes any more).

Attached patch also removes already converted tokens.inc files. Todo are:
./modules/taxonomy/taxonomy.tokens.inc
./modules/statistics/statistics.tokens.inc
./modules/poll/poll.tokens.inc

I'd convert them all, however I'd prefer to get some more feedback first, so I haven't to convert them all multiple times ;)

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
102.44 KB

Forgot to remove upload.tokens.inc from the info file.

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
fago’s picture

FileSize
102.85 KB

grml, wrong file. Next try.

Status: Needs review » Needs work

The last submitted patch failed testing.

mh86’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
106.56 KB

and to add system.entity.inc..

fago’s picture

Puh, won! Nice game, test-bot!

fago’s picture

FileSize
106.75 KB

I talked with pwolanin about the patch and entity tags. We realized the book module integration is a bad example for tags, as any node might be a book. Thinking more about it I think there might be a lot of "entity tags" that occur per-instance and can be hardly predicted. So it's possible best for that case to stay with the token 6.x behaviour of showing everything even if it's possible not there.

Thus I'd suggest to
• allow tags to be added automatically to the entity.
• make sure getter callbacks throw the exception reliable if the property isn't there. Thus the caller can easily react as appropriate.
• Tags being not default can still work as before and can be assigned to bundles. So we avoid duplicated specified properties and can easily add further per-tag metadata like an RDF class.

So I introduced a new attribute 'default tags' for an entity info, so modules can add their tags there and migrated the added book integration to that. Updated patch attached.

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
105.58 KB

Re-rolled.

fago’s picture

FileSize
106.77 KB

OK, I started working on how to integrate the RDF stuff with that. So I come up with a improvement here, that helps modules adding new attributes to the metadata.

I refactored hook_token_format_info() into a more general hook_property_info(), which may be used to provide defaults per property type. That way one cannot only provide formats for a property type, but also defaults for any additionally introduced properties. That way the RDF integration can easily provide sane defaults for RDF related per data type. Updated patch attached.

fago’s picture

FileSize
107.02 KB

some more improvements:

• Added the possibility for field instances to specify 'property info' which is used when generating property info. Handy when manually creating instances.
• I improved handling of per-instance properties. Now bundles are auto-detected and tags have to be assigned by the module specifying the tag, that's easy by using hook_entity_load(). Then the assigned tags are picked up by the wrapper automatically. As example have a look at the updated book module intgeration.
As the wrapper detects the bundle and tags now on its own, the options for that are gone and it's easier to use. However there is still the possibility to specify 'default tags' per entity and bundle info, as well as to specify 'bundle' and 'tags' for an entity referenced by a property. It's still needed as we need this information at pre-execution time e.g. when generating token help.

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
110.54 KB

good catch, bot. Taxonomy tests don't like the initialized entity tags array. I fixed the tests to use the vids for equality comparison.

fago’s picture

-> Approaching goal 2: # be the common base of the token and rdf patches and of course match my rules2 use case

See http://drupal.org/node/493030#comment-2031672 for a patch that builds upon this for providing the missing RDFa support.

fago’s picture

fago’s picture

FileSize
144.07 KB

I added in the so far missing API docs and completed converted the remaining tokens. So the patch is complete now. However while writing docs I realized that hook_property_info() is quite poorly named as it's not only for property-info defaults, so I renamed it to hook_datatype_info(). Hope that fits better.

Patch status:
• Converts the token system to build upon the property wrapper and hook_entity_info() + hook_datatype_info()
• Existing token integration has been migrated, book module integration has been added to show example usage of an entity tag.
• Field support has been added.
• Comes with API docs and tests.
• Patch for RDFa support #575508: building RDFa support on centralized metadata

Dries’s picture

I'm not an expert on this patch yet, but based on what I've seen and read, I vote against it.

It adds a layer of abstraction and complexity that isn't really needed. The token system, for example, is fairly simple and easy to understand. There is no need to try and generalize it because it actually makes it hard and more difficult to understand. Nested layers of abstraction aren't always a good thing. Independent specialized systems can be a good thing, even if there is some overlap between them. Things like DrupalEntityPropertyWrapper's generally scare me.

I think it would be very helpful to see the real value of this patch by means of an example or something, because I don't see it yet.

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Dries, thanks for your feedback!

I agree that the name "DrupalEntityPropertyWrapper" could be better, it's not easy to find good names for such generic things - suggestions welcome. Well right, the patch adds a layer of abstraction, but I disagree that it isn't needed. An abstraction layer is a good thing to have when it helps making building things easier and hides complexity.

The proposed property-info system helps identifying, getting, formatting and setting entity properties. Thus using modules can rely on that system to easily do their job. Furthermore it provides a place (hook_entity_info()) for modules to attach further information about the properties, which in turn anyone can make use of (-> e.g. RDF mapping)

Some examples where the system is useful:
• The token system building upon it only has to care about extracting tokens and applying the replacements, while it makes use of the system for getting and formatting the actual replacements. Also we could easily extend the system with more formats for numbers, percentages, durations, money..
• The RDFa module could annotate the property-info with an RDF mapping while making use of the system for getting the actual literal values or the URIs of referenced entities. In turn, we have associated the RDF mapping with a way to get *any* property value, thus it's easy for another module to build upon it to generate usual RDF (not RDFa). (see #575508: building RDFa support on centralized metadata)
• Rules would build upon the system to get entity property values for using them as action/condition arguments.
• Rules would also use the system for generating conditions to check entity property values and actions to modify the values. That's cool to save a lot of code, but it's even needed when dealing with remote data, whose structure we cannot predict when writing code ;)
• Import modules like node import, migrate, transformations,.. all solve the problem of knowing which properties there are and how to set them properly. With that system in place, they could rely upon it for that. Also for exporting data the property-info can be easily used.

Also there might be a lot of potential further uses, some *first ideas* that came to my mind: (Of course they need more thinking & discussion)
• It might be good idea to add wrapper variables with enabled sanitizing to the templates (e.g. $node_wrapper). Thus themers would have a way to easily make use of any properties and formats, without having to care about functions, their arguments or sanitizing.
• Modules like the apache solr integration could automatically build their index upon the defined properties, thus automatically generating facets or views integration.
• Modules having to format data like views can build upon the specified formats.

Thus the property-info system
• helps to avoid having to reinvent the wheel, thus saving a lot of duplicated code in terms of info hooks as well as code to actually get/set values.
• allows modules to build upon a tested and working system, thus making their life easier.

That way modules adding some properties just have to provide info once, instead of having to do support for each of the modules building upon it on it's own. So we get a lot of support for free and better tested code. Example: Think of the workflow module adding the property "node publishing date". Automatically you get tokens, RDF support (once provided a mapping or the user specifed one!), conditions+actions for rules, import/export module support and who knows what else once more modules start making use of that.

The same way modules making use of the property-info system benefit automatically as more modules are going to implement the hook (to support module XY). Thus when we implement field API support (like the patch already does) to support RDFa for fields, we get them as tokens for free.

I hope that helped a bit see the value of it, anyway I'll continue working on the RDFa patch on top of it - hopefully this example can help to convince you and others that this really makes sense!

@implementation:
Well, how it's implemented best is another question. I know many people here aren't used to OOP and to deal with magic functions like __get() and __set(). However using OO wrappers made it possible to simply support chained usage like echo $nodeWrapper->author->login->since; what makes much sense too me.

fago’s picture

Status: Needs work » Needs review
FileSize
150.62 KB

Well while working on the RDFa support, I realized that the system isn't really complete without the possibility to deal with multiple values. Thus I had a look at that. I refactored the DrupalPropertyFormatWrapper to a DrupalPropertyValueWrapper, which is capable with dealing with lists of values by using ArrayAccess. Thus for a wrapped list it's possible to get an array of values, apply a format to the whole list or to get wrapper for single list items, thus thanks to chaining to format single items.

I added a simple default format for lists, that just implodes the default formats of the list items with commas. Then I also improved the field module support to make use of lists, when the cardinality is > 1. In turn we also get improved token support, being able to deal with multiple valued fields, e.g.:

all values: [node:field_list]
single value: [node:field_list:0]
custom format: [node:field_list:2:format]

Updated patch attached.

fago’s picture

FileSize
153.27 KB

I found bug when formating lists, fixed that and added it to the tests. Also added the previously missing node - term relation and a test using tokens for node terms (testing multiple value tokens).

fago’s picture

I posted a updated RDFa patch, see http://drupal.org/node/575508#comment-2078792. This one makes use of the multiple value support. Thanks to the property wrapper it was easy to annotate related entities.

Further as the rdf-metadata is annotated to the properties, it's easy to use them to generate usual RDF. Basically something like that suffices for generating RDF of a node in n3:


//TODO: Add registered namespaces to the output.
$entity_info = entity_get_info();
$wrapper = drupal_get_property_wrapper('node', $node);

// Loop over all properties and output them
foreach ($wrapper as $property) {
  $info = $wrapper->getPropertyInfo($property);
  if (isset($info['rdf-property'])) {
    $value = $wrapper->$property;
    if (isset($entity_info[$info['type']])) {
      // Output a URI referencing to the entity.
      $value = "<" . $value->{'rdf-uri'} . ">";
    }
    else {
      // Deal with a literal value.
      if (isset($info['rdf-value'])) {
        $value = $value->$info['rdf-value'];
      }
      if (isset($info['rdf-datatype'])) {
        $value .= '^^' . $info['rdf-datatype'];
      }
      $value = '"' . $value . '"';
    }
    foreach ((array)$info['rdf-property'] as $property) {
      $output[] = $property . " " . $value;
    }
  }
}

echo "<" . $wrapper->{'rdf-uri'} .">\n\t" . implode(";\n\t", $output) . ".";

I hope that's a good use-case showing the value!

Status: Needs review » Needs work

The last submitted patch failed testing.

yched’s picture

Hard for me to do a proper review right now, just a note that you cannot rely on the 'safe' element on a field value. 'safe' is only incidentally used by text fields, and is in no way standard across field types.

fago’s picture

Status: Needs work » Needs review
FileSize
150.33 KB

@#63:
Thanks for having a look anyway. I'm aware that 'safe' isn't used by all fields, but I thought the core fields do. List and text field seem to do so, but it looks like number does not and for lists it's the key. -> I have to fix that, thanks.

Anyway the getter provided is just a 'default' getter, thus any field module may override that. I noted that some new API docs for hook_field_info() are missing, so I added those and re-rolled the patch.

Setting back to needs-review to let the test-bot run.

fago’s picture

FileSize
150.01 KB

Re-rolled patch with suitable for -p0.

FYI, I'm using now github to develop this: http://github.com/fago/drupal

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

Status: Needs work » Needs review
FileSize
150.47 KB

@'safe' value of fields:
I had a look at this and changed the default getters to use 'safe' for fields using sanitizing and 'value' else. If this doesn't suite, the field module should override the defaults and provide custom getters. I also noted that in the API docs of hook_field_info().

Re-rolled the patch including that changes.

@yched: The list module seems to make use of 'safe' in the key formatter, but it didn't find a reference to that elsewhere? Probably a bug or am I missing something?

fago’s picture

To ease reviewing, here an up2date summary:

What does the patch?

• Adds metadata about entity properties in hook_entity_info(), for now getter + setter callbacks may be specified as wells as customized ways for formatting properties, which is used to add in field API formatters.
• Allows specifying per bundle properties and properties per "entity tag". Thus it's possible to benefit from properties available per bundle (e.g. if the node type is known) or per "entity tag", see below.
• To deal with properties only being available in certain situations, entites can be tagged. The wrapper reads the tags and adds in associated properties automatically. As an example, book module integration has been created, as any node may be a book page.
However still it's possible to set 'default tags' per entity, bundle and when referencing to another entity, as this is needed beforehand. E.g. when generating token help.
• Automatically adds in properties for fields! Field types can specify their property-type. Additional callbacks for custom properties per field-types are supported, which may be used by field types to better customize their properties and/or by modules to add in default meta-data to field-properties, e.g. rdf module might add a default rdf mapping.
• Provides a DrupalEntityPropertyWrapper and a DrupalPropertyValueWrapper class to easily get and optionally format properties for entites. It supports chained usage, e.g.: 
 echo $nodeWrapper->book->author->login->since; 
The value wrapper also supports array access in case of multiple values. E.g. echo $nodeWrapper->taxonomy[0]->parent->name;
• Multiple values support: It uses type names of the format "list" to describe list of values of a certain type. Also there is a generic list formatter that just implodes the default formats of contained values with commas. That way multiple valued fields may have their multipel values formatter, but also single values can be accessed and formatted using array access.

About the implementation:

• Properties get defined in hook_entity_info() and get cached. Per datatype defaults live in hook_datatype_info(), mostly used for providing formats (e.g. for dates), but may serve to add in default values for any attribute, e.g. the RDF patch uses it to provide sane defaults for mapping dates.
• Moved hook_entity_info() implementation into .entity.inc files at the entity info gets cached. Once we have a general solution for that, that needs probably refined.
• Patch caches translated strings. With that is dealt here: #503550: Translated strings are cached in _info() hooks
• Fixed field_format() to actually work and made use of it.
• Fixed the optional module_load_includes() $name parameter to be actually useful by auto-prefixing the module name.
• Changes the token system to make use of those wrappers, thus deprecating hook_tokens(). For specifiying token formats hook_datatype_info() is used. Currently only date formats are defined, but that way it could be used to add in additional formats for e.g. numbers, duration, money, percentages,...
• Added a function token_format() to make the defined formats usable without any wrapper.

How does that change the token system?

• Made use of the wrappers for generating token values. Thus removed hook_tokens() in favour of 'properties' in hook_entity_info().
• Getting properties works now based on the callbacks, which saves us from firing up a hook when getting actual property values, thus avoiding file inclusions. Also callbacks can be easily re-used.
• Changed the describing array to use 'label' instead of 'name' for consistency with the rest of hook_entity_info().
• Added a new $types parameter to be able to pass in multiple objects of the same type, e.g. two users. Thus it's now possible to get tokens for $sender and $recipient users without the need of implementing a hook.
• Adding in custom tokens for a special use-case, e.g. adding tokens to a bookmarked node works by associate the custom tokens with an entity tag "bookmarked".
• Thanks to the new backend token is now able to deal with multiple values, e.g. http://drupal.org/node/551406#comment-2077118.
• Thanks to formats defined in hook_datatype_info() it's now possible to easily specify formats also for integers, numbers or texts.

Patch status:

• Finished, comes with API docs and tests.
• Converts the existing token integration.
• Adds field + book module integration.
• There is patch adding rdfa support to drupal building upon this system: #575508: building RDFa support on centralized metadata.

Why do we need that?

• In short, to avoid the duplication of entity property metadata and associated code to get/set them.
• Read my previous comment http://drupal.org/node/551406#comment-2056234 and see for an usage example using this to easily generate RDF in n3: http://drupal.org/node/551406#comment-2079176

Status: Needs review » Needs work

The last submitted patch failed testing.

fago’s picture

I'm going on vacation now, so I have to give up :((

Perhaps it makes sense "starting" something like that in contrib though, let's see. ( = improved http://drupal.org/project/fieldtool)

fago’s picture

Version: 7.x-dev » 8.x-dev

I removed the formatting features, refactored the code, added more tests and moved this to http://drupal.org/project/entity. Maybe it's ready for d8 then..

fago’s picture

Component: base system » entity system
Anonymous’s picture

I believe that this work is currently happening as part of a number of different issues in D8, including #1696640: Implement API to unify entity properties and fields and #1778226: [META] Fix RDF module.

fago, could you confirm? If so, I think this issue can be closed out.

fago’s picture

Status: Needs work » Closed (duplicate)

Yep, exactly. So let's close this one.