CodeSmith 使用教程(15) 为Yii Framework 创建生成ActiveRecord的代码模板分析

不点 阅读:165 2021-04-01 00:27:21 评论:0


CodeSmith 使用教程(3): 自动生成Yii Framework ActiveRecord 我们通过SchemaExploer为Yii Framework从数据库生成简单的ActiveRecord类,没有考虑到表和表之间的关系。本例我们使用CodeSmith为Yii Framework创建一个通用的代码模板,可以使用上例介绍的SchemaExploer ,不过在查看CodeSmith自带的例子中有个生成Hibernate的例子,这个模板的使用可以参见CodeSmith 使用教程(1): 概述 ,CodeSmith提供了这个模板的源码,使用到了CodeSmith.SchemaHelper (CodeSmith没有提供相应的文档),不过可以通过阅读NHiberante的模板了解其一般用法。

为生成Yii Framework ActiveRecord类之间的relation ,先要了解一下表和表之间的关系:

两个 AR 类之间的关系直接通过 AR 类所代表的数据表之间的关系相关联。 从数据库的角度来说,表 A 和 B 之间有三种关系:一对多(one-to-many,例如 tbl_user 和 tbl_post),一对一( one-to-one 例如 tbl_user 和tbl_profile)和 多对多(many-to-many 例如 tbl_category 和 tbl_post)。 在 AR 中,有四种关系:

  • BELONGS_TO(属于): 如果表 A 和 B 之间的关系是一对多,则 表 B 属于 表 A (例如 Post 属于 User);
  • HAS_MANY(有多个): 如果表 A 和 B 之间的关系是一对多,则 A 有多个 B (例如 User 有多个 Post);
  • HAS_ONE(有一个): 这是 HAS_MANY 的一个特例,A 最多有一个 B (例如 User 最多有一个 Profile);
  • MANY_MANY: 这个对应于数据库中的 多对多 关系。 由于多数 DBMS 不直接支持 多对多 关系,因此需要有一个关联表将 多对多 关系分割为 一对多 关系。 在我们的示例数据结构中,tbl_post_category 就是用于此目的的。在 AR 术语中,我们可以解释 MANY_MANY 为 BELONGS_TO 和 HAS_MANY 的组合。 例如,Post 属于多个(belongs to many) Category ,Category 有多个(has many) Post.

本例还是使用Chinook数据库,修改Yii Framework 开发教程(27) 数据库-关联Active Record示例。数据表之间的关系如下:

20130107001

CodeSmith 中PLINQO-NH代码位置:

缺省目录为C:\Program Files (x86)\CodeSmith\v6.5\Samples\Templates\Frameworks\PLINQO-NH

20130111001

CodeSmith.SchemaHelper定义的主要类有:

20130111002

几个主要的类为

  • EntityManager 管理所有的Entity(对应于整个数据库)
  • Entity实体类(对应到单个表,视图)
  • IAssoication 关系(定义表和表之间的关系)
  • AssoicationType 关系的类型 (见下表)

根据AssociationType ,数据库之间的关系以及Yii AR支持的几种关系,可以定义下表:

20130111003

整个模板也是采用主-从模板的方式 ,主模板枚举EntityManager中的每个Entity,然后调用子模板为每个表生成AR类:

  1. public void Generate()  
  2. {  
  3.    EntityManager entityManager = CreateEntityManager();  
  4.    foreach(IEntity entity in entityManager.Entities)  
  5.     {  
  6.         if (!(entity is CommandEntity)) {  
  7.             RenderEntity(entity);  
  8.         }  
  9.     }  
  10. }  
  11.   
  12. ...  
  13.   
  14. private void RenderEntity(IEntity entity)  
  15. {  
  16.   
  17.     string folder=@"../models/";  
  18.     EntityTemplate entityTemplate = this.Create<EntityTemplate>();  
  19.     entityTemplate.SourceEntity = entity;  
  20.     entityTemplate.RenderToFile(folder+entity.Name+".php"true);  
  21. }  
