thumbnailIn our article “Creating an OOP Class-Based Language with a Prototype-Based OOP Language”, we said:

Why try to create a class-based OOP language from a prototyped-base OOP language ? The reasons to do so are numerous like simulating an other language or use it as a model for building Semantic Layers for DSL or Code Generators, ORM Framework Tools, etc… only imagination is the limit.

So here’s a simple illustration with a PHP Framework: Symfony. Symfony is a well reknowned PHP Framework based on a MVC Model. It also uses an ORM Framework for persisting the object in the database, either Propel or Doctrine which allows to generate the database from an Object model defined by an XML or an YAML schema definition.

Let’s take the propel version as used in their Askeet tutorial. Their Model is presented below:
symfony-mcd

You could use a graphics software like dbdesigner4 to first model your database and then use this online tool to generate the xml schema file for propeller or create the xml / yaml file by hand as prefered by symfony tutorial or use our UML model mapping below which is much easier to both read and write than xml and even yaml:


unprotect 'question
unprotect 'answer
unprotect 'user
unprotect 'interest
unprotect 'relevancy

Model: {

  class question [
    Id: 0
    title: ""
    body: ""
    created_at: 'now
    updated_at: 'now
    user_Id: [User]
  ]

  class answer [
    Id: 0
    question_Id: [question]
    user_id: [user]
    body: ""
    created_at: 'now
    updated_at: 'now
  ]

  class user [
    Id: 0
    first_name: ""
    last_name: ""
    created_at: 'now
  ]

  class interest [
    question_id: [question]
    user_id: [user]
    created_at: 'now
  ]

  class relevancy [
    answer_id: [answer]
    user_id: [user]
    score: 0
    created_at: now
  ]

}

The unprotect instruction is to allow to redefine the same class several times in the same user’s session since the class function makes this protection. The most important point is that in the model above, the reference to another table is very simple to specify: just put the table between brackets. For example if question references user’s table in user_id field, you would write:


user_id: [user]

The field’s type will be easily infered through ducktyping using rebol’s type? function:


>> type? ""
== string!

This model is directly executable thanks to our class and new functions (see previous lesson to copy and paste them in console) by typing:


do Model

Simplistic isn’t it :)

If you’re too lazy to read the previous article or if you are a beginner, just copy and paste the resulting functions:


Class: func['Class Body [block!]][
    Type: to-word Class
    set Type Make Object! Body
    Protect Type
]

new: func[Class [Object!] Param-Block [block!]][
    Constructor: to-word pick pick Class 1 2
    Obj: Make Class []
    params: copy ""
    foreach param Param-Block [
      if string? param [
        param: rejoin [{"} param {"}]
      ]
      append params param
      append params " "
    ]
    do rejoin [{do get in Obj constructor} { } params]
    Obj
]

The code to transform the model into Propeller XML file follows:



class-list: [question answer user interest relevancy]

;for mapping table names, not mandatory
table-name-list: [
  question "ask_question"
  answer "ask_answer"
]

xml: copy ""

emit: func [snippet][
  append xml build-markup snippet
  append xml newline
]

emit {<?xml version="1.0" encoding="UTF-8"?>}
emit {<database name="propel" defaultIdMethod="native" noxsd="true">}

foreach object class-list [

  root-name: to-string object
  php-name: root-name
  php-name/1: Uppercase php-name/1
  if/else not none? it: select table-name-list Object [
    Table-Name: it
  ][
    Table-name: join "ask_" root-name
  ]

  emit {<table name="<%Table-name%>" phpName="<%php-name%>">}

  object-members: pick to-block mold get object 3

  foreach [-name -definition] object-members [

      name: to-string -name
      definition: -definition

      def-type: to-string (type? definition) 

      switch def-type [

        "integer" [type: "integer"

          either (name = "Id") [
            emit {<column name="<%name%>" type="<%type%>" required="true" primaryKey="true" autoIncrement="true"/>}
          ][
            emit {<column name="<%name%>" type="<%type%>" />}
          ]
        ]

        "string" [type: "longvarchar"
          either (name = "Id") [
              emit {<column name="<%name%>" type="<%type%>" required="true" primaryKey="true" autoIncrement="true"/>}
          ][
            emit {<column name="<%name%>" type="<%type%>" />}
          ]
        ]

        "date" [type: "timestamp"
          either (name = "Id") [
            emit {<column name="<%name%>" type="<%type%>" required="true" primaryKey="true" autoIncrement="true"/>}
          ][
            emit {<column name="<%name%>" type="<%type%>" />}
          ]
        ]        

        "block" [

          emit {
  <foreign-key foreignTable="<%foreign-table: definition/1%>">
    <reference local="<%foreign-table%>_id" foreign="id"/>
  </foreign-key>
          }

        ]

      ]
  ]
  emit {</table>}
]

emit {</database>}

write clipboard:// xml

xml output:

