> 백엔드 개발 > PHP 튜토리얼 > php7扩展开发[11] MVC之路由解析和加载文件

php7扩展开发[11] MVC之路由解析和加载文件

WBOY
풀어 주다: 2016-06-23 13:09:30
원래의
1223명이 탐색했습니다.

```
场景:想要用C实现PHP的一个MVC结构的路由解析和加载文件的功能,一共要解决几个问题
1.由于MVC要加载多个C文件,所以要修正config.m4,修改config.m4内容第十行左右,去掉dnl,
PHP_ARG_WITH(dora, for route support,
dnl Make sure that the comment is aligned:
[  --with-route             Include dora support])
在下面追加到以下内容:
if test -z "$PHP_DEBUG" ; then
    AC_ARG_ENABLE(debug, [--enable-debug compile with debugging system], [PHP_DEBUG=$enableval],[PHP_DEBUG=no] )
fi

最后一行,加载所需所有C文件,如下:
  PHP_NEW_EXTENSION(dora, dora.c common/utilts.c loader/loader.c route/route.c controller/controller.c model/model.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
  PHP_ADD_BUILD_DIR([$ext_builddir/common])
  PHP_ADD_BUILD_DIR([$ext_builddir/loader])
  PHP_ADD_BUILD_DIR([$ext_builddir/route])
  PHP_ADD_BUILD_DIR([$ext_builddir/controller])
  PHP_ADD_BUILD_DIR([$ext_builddir/model])
 
```
```c

#include "utilts.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "Zend/zend_list.h"
#include "Zend/zend_interfaces.h"

//执行PHP文件函数
int zend_execute_scripts_ext(char *filepath){

    zval retval;

    zend_file_handle zfd;
    zfd.type = ZEND_HANDLE_FILENAME;
    zfd.filename = filepath;
    zfd.free_filename = 0;
    zfd.opened_path = NULL;

    //zend_execute_scripts(int type, zval *retval, int file_count, ...);
    //FAILURE OR SUCCESS
    return  zend_execute_scripts(ZEND_INCLUDE TSRMLS_CC,&retval,1,&zfd);
    


}


//调用类中的方法
int call_user_class_method(zval *retval, zend_class_entry *obj_ce,
                           zval *obj, zval func,  uint32_t params_count, zval params[]){


    HashTable *function_table;

    if(obj) {
                function_table = &Z_OBJCE_P(obj)->function_table;
        }else{
                function_table = (CG(function_table));
    }

    zend_fcall_info fci;  
    fci.size = sizeof(fci);  
    fci.function_table = function_table;  
    fci.object =  obj ? Z_OBJ_P(obj) : NULL;;
    fci.function_name = func;   
    fci.retval = retval;  
    fci.param_count = params_count;  
    fci.params = params;  
    fci.no_separation = 1;  
    fci.symbol_table = NULL;  


 
    //FAILURE OR SUCCESS
    return  zend_call_function(&fci, NULL TSRMLS_CC);         //函数调用结束。  

}


```
```c
3.修改php_route.h头文件内容

在第五十行左右,加入以下内容

//定义类
extern zend_class_entry *route_ce;
//定义loader类中的方法
PHP_METHOD(route_ce,__construct);
PHP_METHOD(route_ce,run);
```
```c
4.修改route.c文件内容

/**
 * 声明构造函数
 * @param
 * @return
 */
ZEND_METHOD(route,__construct){

 
    zval *app_dir;

    if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &app_dir) == FAILURE )
    {
        RETURN_NULL();
    }
    //zend_update_static_property_stringl(zend_class_entry *scope, const char *name, size_t name_length, const char *value, size_t value_length);
    zend_update_static_property(route_ce, "app_dir", sizeof("app_dir")-1, app_dir TSRMLS_CC);

}



/**
 * 加载view
 * @param
 * @return
 */
ZEND_METHOD(route,run){


      zend_string* controller_name = zend_string_init("Index",strlen("Index"),0);
      zend_string* action_name     = zend_string_init("Index",strlen("Index"),0);

      zval *c_result;
      zval *a_result;
      int flag;



      //设置站点目录
      zval *app_dir = zend_read_static_property(Z_OBJCE_P(getThis()), "app_dir", sizeof("app_dir")-1, 0 TSRMLS_DC);


      //获取GET请求参数hashtable
      zval *get_arr = &PG(http_globals)[TRACK_VARS_GET];
      HashTable *ht= HASH_OF(get_arr);
      //int array_count = zend_hash_num_elements(Z_ARRVAL_P(get_arr));
    

      //获取controller_name
      zend_string *c_key= zend_string_init("controller", sizeof("controller")-1, 0);

      if ((c_result = zend_hash_find(ht, c_key)) != NULL) {
      
            controller_name = zval_get_string(c_result);
        
      }else{

            zend_error_noreturn(E_CORE_ERROR,  "Couldn't find controller param in url.");

      }
      //释放key的变量
      zend_string_release(c_key);


      //获取action_name
      zend_string *a_key= zend_string_init("action", sizeof("action")-1, 0);

      if ((a_result = zend_hash_find(ht, a_key)) != NULL) {

            action_name = zval_get_string(a_result);
            //php_printf("%s\n", Z_STRVAL_P(a_result));
            //php_printf("%s\n", zval_get_string(a_result));
      }else{

            zend_error_noreturn(E_CORE_ERROR,"Couldn't find action param in url.");

      }

      //释放key的变量
      zend_string_release(a_key);


      //拼装controller文件路径
      char *path = Z_STRVAL_P(app_dir);
      char *c_2 = "controllers/";
      strcat(path,c_2);

      //zend_string->char *
      char *c_3 = ZSTR_VAL(controller_name);
      strcat(path,c_3);

      char *c_4 = ".php";
      strcat(path,c_4);

      //php_printf("%s\n", c_1);
      // php_printf("%s\n", controller_name);
      // php_printf("%s\n", action_name);
      //PHPWRITE(Z_STRVAL_P(app_dir), Z_STRLEN_P(app_dir));



      //加载执行controller文件
      flag = zend_execute_scripts_ext(c_1);

      if(flag == FAILURE){

            zend_error_noreturn(E_CORE_ERROR,"Couldn't find file: %s.",c_1);

      }


      //查找controller对应的
      //zend_class_entry *zend_lookup_class(zend_string *name);
      zend_class_entry *controller_ce = zend_lookup_class(controller_name);

      if(controller_ce == NULL){

            zend_error_noreturn(E_CORE_ERROR,"Couldn't find file: %s.",c_1);
      }


      zval obj;
      object_init_ex(&obj, controller_ce);

      
      zval function_name;
      ZVAL_STRING(&function_name,ZSTR_VAL(action_name));

      
      flag = call_user_class_method(return_value, controller_ce, &obj, function_name, 0, NULL);

      if(flag == FAILURE){


            zend_error_noreturn(E_CORE_ERROR,
                                "Couldn't find implementation for method %s%s%s",
                                controller_ce ? ZSTR_VAL(controller_ce->name) : "",
                                controller_ce ? "::" : "",
                                function_name);
        
      }

      //RETURN_ZVAL(get_arr, 1, 0);

}

const zend_function_entry route_functions[] = {


    //注册route类中的方法
    ZEND_ME(route, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
    ZEND_ME(route,run,NULL,ZEND_ACC_PUBLIC)


    PHP_FE_END    /* Must be the last line in route_functions[] */
};

PHP_MINIT_FUNCTION(route)
{

    //注册route类
    zend_class_entry ce;

    //define INIT_NS_CLASS_ENTRY(class_container, ns, class_name, functions)
    INIT_NS_CLASS_ENTRY(ce,"Dora" ,"Route", route_functions);
    route_ce = zend_register_internal_class(&ce TSRMLS_CC);

    //声明一个静态数据成员app_dir
    //zend_declare_property_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value, int access_type);
    zend_declare_property_string(route_ce, "app_dir", strlen("app_dir"), "",ZEND_ACC_PUBLIC|ZEND_ACC_STATIC TSRMLS_DC);
    

    return SUCCESS;
}
```
```c
5.编译安装
phpize
./configure --with-php-config=/usr/bin/php-config
make && make install

```
```c
6.php7创建类所有到的知识点


常见的变量操作宏

CG    -> Complier Global      编译时信息,包括函数表等(zend_globals_macros.h:32)
EG    -> Executor Global      执行时信息(zend_globals_macros.h:43)
PG    -> PHP Core Global      主要存储php.ini中的信息
SG    -> SAPI Global          SAPI信息

//===============================================================================
PHP7中的zval的类型:
/* regular data types */
define IS_UNDEF                    0
define IS_NULL                     1
define IS_FALSE                    2
define IS_TRUE                     3
define IS_LONG                     4
define IS_DOUBLE                   5
define IS_STRING                   6
define IS_ARRAY                    7
define IS_OBJECT                   8
define IS_RESOURCE                 9
define IS_REFERENCE                10

define IS_CONSTANT                 11
define IS_CONSTANT_AST             12

define _IS_BOOL                    13
define IS_CALLABLE                 14

define IS_INDIRECT                 15
define IS_PTR                      17

//===============================================================================
PHP7中获取的zval赋值:
- ZVAL_STRING(zv, str, 1);
+ ZVAL_STRING(zv, str);
- ZVAL_STRINGL(zv, str, len, 1);
+ ZVAL_STRINGL(zv, str, len);
- ZVAL_STRING(zv, str, 0);
+ ZVAL_STRING(zv, str);
+ efree(str);
- ZVAL_STRINGL(zv, str, len, 0);
+ ZVAL_STRINGL(zv, str, len);

//===============================================================================

PHP7中获取的zval的值和长度:
define Z_LVAL(zval)                (zval).value.lval
define Z_LVAL_P(zval_p)            Z_LVAL(*(zval_p))

define Z_DVAL(zval)                (zval).value.dval
define Z_DVAL_P(zval_p)            Z_DVAL(*(zval_p))

define Z_STR(zval)                 (zval).value.str
define Z_STR_P(zval_p)             Z_STR(*(zval_p))

define Z_STRVAL(zval)              ZSTR_VAL(Z_STR(zval))
define Z_STRVAL_P(zval_p)          Z_STRVAL(*(zval_p))

define Z_STRLEN(zval)              ZSTR_LEN(Z_STR(zval))
define Z_STRLEN_P(zval_p)          Z_STRLEN(*(zval_p))

define Z_STRHASH(zval)             ZSTR_HASH(Z_STR(zval))
define Z_STRHASH_P(zval_p)         Z_STRHASH(*(zval_p))

define Z_ARR(zval)                 (zval).value.arr
define Z_ARR_P(zval_p)             Z_ARR(*(zval_p))

define Z_ARRVAL(zval)              Z_ARR(zval)
define Z_ARRVAL_P(zval_p)          Z_ARRVAL(*(zval_p))

define Z_OBJ(zval)                 (zval).value.obj
define Z_OBJ_P(zval_p)             Z_OBJ(*(zval_p))

define Z_OBJ_HT(zval)              Z_OBJ(zval)->handlers
define Z_OBJ_HT_P(zval_p)          Z_OBJ_HT(*(zval_p))

define Z_OBJ_HANDLER(zval, hf)     Z_OBJ_HT((zval))->hf
define Z_OBJ_HANDLER_P(zv_p, hf)   Z_OBJ_HANDLER(*(zv_p), hf)

define Z_OBJ_HANDLE(zval)          (Z_OBJ((zval)))->handle
define Z_OBJ_HANDLE_P(zval_p)      Z_OBJ_HANDLE(*(zval_p))

define Z_OBJCE(zval)               (Z_OBJ(zval)->ce)
define Z_OBJCE_P(zval_p)           Z_OBJCE(*(zval_p))

define Z_OBJPROP(zval)             Z_OBJ_HT((zval))->get_properties(&(zval))
define Z_OBJPROP_P(zval_p)         Z_OBJPROP(*(zval_p))

define Z_OBJDEBUG(zval,tmp)        (Z_OBJ_HANDLER((zval),get_debug_info)?Z_OBJ_HANDLER((zval),get_debug_info)(&(zval),&tmp):(tmp=0,Z_OBJ_HANDLER((zval),get_properties)?Z_OBJPROP(zval):NULL))
define Z_OBJDEBUG_P(zval_p,tmp)    Z_OBJDEBUG(*(zval_p), tmp)

define Z_RES(zval)                 (zval).value.res
define Z_RES_P(zval_p)             Z_RES(*zval_p)

define Z_RES_HANDLE(zval)          Z_RES(zval)->handle
define Z_RES_HANDLE_P(zval_p)      Z_RES_HANDLE(*zval_p)

define Z_RES_TYPE(zval)            Z_RES(zval)->type
define Z_RES_TYPE_P(zval_p)        Z_RES_TYPE(*zval_p)

define Z_RES_VAL(zval)             Z_RES(zval)->ptr
define Z_RES_VAL_P(zval_p)         Z_RES_VAL(*zval_p)

define Z_REF(zval)                 (zval).value.ref
define Z_REF_P(zval_p)             Z_REF(*(zval_p))

define Z_REFVAL(zval)              &Z_REF(zval)->val
define Z_REFVAL_P(zval_p)          Z_REFVAL(*(zval_p))

define Z_AST(zval)                 (zval).value.ast
define Z_AST_P(zval_p)             Z_AST(*(zval_p))

define Z_ASTVAL(zval)              (zval).value.ast->ast
define Z_ASTVAL_P(zval_p)          Z_ASTVAL(*(zval_p))

define Z_INDIRECT(zval)            (zval).value.zv
define Z_INDIRECT_P(zval_p)        Z_INDIRECT(*(zval_p))

define Z_CE(zval)                  (zval).value.ce
define Z_CE_P(zval_p)              Z_CE(*(zval_p))

define Z_FUNC(zval)                (zval).value.func
define Z_FUNC_P(zval_p)            Z_FUNC(*(zval_p))

define Z_PTR(zval)                 (zval).value.ptr
define Z_PTR_P(zval_p)             Z_PTR(*(zval_p))

//===============================================================================

php7 用来判断类型和取值

void display_value(zval zv,zval *zv_p,zval **zv_pp)
{
    if( Z_TYPE(zv) == IS_NULL )
    {
        php_printf("类型是 IS_NULL!\n");
    }
     
    if( Z_TYPE_P(zv_p) == IS_LONG )
    {
        php_printf("类型是 IS_LONG,值是:%ld" , Z_LVAL_P(zv_p));
    }
     
    if(Z_TYPE_PP(zv_pp) == IS_DOUBLE )
    {
        php_printf("类型是 IS_DOUBLE,值是:%f" , Z_DVAL_PP(zv_pp) );
    }
}

//================================================================================

PHP7中的定义返回值的宏 Zend/zend_API.h
define RETVAL_BOOL(b)                                  ZVAL_BOOL(return_value, b)
define RETVAL_NULL()                                   ZVAL_NULL(return_value)
define RETVAL_LONG(l)                                  ZVAL_LONG(return_value, l)
define RETVAL_DOUBLE(d)                                ZVAL_DOUBLE(return_value, d)
define RETVAL_STR(s)                                   ZVAL_STR(return_value, s)
define RETVAL_INTERNED_STR(s)                  ZVAL_INTERNED_STR(return_value, s)
define RETVAL_NEW_STR(s)                               ZVAL_NEW_STR(return_value, s)
define RETVAL_STR_COPY(s)                              ZVAL_STR_COPY(return_value, s)
define RETVAL_STRING(s)                                ZVAL_STRING(return_value, s)
define RETVAL_STRINGL(s, l)                    ZVAL_STRINGL(return_value, s, l)
define RETVAL_EMPTY_STRING()                   ZVAL_EMPTY_STRING(return_value)
define RETVAL_RES(r)                                   ZVAL_RES(return_value, r)
define RETVAL_ARR(r)                                   ZVAL_ARR(return_value, r)
define RETVAL_OBJ(r)                                   ZVAL_OBJ(return_value, r)
define RETVAL_ZVAL(zv, copy, dtor)             ZVAL_ZVAL(return_value, zv, copy, dtor)
define RETVAL_FALSE                                    ZVAL_FALSE(return_value)
define RETVAL_TRUE                                     ZVAL_TRUE(return_value)


define RETURN_BOOL(b)                                  { RETVAL_BOOL(b); return; }
define RETURN_NULL()                                   { RETVAL_NULL(); return;}
define RETURN_LONG(l)                                  { RETVAL_LONG(l); return; }
define RETURN_DOUBLE(d)                                { RETVAL_DOUBLE(d); return; }
define RETURN_STR(s)                                   { RETVAL_STR(s); return; }
define RETURN_INTERNED_STR(s)                  { RETVAL_INTERNED_STR(s); return; }
define RETURN_NEW_STR(s)                               { RETVAL_NEW_STR(s); return; }
define RETURN_STR_COPY(s)                              { RETVAL_STR_COPY(s); return; }
define RETURN_STRING(s)                                { RETVAL_STRING(s); return; }
define RETURN_STRINGL(s, l)                    { RETVAL_STRINGL(s, l); return; }
define RETURN_EMPTY_STRING()                   { RETVAL_EMPTY_STRING(); return; }
define RETURN_RES(r)                                   { RETVAL_RES(r); return; }
define RETURN_ARR(r)                                   { RETVAL_ARR(r); return; }
define RETURN_OBJ(r)                                   { RETVAL_OBJ(r); return; }
define RETURN_ZVAL(zv, copy, dtor)             { RETVAL_ZVAL(zv, copy, dtor); return; }
define RETURN_FALSE                                    { RETVAL_FALSE; return; }
define RETURN_TRUE                                     { RETVAL_TRUE; return; }
array_init(return_value);//初始化return_value成数组,此操作完后就可以返回一个空的数组
object_init(return_value);//初始化return_value成Object,此操作完成后返回一个空的对像




//===============================================================================
grep "define ZEND_ACC"  Zend/*.h
内核中提供了定义类以方法的修饰词 Zend/zend_compile.h声明定义

define ZEND_ACC_STATIC         0x01
define ZEND_ACC_ABSTRACT       0x02
define ZEND_ACC_FINAL          0x04
define ZEND_ACC_IMPLEMENTED_ABSTRACT       0x08
define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS    0x10
define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS    0x20
define ZEND_ACC_INTERFACE                  0x40
define ZEND_ACC_TRAIT                      0x80
define ZEND_ACC_ANON_CLASS                 0x100
define ZEND_ACC_ANON_BOUND                 0x200

define ZEND_ACC_PUBLIC     0x100
define ZEND_ACC_PROTECTED  0x200
define ZEND_ACC_PRIVATE    0x400
define ZEND_ACC_PPP_MASK  (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)
define ZEND_ACC_CHANGED    0x800
define ZEND_ACC_IMPLICIT_PUBLIC    0x1000
define ZEND_ACC_CTOR       0x2000
define ZEND_ACC_DTOR       0x4000
define ZEND_ACC_CLONE      0x8000


//===============================================================================
1. grep ZEND_ACC  Zend/*.h
内核中提供了定义类属性的宏  Zend/zend_API.h声明定义

ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment);
ZEND_API int zend_declare_property(zend_class_entry *ce, const char *name, size_t name_length, zval *property, int access_type);
ZEND_API int zend_declare_property_null(zend_class_entry *ce, const char *name, size_t name_length, int access_type);
ZEND_API int zend_declare_property_bool(zend_class_entry *ce, const char *name, size_t name_length, zend_long value, int access_type);
ZEND_API int zend_declare_property_long(zend_class_entry *ce, const char *name, size_t name_length, zend_long value, int access_type);
ZEND_API int zend_declare_property_double(zend_class_entry *ce, const char *name, size_t name_length, double value, int access_type);
ZEND_API int zend_declare_property_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value, int access_type);
ZEND_API int zend_declare_property_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_len, int access_type);


更新类中的数据成员 Zend/zend_API.h声明定义

ZEND_API void zend_update_property_ex(zend_class_entry *scope, zval *object, zend_string *name, zval *value);
ZEND_API void zend_update_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zval *value);
ZEND_API void zend_update_property_null(zend_class_entry *scope, zval *object, const char *name, size_t name_length);
ZEND_API void zend_update_property_bool(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_long value);
ZEND_API void zend_update_property_long(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_long value);
ZEND_API void zend_update_property_double(zend_class_entry *scope, zval *object, const char *name, size_t name_length, double value);
ZEND_API void zend_update_property_str(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_string *value);
ZEND_API void zend_update_property_string(zend_class_entry *scope, zval *object, const char *name, size_t name_length, const char *value);
ZEND_API void zend_update_property_stringl(zend_class_entry *scope, zval *object, const char *name, size_t name_length, const char *value, size_t value_length);

grep "zend_read_"  ../../Zend/*.h
读取类中的数据成员 在Zend/zend_API.h声明定义

ZEND_API zval *zend_read_property(zend_class_entry *scope, zval *object, const char *name, size_t name_length, zend_bool silent, zval *rv);



//===============================================================================
2. grep "zend_declare_class_constant"  Zend/*.h
创建类中的常量的方法在Zend/zend_API.h声明定义

ZEND_API int zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value);
ZEND_API int zend_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length);
ZEND_API int zend_declare_class_constant_long(zend_class_entry *ce, const char *name, size_t name_length, zend_long value);
ZEND_API int zend_declare_class_constant_bool(zend_class_entry *ce, const char *name, size_t name_length, zend_bool value);
ZEND_API int zend_declare_class_constant_double(zend_class_entry *ce, const char *name, size_t name_length, double value);
ZEND_API int zend_declare_class_constant_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_length);
ZEND_API int zend_declare_class_constant_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value);

更新类中的常量数据成员 Zend/zend_API.h声明定义
ZEND_API int zend_update_class_constants(zend_class_entry *class_type);



//=================================================================================
3. grep "zend_update_static_"  ../../Zend/*.h
更新类中的静态数据成员 在Zend/zend_API.

ZEND_API int zend_update_static_property(zend_class_entry *scope, const char *name, size_t name_length, zval *value);
ZEND_API int zend_update_static_property_null(zend_class_entry *scope, const char *name, size_t name_length);
ZEND_API int zend_update_static_property_bool(zend_class_entry *scope, const char *name, size_t name_length, zend_long value);
ZEND_API int zend_update_static_property_long(zend_class_entry *scope, const char *name, size_t name_length, zend_long value);
ZEND_API int zend_update_static_property_double(zend_class_entry *scope, const char *name, size_t name_length, double value);
ZEND_API int zend_update_static_property_string(zend_class_entry *scope, const char *name, size_t name_length, const char *value);
ZEND_API int zend_update_static_property_stringl(zend_class_entry *scope, const char *name, size_t name_length, const char *value, size_t value_length);


grep "zend_read_"  ../../Zend/*.h
读取类中的数据成员 在Zend/zend_API.h声明定义

ZEND_API zval *zend_read_static_property(zend_class_entry *scope, const char *name, size_t name_length, zend_bool silent);


```

- 请尊重本人劳动成功,可以随意转载但保留以下信息
- 作者:岁月经年
- 时间:2016年04月

원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