13
\$\begingroup\$

I'm developing a web application that leverages multiple programming languages. The data flow resembles:

  • Browser » PHP » PL/SQL » XML » XSLT » XHTML + JavaScript » Browser

Using different languages makes it tempting to hard-code various constants within the different languages, which encourages inconsistencies. Without a single source, it is highly probable that "Your Name" will sometimes be "Your name" or "your name" or even "Username", depending on what language requires the value.

To avoid duplicating these values, a "configuration" table exists:

| code              | label               | languages  | data_type |
+-------------------+---------------------+------------+-----------+
| DEFAULT_USER_NAME | Your Name           | xsl,js,php | string    |
| BASE_IMAGE        | ${BASE_APP}images/  | xsl,js     | string    |
| BASE_ICON         | ${BASE_IMAGE}icons/ | xsl,js     | string    |
| BASE_APP          | /app/               | xsl,js,php | string    |

The configuration table is then converted to XML, with the references expanded as expected:

BASE_ICON = /app/images/icons/

Once in XML, files for constants are generated for the appropriate syntax:

  • constants.php
  • constants.xsl
  • constants.js

Since the constants are now defined in a single table, there is no more duplication.

Problem

I am concerned that the problem of multi-language constants has a technically better solution.

When a project uses multiple programming languages that tightly communicate, how would you prevent constant values from being repeated throughout different language-specific source files?

In other words, is there a better design for eliminating duplicate values across different programming languages?

Generate XML

The code to generate the XML resembles...

CREATE OR REPLACE FUNCTION generate_configuration_xml()
  RETURNS text AS
$BODY$
DECLARE
  v_result TEXT DEFAULT '<configuration/>';
BEGIN
  SELECT
    xmlroot (
      xmlelement( name "configuration",
        xmlagg(
          xmlelement( name "constant",
            xmlattributes(
              c.code AS "name",
              c.languages AS "languages"
            ),
            xmlelement( name "value",
              xmlattributes(
                c.data_type AS "type"
              ),
              recipe.get_configuration_value( c.code, 0 )
            )
          )
        )
      ),
      VERSION '1.0',
      STANDALONE YES
    )
  INTO
    v_result
  FROM
    configuration c;

  RETURN v_result;
END;

Get Configuration Value

The code to retrieve a constant (as called above) resembles:

LOOP
  SELECT substring( v_result from '\$\{.+?\}' ) INTO v_code_replace;
  EXIT WHEN v_code_replace IS NULL;

  p_code := substr( v_code_replace, 3, length( v_code_replace ) - 3 );
  v_code_value := get_configuration_value( p_code, p_depth + 1 );
  v_result := replace( v_result, v_code_replace, v_code_value );
END LOOP;

Generated XML

This produces an XML file similar to the following:

<?xml version="1.0" standalone="yes"?>
<configuration>
  <constant name="BASE_IMAGE" languages="xsl.js">
    <value type="string">/app/images/</value>
  </constant>
  <constant name="BASE_ICON" languages="xsl.js">
    <value type="string">/app/images/icons/</value>
  </constant>
  <constant name="DEFAULT_USER_NAME" languages="xsl.js,php">
    <value type="string">Your Name</value>
  </constant>
</configuration>

Transformation

Using the above language-neutral XML document, XSL can generate constants in all programming languages used by the project. The template to transform the constants into various language-specific declarations resembles:

<xsl:template match="//configuration">
  <xsl:choose>
    <xsl:when test="$language = 'xsl'">
      <xsl:call-template name="xsl-header" />
      <xsl:text disable-output-escaping="yes"><![CDATA[<!-- ]]></xsl:text> <xsl:call-template name="comment" /> <xsl:text disable-output-escaping="yes"><![CDATA[ -->]]></xsl:text>
      <xsl:text>&#xA;</xsl:text>
      <xsl:apply-templates select="constant[contains(@languages,'xsl')]" mode="xsl" />
      <xsl:call-template name="xsl-footer" />
    </xsl:when>
    <xsl:when test="$language = 'php'">
      <xsl:call-template name="php-header" />
      <xsl:text>// </xsl:text><xsl:call-template name="comment" />
      <xsl:text>&#xA;</xsl:text>
      <xsl:apply-templates select="constant[contains(@languages,'php')]" mode="php" />
      <xsl:call-template name="php-footer" />
    </xsl:when>
    <xsl:when test="$language = 'javascript'">
      <xsl:text>// </xsl:text><xsl:call-template name="comment" />
      <xsl:text>&#xA;</xsl:text>
      <xsl:apply-templates select="constant[contains(@languages,'javascript')]" mode="javascript" />
    </xsl:when>
  </xsl:choose>
</xsl:template>

XSL

The XML can be transformed into XSL (e.g., constants.xsl):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="DEFAULT_USER_NAME">Your Name</xsl:variable>
</xsl:stylesheet>

JavaScript

The XML can be transformed into JavaScript (e.g., constants.js):

var DEFAULT_USER_NAME = "Your Name";

PHP

And the XML can be transformed into PHP (e.g., constants.php):

<?php
$DEFAULT_USER_NAME = 'Your Name';
?>

These files can then be included by their respective "main" entry points. Any time a new value is required (i.e., change "Your Name" to "Click 'Your Name' To Begin"), the constants can be auto-generated from the configuration table, enforcing consistency.

Execution

The constants are then generated as follows:

#!/bin/bash

# Fetch the most recent values for the constants.
psql schema -t -A -q -c 'select generate_configuration_xml();' -o constants.xml