<?xml version="1.0" encoding="UTF-8"?>
<database name="propel" defaultIdMethod="native" noxsd="true">
<table name="ask_question" phpName="Question">
<column name="Id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
<column name="title" type="longvarchar" />
<column name="body" type="longvarchar" />

  <foreign-key foreignTable="User">
    <reference local="User_id" foreign="id"/>
  </foreign-key>

</table>
<table name="ask_answer" phpName="Answer">
<column name="Id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>

  <foreign-key foreignTable="question">
    <reference local="question_id" foreign="id"/>
  </foreign-key>

  <foreign-key foreignTable="user">
    <reference local="user_id" foreign="id"/>
  </foreign-key>

<column name="body" type="longvarchar" />
</table>
<table name="ask_User" phpName="User">
<column name="Id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
<column name="first_name" type="longvarchar" />
<column name="last_name" type="longvarchar" />
</table>
<table name="ask_Interest" phpName="Interest">

  <foreign-key foreignTable="question">
    <reference local="question_id" foreign="id"/>
  </foreign-key>

  <foreign-key foreignTable="user">
    <reference local="user_id" foreign="id"/>
  </foreign-key>

</table>
<table name="ask_Relevancy" phpName="Relevancy">

  <foreign-key foreignTable="answer">
    <reference local="answer_id" foreign="id"/>
  </foreign-key>

  <foreign-key foreignTable="user">
    <reference local="user_id" foreign="id"/>
  </foreign-key>

<column name="score" type="integer" />
<column name="created_at" type="timestamp" />
</table>
</database>

Of course, you can adapt this for any kind of output: YAML, …

The whole source code for this example is below:


Rebol []

Class: func['Class Body [block!]][
    Type: to-word Class
    set Type Make Object! Body
    Protect Type
]

new: func[Class [Object!] Param-Block [block!]][
    Constructor: to-word pick pick Class 1 2
    Obj: Make Class []
    params: copy ""
    foreach param Param-Block [
      if string? param [
        param: rejoin [{"} param {"}]
      ]
      append params param
      append params " "
    ]
    do rejoin [{do get in Obj constructor} { } params]
    Obj
]

unprotect 'question
unprotect 'answer
unprotect 'user
unprotect 'interest
unprotect 'relevancy

Model: {

  class question [

    Id: 0
    title: ""
    body: ""
    created_at: 'now
    updated_at: 'now
    user_Id: [User]

  ]

  class answer [

    Id: 0
    question_Id: [question]
    user_id: [user]
    body: ""
    created_at: 'now
    updated_at: 'now

  ]

  class user [

    Id: 0
    first_name: ""
    last_name: ""
    created_at: 'now
  ]

  class interest [
    question_id: [question]
    user_id: [user]
    created_at: 'now
  ]

  class relevancy [
    answer_id: [answer]
    user_id: [user]
    score: 0
    created_at: now
  ]

}

do model

class-list: [question answer user interest relevancy]
table-name-list: [
  question "ask_question"
  answer "ask_answer"
]

xml: copy ""

emit: func [snippet][
  append xml build-markup snippet
  append xml newline
]

emit {<?xml version="1.0" encoding="UTF-8"?>}
emit {<database name="propel" defaultIdMethod="native" noxsd="true">}

foreach object class-list [

  root-name: to-string object
  php-name: root-name
  php-name/1: Uppercase php-name/1
  if/else not none? it: select table-name-list Object [
    Table-Name: it
  ][
    Table-name: join "ask_" root-name
  ]

  emit {<table name="<%Table-name%>" phpName="<%php-name%>">}

  object-members: pick to-block mold get object 3

  foreach [-name -definition] object-members [

      name: to-string -name
      definition: -definition

      def-type: to-string (type? definition) 

      switch def-type [

        "integer" [type: "integer"

          either (name = "Id") [
            emit {<column name="<%name%>" type="<%type%>" required="true" primaryKey="true" autoIncrement="true"/>}
          ][
            emit {<column name="<%name%>" type="<%type%>" />}
          ]
        ]

        "string" [type: "longvarchar"
          either (name = "Id") [
              emit {<column name="<%name%>" type="<%type%>" required="true" primaryKey="true" autoIncrement="true"/>}
          ][
            emit {<column name="<%name%>" type="<%type%>" />}
          ]
        ]

        "date" [type: "timestamp"
          either (name = "Id") [
            emit {<column name="<%name%>" type="<%type%>" required="true" primaryKey="true" autoIncrement="true"/>}
          ][
            emit {<column name="<%name%>" type="<%type%>" />}
          ]
        ]        

        "block" [

          emit {
  <foreign-key foreignTable="<%foreign-table: definition/1%>">
    <reference local="<%foreign-table%>_id" foreign="id"/>
  </foreign-key>
          }

        ]

      ]
  ]
  emit {</table>}
]

emit {</database>}

write clipboard:// xml

1 people like this post.
Bookmark and Share