public void Generate() 
{ 
   EntityManager entityManager = CreateEntityManager(); 
   foreach(IEntity entity in entityManager.Entities) 
	{ 
		if (!(entity is CommandEntity)) { 
			RenderEntity(entity); 
		} 
	} 
} 
 
... 
 
private void RenderEntity(IEntity entity) 
{ 
 
	string folder=@"../models/"; 
	EntityTemplate entityTemplate = this.Create<EntityTemplate>(); 
	entityTemplate.SourceEntity = entity; 
	entityTemplate.RenderToFile(folder+entity.Name+".php", true); 
} 
子模板则根据每个Entity的Assoications(关系属性)为AR 生成relations函数,

  1. <?php  
  2.   
  3. class <%= SourceEntity.Name %> extends CActiveRecord  
  4. {  
  5.     public static function model($className=__CLASS__)  
  6.     {  
  7.         return parent::model($className);  
  8.     }  
  9.   
  10.     public function tableName()  
  11.     {  
  12.         return '<%= SourceEntity.GetSafeName() %>';  
  13.     }  
  14.   
  15.     <%if (SourceEntity.Associations.Count>0){ %>  
  16.     public function relations()  
  17.     {  
  18.         return array(  
  19.          <% IEnumerable<IAssociation> associations = SourceEntity.Associations; %>  
  20.          <% foreach(IAssociation association in associations) { %>  
  21.          <% if(association.Entity.Name!=association.ForeignEntity.Name) {%>  
  22.             <% if (association.AssociationType == AssociationType.ManyToOne  
  23.                 || association.AssociationType==AssociationType.ManyToZeroOrOne) { %>  
  24.             '<%= ToCameral(association.Name) %>'=>array(self::BELONGS_TO,  
  25.             '<%= association.ForeignEntity.Name %>',  
  26.             <%=GetBelongToKey(association) %>  
  27.             <% } %>  
  28.             <% if (association.AssociationType == AssociationType.OneToMany  
  29.                 || association.AssociationType==AssociationType.ZeroOrOneToMany) { %>  
  30.             '<%= ToCameral(association.Name) %>'=>array(self::HAS_MANY,  
  31.             '<%= association.ForeignEntity.Name %>',  
  32.             <%=GetKey(association) %>  
  33.             <% } %>  
  34.             <% if (association.AssociationType == AssociationType.OneToOne  
  35.                 || association.AssociationType==AssociationType.OneToZeroOrOne) { %>  
  36.             '<%= ToCameral(association.Name) %>'=>array(self::HAS_ONE,  
  37.             '<%= association.ForeignEntity.Name %>',  
  38.             <%=GetKey(association) %>  
  39.             <% } %>  
  40.             <% if (association.AssociationType == AssociationType.ManyToMany) { %>  
  41.             '<%= ToCameral(association.Name) %>'=>array(self::MANY_MANY,  
  42.             '<%= association.IntermediaryAssociation.Entity.Name %>',  
  43.             <%=GetManyToManyKey(association) %>  
  44.             <% } %>  
  45.          <% } %>  
  46.      <% } %>  
  47.         );  
  48.     }  
  49.     <% } %>  
  50. }  
  51.   
  52. ?>  
  53.   
  54. <script runat="template">  
  55.   
  56. public string ToCameral(string name)  
  57. {  
  58.     return StringUtil.ToCamelCase(name);  
  59.  }  
  60.   
  61. public string GetKey(IAssociation association)  
  62. {  
  63.     string retString=string.Empty;  
  64.   
  65.     if(association.Properties.Count>1)  
  66.     {  
  67.         retString="array(";  
  68.         foreach (AssociationProperty associationProperty in association.Properties)  
  69.         {  
  70.             retString+="'"+associationProperty.ForeignProperty.GetSafeName()+"',";  
  71.         }  
  72.         retString+="),";  
  73.     }else{  
  74.         foreach (AssociationProperty associationProperty in association.Properties)  
  75.         {  
  76.             retString+="'"+associationProperty.ForeignProperty.GetSafeName()+"'),";  
  77.         }  
  78.   
  79.     }  
  80.     return retString;  
  81. }  
  82.   
  83. public string GetBelongToKey(IAssociation association)  
  84. {  
  85.     string retString=string.Empty;  
  86.   
  87.     if(association.Properties.Count>1)  
  88.     {  
  89.         retString="array(";  
  90.         foreach (AssociationProperty associationProperty in association.Properties)  
  91.         {  
  92.             retString+="'"+associationProperty.Property.GetSafeName()+"',";  
  93.         }  
  94.         retString+="),";  
  95.     }else{  
  96.         foreach (AssociationProperty associationProperty in association.Properties)  
  97.         {  
  98.             retString+="'"+associationProperty.Property.GetSafeName()+"'),";  
  99.         }  
  100.   
  101.     }  
  102.     return retString;  
  103. }  
  104.   
  105. public string GetManyToManyKey(IAssociation association)  
  106. {  
  107.   
  108.     string retString="'"+association.ForeignEntity.GetSafeName()+"(";  
  109.   
  110.     foreach (AssociationProperty associationProperty in association.Properties)  
  111.     {  
  112.         retString+=associationProperty.ForeignProperty.GetSafeName()+",";  
  113.     }  
  114.     IAssociation intermidateAssociation=association.IntermediaryAssociation;  
  115.     if(intermidateAssociation!=null)  
  116.     {  
  117.            foreach (AssociationProperty associationProperty in intermidateAssociation.Properties)  
  118.         {  
  119.             retString+=associationProperty.ForeignProperty.GetSafeName()+",";  
  120.         }  
  121.     }  
  122.   
  123.     retString=retString.Substring(0,retString.Length-1);  
  124.     retString+=")'),";  
  125.     return retString;  
  126. }  
  127. </script>  
<?php 
 
class <%= SourceEntity.Name %> extends CActiveRecord 
{ 
	public static function model($className=__CLASS__) 
	{ 
		return parent::model($className); 
	} 
 
	public function tableName() 
	{ 
		return '<%= SourceEntity.GetSafeName() %>'; 
	} 
 
    <%if (SourceEntity.Associations.Count>0){ %> 
    public function relations() 
	{ 
		return array( 
 		 <% IEnumerable<IAssociation> associations = SourceEntity.Associations; %> 
         <% foreach(IAssociation association in associations) { %> 
         <% if(association.Entity.Name!=association.ForeignEntity.Name) {%> 
            <% if (association.AssociationType == AssociationType.ManyToOne 
                || association.AssociationType==AssociationType.ManyToZeroOrOne) { %> 
            '<%= ToCameral(association.Name) %>'=>array(self::BELONGS_TO, 
			'<%= association.ForeignEntity.Name %>', 
			<%=GetBelongToKey(association) %> 
            <% } %> 
            <% if (association.AssociationType == AssociationType.OneToMany 
                || association.AssociationType==AssociationType.ZeroOrOneToMany) { %> 
            '<%= ToCameral(association.Name) %>'=>array(self::HAS_MANY, 
			'<%= association.ForeignEntity.Name %>', 
			<%=GetKey(association) %> 
            <% } %> 
            <% if (association.AssociationType == AssociationType.OneToOne 
                || association.AssociationType==AssociationType.OneToZeroOrOne) { %> 
            '<%= ToCameral(association.Name) %>'=>array(self::HAS_ONE, 
			'<%= association.ForeignEntity.Name %>', 
			<%=GetKey(association) %> 
            <% } %> 
            <% if (association.AssociationType == AssociationType.ManyToMany) { %> 
            '<%= ToCameral(association.Name) %>'=>array(self::MANY_MANY, 
			'<%= association.IntermediaryAssociation.Entity.Name %>', 
			<%=GetManyToManyKey(association) %> 
            <% } %> 
         <% } %> 
     <% } %> 
		); 
	} 
    <% } %> 
} 
 
?> 
 
<script runat="template"> 
 
public string ToCameral(string name) 
{ 
    return StringUtil.ToCamelCase(name); 
 } 
 
public string GetKey(IAssociation association) 
{ 
    string retString=string.Empty; 
 
    if(association.Properties.Count>1) 
    { 
        retString="array("; 
        foreach (AssociationProperty associationProperty in association.Properties) 
        { 
            retString+="'"+associationProperty.ForeignProperty.GetSafeName()+"',"; 
        } 
        retString+="),"; 
    }else{ 
        foreach (AssociationProperty associationProperty in association.Properties) 
        { 
            retString+="'"+associationProperty.ForeignProperty.GetSafeName()+"'),"; 
        } 
 
    } 
    return retString; 
} 
 
public string GetBelongToKey(IAssociation association) 
{ 
    string retString=string.Empty; 
 
    if(association.Properties.Count>1) 
    { 
        retString="array("; 
        foreach (AssociationProperty associationProperty in association.Properties) 
        { 
            retString+="'"+associationProperty.Property.GetSafeName()+"',"; 
        } 
        retString+="),"; 
    }else{ 
        foreach (AssociationProperty associationProperty in association.Properties) 
        { 
            retString+="'"+associationProperty.Property.GetSafeName()+"'),"; 
        } 
 
    } 
    return retString; 
} 
 
public string GetManyToManyKey(IAssociation association) 
{ 
 
    string retString="'"+association.ForeignEntity.GetSafeName()+"("; 
 
    foreach (AssociationProperty associationProperty in association.Properties) 
    { 
        retString+=associationProperty.ForeignProperty.GetSafeName()+","; 
    } 
    IAssociation intermidateAssociation=association.IntermediaryAssociation; 
    if(intermidateAssociation!=null) 
    { 
           foreach (AssociationProperty associationProperty in intermidateAssociation.Properties) 
        { 
            retString+=associationProperty.ForeignProperty.GetSafeName()+","; 
        } 
    } 
 
    retString=retString.Substring(0,retString.Length-1); 
    retString+=")'),"; 
    return retString; 
} 
</script> 

然后generated output 就可以为数据库的表生成对应的AR类,比如生成的Track类

  1. class Track extends CActiveRecord  
  2. {  
  3.     public static function model($className=__CLASS__)  
  4.     {  
  5.         return parent::model($className);  
  6.     }  
  7.   
  8.     public function tableName()  
  9.     {  
  10.         return 'track';  
  11.     }  
  12.   
  13.     public function relations()  
  14.     {  
  15.         return array(  
  16.             'album'=>array(self::BELONGS_TO,'Album','AlbumId'),  
  17.             'genre'=>array(self::BELONGS_TO,'Genre','GenreId'),  
  18.             'mediatype'=>array(self::BELONGS_TO,'Mediatype','MediaTypeId'),  
  19.             'invoicelines'=>array(self::HAS_MANY,'Invoiceline','TrackId'),  
  20.             'playlists'=>array(self::MANY_MANY,'Playlist','playlisttrack(TrackId,PlaylistId)'),  
  21.         );  
  22.     }  
  23. }  
class Track extends CActiveRecord 
{ 
	public static function model($className=__CLASS__) 
	{ 
		return parent::model($className); 
	} 
 
	public function tableName() 
	{ 
		return 'track'; 
	} 
 
    public function relations() 
	{ 
		return array( 
            'album'=>array(self::BELONGS_TO,'Album','AlbumId'), 
            'genre'=>array(self::BELONGS_TO,'Genre','GenreId'), 
            'mediatype'=>array(self::BELONGS_TO,'Mediatype','MediaTypeId'), 
            'invoicelines'=>array(self::HAS_MANY,'Invoiceline','TrackId'), 
            'playlists'=>array(self::MANY_MANY,'Playlist','playlisttrack(TrackId,PlaylistId)'), 
		); 
	} 
} 

如果实在看不懂本例也无所谓,可以直接使用该模板,只要设置数据源 ,如果数据库的表有前缀,比如Wordpress的表有wp_ 可以设置表前缀(不是必须的)

20130111004

本例下载 ,如果需要使用本例的模板,直接把项目中protected下的codesmith 目录拷贝到你自己的项目中,然后为codesmith.csp 配置数据源(或者还有表前缀),然后生成代码即可。

20130111005

本例下载

声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

发表评论
搜索
排行榜
关注我们

一个IT知识分享的公众号