# Transform the constants into a programming language variable.
xsltproc --stringparam language javascript generate.xsl constants.xml > constants.js
xsltproc --stringparam language xsl generate.xsl constants.xml > constants.xsl
xsltproc --stringparam language php generate.xsl constants.xml > constants.php

rm constants.xml

This produces a number of different files. Publishing the same constants in Java would require little more than the following:

xsltproc --stringparam language java generate.xsl constants.xml > Constants.java

Is there a better way?

\$\endgroup\$
6
  • \$\begingroup\$ What's wrong with putting everything in this table? Each language doesn't have to use all of it. \$\endgroup\$ Dec 26, 2012 at 21:14
  • \$\begingroup\$ There's nothing wrong with putting everything in the table. I'm wondering what other approaches are possible -- perhaps using a table to generate constants for different programming languages is not ideal? \$\endgroup\$ Dec 26, 2012 at 21:54
  • \$\begingroup\$ This is a very interesting dilemma. But you should know that this is code review and code to review is required by the FAQ, even if that code is just a sample or invented on the spot (as long as it works). Besides, I'm having a difficult time trying to figure out exactly what you are talking about. If you could provide an example in each case that would help immensely. \$\endgroup\$
    – mseancole
    Dec 26, 2012 at 21:57
  • \$\begingroup\$ (assume Oracle because of PHP/SQL) What is the value of including xml and xlst in the mix, why not just use PHP and the Oracle Database on backend, JS in front-end. This would solve most of your worries. \$\endgroup\$
    – konijn
    Sep 10, 2014 at 19:51
  • \$\begingroup\$ Gotcha! Thanks for shedding light on this. \$\endgroup\$
    – konijn
    Sep 10, 2014 at 21:33

1 Answer 1

6
+50
\$\begingroup\$

I'm not familiar with PL/SQL so I'm unsure what exactly is being done with it. However, the commonly accepted solution for sharing between PHP and JavaScript is to use a JSON file. JSON is native to JS, its even in the name. PHP also supports JSON via its json_encode() and json_decode() functions. Here's an example of what the JSON file might look like:

{
    "BASE_IMAGE" : "/app/images/",
    "BASE_ICON" : "/app/images/icons/",
    "DEFAULT_USER_NAME" : "Your Name"
}

You can import a JSON string with json_decode(), which means you need to read the file into a string first, in order to get either a JSON object or a multidimensional array.

//as an object
$constants = json_decode( $json );
echo $constants->BASE_IMAGE;// echos /app/images/

//as an array
$constants = json_decode( $json, TRUE );
echo $constants[ 'BASE_IMAGE' ];// echos /app/images/

And of course, if you wish to use them as actual constants, you can loop over the array and define them.

foreach( $constants AS $constant => $value ) {
    define( $constant, $value );
}

I believe this accomplishes the same thing you are currently doing, only without the extra files. The only downfall is that you wont be able to use those XML entities to declare shared elements, unless of course you end up generating the JSON from the XML. However, I think that might be just a bit much. At that point you should then just turn to some JS library to read the XML directly as you can already do with PHP. Hope this helps.

\$\endgroup\$
7
  • \$\begingroup\$ @DaveJarvis: I was assuming the XSLT was simply to help convert the XML file into the various languages you needed to use them in. If the XML file became unnecessary wouldn't the XSLT file also become unnecessary? I don't know PL/SQL so I'm unable to offer much advice here, but a quick google search suggests there might be libraries for it. The same goes for bash. I know Java has libraries, though I haven't had much experience with them. The problem would be downloading these extra libraries, but I don't know of any other way. Then again, I didn't even know about the one you found. \$\endgroup\$
    – mseancole
    Dec 27, 2012 at 17:50
  • \$\begingroup\$ The same value (e.g., DEFAULT_USER_NAME) could be used in PHP, JavaScript, XSL, Java, PL/SQL, or even bash. For PL/SQL, there's not much of an issue because the database can select from the configuration table directly. I'm wondering if this approach (using a configuration table, generating an XML document, then transforming the constants into their native implementation language via XSL) to isolate duplicate constants is a good design. I don't know why you mention Java libraries -- it has nothing to do with the problem? \$\endgroup\$ Dec 27, 2012 at 18:58
  • \$\begingroup\$ @DaveJarvis: I mentioned Java and bash libraries because you mentioned possible future expansion into them. I think I may have been missing something which is why there seems to be confusion. I was under the impression your configuration table began with the XML file. But I think I got it now. Is PL/SQL being used for anything else, or would removing it, the original config table, the XML, AND XSLT and replacing them with a single JSON file be plausible? As I mentioned, all those other languages have libraries to import the JSON to be used directly, which seemingly solves your dilemma. \$\endgroup\$
    – mseancole
    Dec 27, 2012 at 19:19
  • \$\begingroup\$ The configuration table is, quite literally, a database table. I use the configuration table to generate an XML document, and that XML document becomes the source point for creating variables in other languages. There is PL/SQL code that relies on the configuration table. Moving to JSON away from a configuration table means duplicating '/app/' in the JSON file. I'm trying to avoid duplication altogether. Plus, maintaining a database is easier than a JSON file (i.e., there is a huge supporting infrastructure that exists for generating web based code table editors). \$\endgroup\$ Dec 27, 2012 at 20:50
  • 1
    \$\begingroup\$ But then to access the XML, wouldn't I have to write and maintain import code (i.e., more than one line of code) for every language in separate source files? (As opposed to one XSL file, which also avoids repetitious logic?) \$\endgroup\$ Dec 27, 2012 at 23:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.