processwire-requests icon indicating copy to clipboard operation
processwire-requests copied to clipboard

Field name aliases in template context

Open Toutouwai opened this issue 6 years ago • 14 comments

Short description of the enhancement

When setting up fields for a project, there's always a trade-off between code readability and efficient use of fields.

In one extreme you create new fields for every usage in every template and name the fields according the usage. So if in template "person" you need a field for age and in template "walk" you need a field for distance you then you create separate integer fields "age" and "distance". The pro here is that your field names are self-explanatory when used in template files, but the con is an unnecessarily large number of fields.

In the other extreme you only ever create the minimum number of fields with generic names like "text_1", "integer_1", etc. So in the example above you use field integer_1 in both person and walk templates. The pro here is that your field use is very efficient, but the con is that your field names are meaningless within your template files, and you find yourself needing to add comments every time the fields are used.

I have an idea that might allow the best of both worlds but I'm not sure about the ideal way to implement it. So this is just one way it could be done...

Fields could be given "aliases" in template context. So when I add the field integer_1 to my person template, I edit the field in template context from the template editor and give it the alias "age". Now when I want to get the field value in my template file I can get it using the alias plus some special symbol (e.g. *) to indicate that I am using an alias name.

echo $page->*age; // the same as $page->integer_1

So when PW is asked for a field name preceded by an asterisk it knows to check for a field with that alias in $page's template context.

And perhaps if you needed to get a field by alias independent of a template context you could supply the template name before the asterisk.

$field = $fields->person*age; // the same as $fields->integer_1

So, in general, PW would give special treatment when asked to deal with a field name that contains an asterisk.

Having a feature like this would allow for the efficient use of fields and more readable code without needing a lot of explanatory comments.

Toutouwai avatar Jan 25 '18 00:01 Toutouwai

Okay, the asterisk (or other special character) idea isn't going to fly because it's invalid syntax. But I'm sure someone will have a good idea for how to indicate a field name alias.

Toutouwai avatar Jan 25 '18 00:01 Toutouwai

I do like this idea - I actually suggested somewhere a long time ago, but I don't remember where or what Ryan's comment about it was :)

adrianbj avatar Jan 25 '18 00:01 adrianbj

Okay, the asterisk (or other special character) idea isn't going to fly because it's invalid syntax. But I'm sure someone will have a good idea for how to indicate a field name alias.

An API should be as self-explanatory as possible, but if I saw a request like $page->*age or $page->__age or $page->a_age (or whatever syntax we might come up with) I wouldn't know what it does without checking the docs first. In this regard I must say that I'm against this idea – or at least that kind of implementation of this idea :)

How about a method call, such as $page->alias('age'), instead? For the latter use case it could be something along the lines of $fields->alias('person', 'age'). While that's a bit verbose, it's also a bit more obvious. If verbosity is an issue, perhaps it could be shortened to a() – although that's not very self-explanatory either.

I must also admit that I'm questioning whether this is really something that should be in the core, or perhaps it would be better to implement this as a third party module?

teppokoivula avatar Jan 29 '18 14:01 teppokoivula

How about a method call, such as $page->alias('age'), instead?

Good idea.

I must also admit that I'm questioning whether this is really something that should be in the core, or perhaps it would be better to implement this as a third party module?

A module could be okay, but getting a field value is such a common, basic need that you'd want it to be as efficient as possible. Seems like having this in the core would allow for greater efficiency than in a module that would have to add hooks.

Toutouwai avatar Jan 29 '18 21:01 Toutouwai

I like the idea of aliases, but if we pursue them, I kind of think they should be global (as a choice) rather than just specific to template context. Otherwise it could add a real challenge to writing code that is shared among multiple templates. Supporting the option for template context would of course be great as an option, and I'm thinking that depending on the case, one may be more useful than the other. As for some special syntax or using function calls, why not just use regular field syntax? $page->age. I don't think the API code needs to project than it's using an alias (with some different syntax). After all, if using aliases, I think you'd want to be able to use them in selectors too, or anywhere else you might use a field name.

ryancramerdesign avatar Feb 06 '18 11:02 ryancramerdesign

I must admit that I was mostly thinking that aliases would streamline template-specific field naming, ie. cases where you'd otherwise have multiple fields with template-specific prefix and more common suffix: contact_email, admin_email, notifications_email, longcat_length, human_length, movie_length, etc.

Obviously that's not compatible with what Ryan mentions above at all, but perhaps this isn't what others were thinking of either? Also: I like Ryan's idea more, to be honest, even though I'm still a bit worried that this will make debugging more complicated without actually adding that much value.

Just my five cents.

teppokoivula avatar Feb 06 '18 15:02 teppokoivula

I like the idea of aliases for fields because I have built several sites now that have dozens of fields that do the same thing, similar to the age and distance example.

It might be as simple as adding a textbox in the field creation area that would accept a comma-separated list of aliases for that field. No template would need to be specified, it would just be accessible as one of the names for that list.

For example, an Integer field named num_int. Aliases: age, distance, weight.

For a template, it would be added as num_int, but accessible in these ways:

$page->num_int;
$page->age;
$page->distance;
$page->weight;

Two issues with this approach would be if you wanted to use the same fieldtype more than once on a given template, and second, we'd have to make sure on field-edit-save that the alias being input wasn't already in use.

EDIT:

Another idea...but considerably more complex. Maybe there's a new fieldtype called FieldtypeAlias. When selecting that fieldtype during creation, an additional step exists whereby the true Fieldtype is selected. In this case, FieldtypeInteger. The field is then set up as an Integer field like normal, but has a standardized name.

When adding it to a Fieldgroup/Template, you would then specify its alias (name) for that instance on that template. This would allow you to add several of the same FieldtypeAlias-es to the same template, all with different names, but a single set of options.

ethanbeyer avatar Feb 06 '18 15:02 ethanbeyer

Two issues with this approach would be if you wanted to use the same fieldtype more than once on a given template,

Not sure I'm getting the first one right – if aliases are global, you could only add each field to any given template once, and if it has one or more aliases, they would all point to the same value, right? As far as I can tell, this has nothing to do fieldtypes per se.

and second, we'd have to make sure on field-edit-save that the alias being input wasn't already in use.

Agreed. For this to be (as) intuitive (as possible) and work as expected with selectors etc. ProcessWire should a) make sure that field names and aliases don't overlap and that b) aliases don't overlap with other aliases :)

teppokoivula avatar Feb 06 '18 16:02 teppokoivula

Not sure I'm getting the first one right – if aliases are global, you could only add each field to any given template once, and if it has one or more aliases, they would all point to the same value, right? As far as I can tell, this has nothing to do fieldtypes per se.

I'm seeing each alias as a single field with the same options, but different values per alias.

ethanbeyer avatar Feb 06 '18 16:02 ethanbeyer

I'm seeing each alias as a single field with the same options, but different values per alias.

Right, now I get it :)

This sounds like a different thing to me, though: whereas in the case of simple aliases ProcessWire could internally convert them to field names, in order to achieve what you've described here it would also have to create new fields behind the scenes and keep their settings in sync – or perhaps switch to a different storage method altogether.

My guess is that this isn't really feasible, but @ryancramerdesign would know for sure.

teppokoivula avatar Feb 06 '18 16:02 teppokoivula

I like the idea of aliases, but if we pursue them, I kind of think they should be global (as a choice) rather than just specific to template context.

I agree. Having the possibility to use aliases in selectors would outweigh any minor benefits to defining them in template context (e.g. the ability to use the same alias to refer to different fields in different template contexts).

Toutouwai avatar Feb 07 '18 21:02 Toutouwai

As this topic has come up in the forum lately I have to raise my voice against this request. I have removed my thumb up :)

I think having this feature could potentially introduce a lot more trouble than benefits. For me this is absolutely not an issue any more and I think it is even less an issue now that the core is a lot more performant on dealing with many fields and templates.

What I also fear is that such a feature could break many other modules or could make advanced topics like writing custom database queries or working with the query selector engine even more complex. I'd be quite afraid for example that such a feature would mean that I have to do a lot of work to also support it in RockFinder.

Next, I'd highly recommend using custom page classes whenever possible. It makes working with PW so much better! When working with custom page classes there's really no need for field aliases as you can simply add your custom methods to that class.

For example if you had a field body for the main body copy and a field body1 that holds some teaser text you could simply add two methods to your custom page class (the backend):

class FooPage extends Page {

  public function teaser() {
    return $this->body1;
  }

}

On the frontend you could then simply do this:

<h1><?= $page->title ?></h1>
<div><?= $page->teaser() ?></div>
<?= $page->body ?>

There's also the option of adding page properties via hooks:

$wire->addHookProperty("Page::teaser", function($event) {
  $event->return = $page->body1;
});

Which would make your frontend look like this:

<h1><?= $page->title ?></h1>
<div><?= $page->teaser ?></div>
<?= $page->body ?>

Ok but what about selectors you might think?

$pages->find("body1%=$searchterm");
// vs
$pages->find("teaser%=$searchterm");

The latter one would obviously not work. But there's also a solution to this problem: Class constants! I'm using them all over as I'm writing all my fields in code via RockMigrations but you can use them without RockMigrations and it is maybe already something very close to what you are requesting:

class FooPage extends Page {

  const field_teaser = "body1";

 ...

}

This would enable you to use a selector like this:

$pages->find([
  [FooPage::teaser, '%=', $searchterm],
  'limit' => 20,
  'sort' => '-created',
]);
// or string syntax
$pages->find(FooPage::teaser."%=$searchterm,limit=20,sort=-created");

If you don't want to use custom page classes for that you can simply add an aliases class to _init.php:

class Aliases {
  const teaser = 'body1';
}

You'd even get the benefit of code completion with such a setup: image

This is how you'd access the teaser field from the frontend:

$page->get(Aliases::teaser);

So you can already use class constants as alias for existing fields and the core would need no change at all.

Hope that makes sense. Here is the link to the mentioned thread with a slightly different example of what I showed above: https://processwire.com/talk/topic/26898-how-do-you-name-your-fields/?do=findComment&comment=222522

BernhardBaumrock avatar Mar 17 '22 22:03 BernhardBaumrock

@BernhardBaumrock, I like your suggestions. But I think it would be relatively easy for the core to support field aliases in a way that doesn't depend on the user writing code in custom page classes, and doesn't need to result in anyone's module breaking.

The way I'm imagining it, the core would introduce some public method like Fields::getFieldName($name_or_alias) and make use of that wherever it knows it is dealing with a string that represents a field name. So it's not going to impact on core database queries because the real field name will have been substituted before then. And if a third-party module wants to support field aliases in some scenario where the core is not already going to automatically handle the alias then it can call the public method.

Toutouwai avatar Mar 18 '22 00:03 Toutouwai

If it is really that easy why not. Personally I just don't have the need for it. But my feeling is that it will not be that easy. Your last sentence is somewhat supporting that point. I think there will be situations where things break or just get more complex. Even if there was a public method to call, we'd have to do that. And if we wanted to support older versions we'd first have to check if the method exists... I don't see the benefit if everybody has so simple alternatives at hand :)

BernhardBaumrock avatar Mar 18 '22 13:03 BernhardBaumrock