> php教程 > php手册 > ThinkPHP- 3.1,thinkphp-3.1

ThinkPHP- 3.1,thinkphp-3.1

WBOY
풀어 주다: 2016-06-13 08:55:35
원래의
3197명이 탐색했습니다.

ThinkPHP- 3.1,thinkphp-3.1

基础:

1. 基础概念

LAMP

LAMP是基于Linux,Apache,MySQL和PHP的开放资源网络开发平台。这个术语来自欧洲,在那里这些程序常用来作为一种标准开发环境。名字来源于每个程序的第一个字母。每个程序在所有权里都符合开放源代码标准:Linux是开放系统;Apache是最通用的网络服务器;MySQL是带有基于网络管理附加工具的关系数据库;PHP是流行的对象脚本语言,它包含了多数其它语言的优秀特征来使得它的网络开发更加有效。开发者在Windows操作系统下使用这些Linux环境里的工具称为使用WAMP。
虽然这些开放源代码程序本身并不是专门设计成同另外几个程序一起工作的,但由于它们都是影响较大的开源软件,拥有很多共同特点,这就导致了这些组件经常在一起使用。在过去的几年里,这些组件的兼容性不断完善,在一起的应用情形变得更加普遍。并且它们为了改善不同组件之间的协作,已经创建了某些扩展功能。目前,几乎在所有的Linux发布版中都默认包含了这些产品。Linux操作系统、Apache服务器、MySQL数据库和Perl、PHP或者 Python语言,这些产品共同组成了一个强大的Web应用程序平台。
随着开源潮流的蓬勃发展,开放源代码的LAMP已经与J2EE和.Net商业软件形成三足鼎立之势,并且该软件开发的项目在软件方面的投资成本较低,因此受到整个IT界的关注。从网站的流量上来说,70%以上的访问流量是LAMP来提供的,LAMP是最强大的网站解决方案.

OOP

面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息。OOP 主要有以下的概念和组件: 
组件 - 数据和功能一起在运行着的计算机程序中形成的单元,组件在 OOP 计算机程序中是模块和结构化的基础。 
抽象性 - 程序有能力忽略正在处理中信息的某些方面,即对信息主要方面关注的能力。 
封装 - 也叫做信息封装:确保组件不会以不可预期的方式改变其它组件的内部状态;只有在那些提供了内部状态改变方法的组件中,才可以访问其内部状态。每类组件都提供了一个与其它组件联系的接口,并规定了其它组件进行调用的方法。 
多态性 - 组件的引用和类集会涉及到其它许多不同类型的组件,而且引用组件所产生的结果得依据实际调用的类型。 
继承性 - 允许在现存的组件基础上创建子类组件,这统一并增强了多态性和封装性。典型地来说就是用类来对组件进行分组,而且还可以定义新类为现存的类的扩展,这样就可以将类组织成树形或网状结构,这体现了动作的通用性。 
由于抽象性、封装性、重用性以及便于使用等方面的原因,以组件为基础的编程在脚本语言中已经变得特别流行。

MVC

MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型(M)、视图(V)、控制器(C),它们各自处理自己的任务。 
视图 :视图是用户看到并与之交互的界面。对老式的Web应用程序来说,视图就是由HTML元素组成的界面,在新式的Web应用程序中,HTML依旧在视图中扮演着重要的角色,但一些新的技术已层出不穷,它们包括Adobe Flash和象XHTML,XML/XSL,WML等一些标识语言和Web services。如何处理应用程序的界面变得越来越有挑战性。MVC一个大的好处是它能为你的应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,不管这些数据是联机存储的还是一个雇员列表,作为视图来讲,它只是作为一种输出数据并允许用户操纵的方式。 
模型 :模型表示企业数据和业务规则。在MVC的三个部件中,模型拥有最多的处理任务。例如它可能用象EJBs和ColdFusion Components这样的构件对象来处理数据库。被模型返回的数据是中立的,就是说模型与数据格式无关,这样一个模型能为多个视图提供数据。由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。 
控制器 :控制器接受用户的输入并调用模型和视图去完成用户的需求。所以当单击Web页面中的超链接和发送HTML表单时,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后确定用哪个视图来显示模型处理返回的数据。 
现在我们总结MVC的处理过程,首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理,然后模型用业务逻辑来处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。

ORM

对象-关系映射(Object/Relation Mapping,简称ORM),是随着面向对象的软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。

AOP

AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

CURD

CURD是一个数据库技术中的缩写词,一般的项目开发的各种参数的基本功能都是CURD。它代表创建(Create)、更新(Update)、读取(Read)和删除(Delete)操作。CURD 定义了用于处理数据的基本原子操作。之所以将CURD 提升到一个技术难题的高度是因为完成一个涉及在多个数据库系统中进行CURD操作的汇总相关的活动,其性能可能会随数据关系的变化而有非常大的差异。
CURD在具体的应用中并非一定使用create、update 、read和delete字样的方法,但是他们完成的功能是一致的。例如,ThinkPHP就是使用add、save、select和delete方法表示模型的CURD操作。

ActiveRecord

Active Record(中文名:活动记录)是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。Active Record 和 Row Gateway (行记录入口)十分相似,但前者是领域模型,后者是一种数据源模式。关系型数据库往往通过外键来表述实体关系,Active Record 在数据源层面上也将这种关系映射为对象的关联和聚集。   Active Record 适合非常简单的领域需求,尤其在领域模型和数据库模型十分相似的情况下。如果遇到更加复杂的领域模型结构(例如用到继承、策略的领域模型),往往需要使用分离数据源的领域模型,结合 Data Mapper (数据映射器)使用。
Active Record 驱动框架一般兼有 ORM 框架的功能,但 Active Record 不是简单的 ORM,正如和 Row Gateway 的区别。由Rails最早提出,遵循标准的ORM模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。

单一入口

单一入口通常是指一个项目或者应用具有一个统一(但并不一定是唯一)的入口文件,也就是说项目的所有功能操作都是通过这个入口文件进行的,并且往往入口文件是第一步被执行的。
单一入口的好处是项目整体比较规范,因为同一个入口,往往其不同操作之间具有相同的规则。另外一个方面就是单一入口带来的好处是控制较为灵活,因为拦截方便了,类似如一些权限控制、用户登录方面的判断和操作可以统一处理了。
或者有些人会担心所有网站都通过一个入口文件进行访问,是否会造成太大的压力,其实这是杞人忧天的想法。

2. 目录结构

目录/文件 说明
ThinkPHP.php 框架入口文件
Common 框架公共文件目录
Conf 框架配置文件目录
Lang 框架系统语言目录
Lib 系统核心基类库目录
Tpl 系统模板目录
Extend 框架扩展目录(关于扩展目录的详细信息请参考后面的扩展章节)
注意:如果你下载的是核心版本,有可能Extend目录是空的,因为ThinkPHP本身不依赖任何扩展。

3. 命名规范

使用ThinkPHP开发的过程中应该尽量遵循下列命名规范:
  • 类文件都是以.class.php为后缀(这里是指的ThinkPHP内部使用的类库文件,不代表外部加载的类库文件),使用驼峰法命名,并且首字母大写,例如DbMysql.class.php;
  • 确保文件的命名和调用大小写一致,是由于在类Unix系统上面,对大小写是敏感的(而ThinkPHP在调试模式下面,即使在Windows平台也会严格检查大小写);
  • 类名和文件名一致(包括上面说的大小写一致),例如 UserAction类的文件命名是UserAction.class.php, InfoModel类的文件名是InfoModel.class.php, 并且不同的类库的类命名有一定的规范;
  • 函数、配置文件等其他类库文件之外的一般是以.php为后缀(第三方引入的不做要求);
  • 函数的命名使用小写字母和下划线的方式,例如 get_client_ip;
  • 方法的命名使用驼峰法,并且首字母小写或者使用下划线“_”,例如 getUserName,_parseType,通常下划线开头的方法属于私有方法;
  • 属性的命名使用驼峰法,并且首字母小写或者使用下划线“_”,例如 tableName、_instance,通常下划线开头的属性属于私有属性;
  • 以双下划线“__”打头的函数或方法作为魔法方法,例如 __call 和 __autoload;
  • 常量以大写字母和下划线命名,例如 HAS_ONE和 MANY_TO_MANY;
  • 配置参数以大写字母和下划线命名,例如HTML_CACHE_ON;
  • 语言变量以大写字母和下划线命名,例如MY_LANG,以下划线打头的语言变量通常用于系统语言变量,例如 _CLASS_NOT_EXIST_;
  • 对变量的命名没有强制的规范,可以根据团队规范来进行;
  • ThinkPHP的模板文件默认是以.html 为后缀(可以通过配置修改);
  • 数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如 think_user 表和 user_name字段,类似 _username 这样的数据表字段可能会被过滤。
在ThinkPHP里面,有一个函数命名的特例,就是单字母大写函数,这类函数通常是某些操作的快捷定义,或者有特殊的作用。例如,ADSL方法等等。 另外有一点非常关键,ThinkPHP默认全部使用UTF-8编码,所以请确保你的程序文件采用UTF-8编码格式保存,并且去掉BOM信息头(去掉BOM头信息有很多方式,不同的编辑器都有设置方法,也可以用工具进行统一检测和处理),否则可能导致很多意想不到的问题。

4. CBD架构

ThinkPHP3.0版本引入了全新的CBD(核心Core+行为Behavior+驱动Driver)架构模式,因为从底层开始,框架就采用核心+行为+驱动的架构体系,核心保留了最关键的部分,并在重要位置设置了标签用以标记,其他功能都采用行为扩展和驱动的方式组合,开发人员可以根据自己的需要,对某个标签位置进行行为扩展或者替换,就可以方便的定制框架底层,也可以在应用层添加自己的标签位置和添加应用行。而标签位置类似于AOP概念中的“切面”,行为都是围绕这个“切面”来进行编程,如果把系统内置的核心扩展看成是一种标准模式的话,那么用户可以把这一切的行为定制打包成一个新的模式,所以在ThinkPHP里面,称之为模式扩展,事实上,模式扩展不仅仅可以替换和增加行为,还可以对底层的MVC进行替换和修改,以达到量身定制的目的。利用这一新的特性,开发人员可以方便地通过模式扩展为自己量身定制一套属于自己或者企业的开发框架,新版的模式扩展是框架扩展的集大成者,开创了新的里程碑,这正是新版的真正魅力所在。

5. 开发流程

使用ThinkPHP创建应用的一般开发流程是:
  • 系统设计、创建数据库和数据表;(可选)
  • 项目命名并创建项目入口文件,开启调试模式;
  • 完成项目配置;
  • 创建项目函数库;(可选)
  • 开发项目需要的扩展(模式、驱动、标签库等);(可选)
  • 创建控制器类;
  • 创建模型类;(可选)
  • 创建模板文件;
  • 运行和调试、分析日志;
  • 开发和设置缓存功能;(可选)
  • 添加路由支持;(可选)
  • 安全检查;(可选 )
  • 部署到生产环境。

6. 入口文件

ThinkPHP采用单一入口模式进行项目部署和访问,无论完成什么功能,一个项目都有一个统一(但不一定是唯一)的入口。应该说,所有项目都是从入口文件开始的,并且所有的项目的入口文件是类似的,入口文件中主要包括:
  • 定义框架路径、项目路径和项目名称(可选)
  • 定义调试模式和运行模式的相关常量(可选)
  • 载入框架入口文件(必须)

7. 项目目录

生成的项目目录结构和系统目录类似,包括:
目录 说明
Common 项目公共文件目录,一般放置项目的公共函数
Conf 项目配置目录,项目所有的配置文件都放在这里
Lang 项目语言包目录(可选 如果不需要多语言支持 可删除)
Lib 项目类库目录,通常包括Action和Model子目录
Tpl 项目模板目录,支持模板主题
Runtime 项目运行时目录,包括Cache(模板缓存)、Temp(数据缓存)、Data(数据目录)和Logs(日志文件)子目录,如果存在分组的话,则首先是分组目录。
如果需要把index.php 移动到App目录的外面,只需要在入口文件中增加项目名称和项目路径定义。
  1.     //定义项目名称
  2.     define('APP_NAME', 'App');
  3.     //定义项目路径
  4.     define('APP_PATH', './App/');
  5.     //加载框架入文件
  6.     require './App/ThinkPHP/ThinkPHP.php';
APP_NAME 是指项目名称,注意APP_NAME 不要随意设置,通常是项目的目录名称,如果你的项目是直接部署在Web根目录下面的话,那么需要设置APP_NAME 为空。
APP_PATH 是指项目路径(必须以“/”结束),项目路径是指项目的Common、Lib目录所在的位置,而不是项目入口文件所在的位置。
注意:在类Unix或者Linux环境下面Runtime目录需要可写权限。

8. 部署目录

目录/文件 说明
ThinkPHP 系统目录(下面的目录结构同上面的系统目录)
Public 网站公共资源目录(存放网站的Css、Js和图片等资源)
Uploads 网站上传目录(用户上传的统一目录)
Home 项目目录(下面的目录结构同上面的应用目录)
Admin 后台管理项目目录
…… 更多的项目目录
index.php 项目Home的入口文件
admin.php 项目Admin的入口文件
…… 更多的项目入口文件
项目的模板文件还是放到项目的Tpl目录下面,只是将外部调用的资源文件, 包括图片 JS 和CSS统一放到网站的公共目录Public下面,分Images、Js和Css子目录存放,如果有可能的话,甚至也可以把这些资源文件单独放一个外部的服务器远程调用,并进行优化。

事实上,系统目录和项目目录可以放到非WEB访问目录下面,网站目录下面只需要放置Public公共目录和入口文件,从而提高网站的安全性。

 

如果希望自己设置目录,可以在入口文件里面更改RUNTIME_PATH常量进行更改,例如:
  1. define('RUNTIME_PATH','./App/temp/');
注意RUNTIME_PATH目录必须设置为可写权限。
除了自定义编译缓存目录之外,还支持自定义编译缓存文件名,例如:
  1. define('RUNTIME_FILE','./App/temp/runtime_cache.php');
ThinkPHP框架中所有配置文件的定义格式均采用返回PHP数组的方式,格式为:
  1. //项目配置文件
  2.  return array(
  3.     'DEFAULT_MODULE'     => 'Index', //默认模块
  4.     'URL_MODEL'          => '2', //URL模式
  5.     'SESSION_AUTO_START' => true, //是否开启session
  6.     //更多配置参数
  7.     //...
  8.  );
配置参数不区分大小写(因为无论大小写定义都会转换成小写)
还可以在配置文件中可以使用二维数组来配置更多的信息,例如:
  1. //项目配置文件
  2.  return array(
  3.     'DEFAULT_MODULE'     => 'Index', //默认模块
  4.     'URL_MODEL'          => '2', //URL模式
  5.     'SESSION_AUTO_START' => true, //是否开启session
  6.     'USER_CONFIG'        => array(
  7.         'USER_AUTH' => true,
  8.         'USER_TYPE' => 2,
  9.     ),
  10.     //更多配置参数
  11.     //...
  12.  );
需要注意的是,二级参数配置区分大小写,也就说读取确保和定义一致。

9. 惯例配置和项目配置,调试配置

惯例重于配置是系统遵循的一个重要思想,系统内置有一个惯例配置文件(位于系统目录下面的Conf\convention.php),按照大多数的使用对常用参数进行了默认配置。 项目配置文件是最常用的配置文件,项目配置文件位于项目的配置文件目录Conf下面,文件名是config.php。
在项目配置文件里面除了添加内置的参数配置外,还可以额外添加项目需要的配置参数。 如果没有配置应用状态,系统默认则默认为debug状态,也就是说默认的配置参数是:
  1. 'APP_STATUS' => 'debug', //应用调试模式状态
debug.php配置文件只需要配置和项目配置文件以及系统调试配置文件不同的参数或者新增的参数。
如果想在调试模式下面增加应用状态,例如测试状态,则可以在项目配置文件中改变设置如下:
  1. 'APP_STATUS' => 'test', //应用调试模式状态
由于调试模式没有任何缓存,因此涉及到较多的文件IO操作和模板实时编译,所以在开启调试模式的情况下,性能会有一定的下降,但不会影响部署模式的性能。
注意:一旦关闭调试模式,项目的调试配置文件即刻失效。

10. 分组配置和读取配置,动态配置

如果启用了模块分组,则可以在对每个分组单独定义配置文件,分组配置文件位于:
项目配置目录/分组名称/config.php
可以通过如下配置启用分组:
  1. 'APP_GROUP_LIST' => 'Home,Admin', //项目分组设定
  2.  'DEFAULT_GROUP'  => 'Home', //默认分组
现在定义了Home和Admin两个分组,则我们可以定义分组配置文件如下:
Conf/Home/config.php
Conf/Admin/config.php
每个分组的配置文件仅在当前分组有效,分组配置的定义格式和项目配置是一样的。
注意:分组名称区分大小写,必须和定义的分组名一致。 定义了配置文件之后,可以使用系统提供的C方法(如果觉得比较奇怪的话,可以借助Config单词来帮助记忆)来读取已有的配置:
  1. C('参数名称')//获取已经设置的参数值
例如,C('APP_STATUS') 可以读取到系统的调试模式的设置值,如果APP_STATUS尚未存在设置,则返回NULL。 C方法同样可以用于读取二维配置:
  1. C('USER_CONFIG.USER_TYPE')//获取用户配置中的用户类型设置
因为配置参数是全局有效的,因此C方法可以在任何地方读取任何配置,哪怕某个设置参数已经生效过期了。 在具体的Action方法里面,我们仍然可以对某些参数进行动态配置,主要是指那些还没有被使用的参数。 
设置新的值:
  1. C('参数名称','新的参数值');
例如,我们需要动态改变数据缓存的有效期的话,可以使用
  1. C('DATA_CACHE_TIME','60');
也可以支持二维数组的读取和设置,使用点语法进行操作,如下:
获取已经设置的参数值:
  1. C('USER_CONFIG.USER_TYPE');
设置新的值:
  1. C('USER_CONFIG.USER_TYPE','1');
3.1版本开始,C函数支持配置保存功能,仅对批量设置有效,使用方法:
  1. C($array,'name');
其中array是一个数组变量,会把批量设置后的配置参数列表保存到name标识的缓存数据中

获取缓存的设置列表数据 可以用
  1. C('','name'); //或者C(null,'name');
会读取name标识的缓存配置数据到当前配置数据(合并)。

11. 扩展配置

项目配置文件在部署模式的时候会纳入编译缓存,也就是说编译后再修改项目配置文件就不会立刻生效,需要删除编译缓存后才能生效。扩展配置文件则不受此限制影响,即使在部署模式下面,修改配置后可以实时生效,并且配置格式和项目配置一样。
设置扩展配置的方式如下(多个文件用逗号分隔):
  1. 'LOAD_EXT_CONFIG' => 'user,db', // 加载扩展配置文件
项目设置了加载扩展配置文件user.php 和db.php分别用于用户配置和数据库配置,那么会自动加载项目配置目录下面的配置文件Conf/user.php和Conf/db.php。 如果希望采用二级配置方式,可以设置如下:
  1. 'LOAD_EXT_CONFIG' => array(
  2.     'USER' => 'user', //用户配置
  3.     'DB'   => 'db', //数据库配置
  4.  ), //加载扩展配置文件
同样的user.php 配置文件内容,但最终获取用户参数的方式就变成了:
  1. C('USER.USER_AUTH_ID');
这种方式可以避免大项目情况中的参数冲突问题。 下面的一些配置文件已经被系统使用,请不要作为自定义的扩展配置重新定义:
文件名 说明
config.php 项目配置文件
tags.php 项目行为配置文件
alias.php 项目别名定义文件
debug.php 项目调试模式配置文件(以及项目设置的APP_STATUS对应的配置文件)
core.php 项目追加的核心编译列表文件(不会覆盖核心编译列表)

12. 函数库

ThinkPHP中的函数库可以分为系统函数库和项目函数库。

系统函数库

库系统函数库位于系统的Common目录下面,有三个文件:
common.php是全局必须加载的基础函数库,在任何时候都可以直接调用;
functions.php是框架标准模式的公共函数库,其他模式可以替换加载自己的公共函数库或者对公共函数库中的函数进行重新定义;
runtime.php是框架运行时文件,仅在调试模式或者编译过程才会被加载,因此其中的方法在项目中不能直接调用;

项目函数库

库项目函数库通常位于项目的Common目录下面,文件名为common.php,该文件会在执行过程中自动加载,并且合并到项目编译统一缓存,如果使用了分组部署方式,并且该目录下存在"分组名称/function.php"文件,也会根据当前分组执行时对应进行自动加载,因此项目函数库的所有函数也都可以无需手动载入而直接使用。
如果项目配置中使用了动态函数加载配置的话,项目Common目录下面可能会存在更多的函数文件,动态加载的函数文件不会纳入编译缓存。
在特殊的情况下,模式可以改变自动加载的项目函数库的位置或者名称。

扩展函数库

库我们可以在项目公共目录下面定义扩展函数库,方便需要的时候加载和调用。扩展函数库的函数定义规范和项目函数库一致,只是函数库文件名可以随意命名,一般来说,扩展函数库并不会自动加载,除非你设置了动态载入。

函数加载

系统函数库和项目函数库中的函数无需加载就可以直接调用,对于项目的扩展函数库,可以采用下面两种方式调用:
动态载入
我们可以在项目配置文件中定义LOAD_EXT_FILE参数,例如:
  1. "LOAD_EXT_FILE"=>"user,db"
通过上面的设置,就会执行过程中自动载入项目公共目录下面的扩展函数库文件user.php和db.php,这样就可以直接在项目中调用扩展函数库user.php和db.php中的函数了,而且扩展函数库的函数修改是实时生效的。

手动载入
如果你的函数只是个别模块偶尔使用,则不需要采用自动加载方式,可以在需要调用的时候采用load方法手动载入,方式如下:
  1. load("@.user")
@.user表示加载当前项目的user函数文件,这样就可以直接user.php扩展函数库中的函数了。

13. 类库

ThinkPHP的类库包括基类库和应用类库,系统的类库命名规则如下:
类库 规则 示例
控制器类 模块名+Action 例如 UserAction、InfoAction
模型类 模型名+Model 例如 UserModel、InfoModel
行为类 行为名+Behavior 例如CheckRouteBehavior
Widget类 Widget名+Widget 例如BlogInfoWidget
驱动类 引擎名+驱动名 例如DbMysql表示mysql数据库驱动、CacheFile表示文件缓存驱动

基类库

基类库是指符合ThinkPHP类库规范的系统类库,包括ThinkPHP的核心基类库和扩展基类库。核心基类库目录位于系统的Lib目录,核心基类库也就是Think类库,扩展基类库位于Extend/Library目录,可以扩展ORG 、Com扩展类库。核心基类库的作用是完成框架的通用性开发而必须的基础类和内置支持类等,包含有:
目录 调用路径 说明
Lib/Core Think.Core 核心类库包
Lib/Behavior Think.Behavior 内置行为类库包
Lib/Driver Think.Driver 内置驱动类库包
Lib/Template Think.Template 内置模板引擎类库包
核心类库包下面包含下面核心类库:
类名 说明
Action 系统基础控制器类
App 系统应用类
Behavior 系统行为基础类
Cache 系统缓存类
Db 系统抽象数据库类
Dispatcher URL调度类
Log 系统日志类
Model 系统基础模型类
Think 系统入口和静态类
ThinkException 系统基础异常类
View 视图类
Widget 系统Widget基础类

应用类库

应用类库是指项目中自己定义或者使用的类库,这些类库也是遵循ThinkPHP的命名规范。应用类库目录位于项目目录下面的Lib目录。应用类库的范围很广,包括Action类库、Model类库或者其他的工具类库,通常包括:
目录 调用路径 说明
Lib/Action @.Action或自动加载 控制器类库包
Lib/Model @.Model或自动加载 模型类库包
Lib/Behavior 用B方法调用或自动加载 应用行为类库包
Lib/Widget 用W方法在模板中调用 应用Widget类库包
项目根据自己的需要可以在项目类库目录下面添加自己的类库包,例如Lib/Common、Lib/Tool等。

类库导入

一、Import显式导入

ThinkPHP模拟了Java的类库导入机制,统一采用import方法进行类文件的加载。import方法是ThinkPHP内建的类库导入方法,提供了方便和灵活的文件导入机制,完全可以替代PHP的require和include方法。例如:
  1. import("Think.Util.Session");
  2.  import("App.Model.UserModel");
import方法具有缓存和检测机制,相同的文件不会重复导入,如果导入了不同的位置下面的同名类库文件,系统也不会再次导入 注意:在Unix或者Linux主机下面是区别大小写的,所以在使用import方法的时候要注意目录名和类库名称的大小写,否则会导入失败。对于import方法,系统会自动识别导入类库文件的位置,ThinkPHP的约定是Think、ORG、Com包的导入作为基类库导入,否则就认为是项目应用类库导入。
  1. import("Think.Util.Session");
  2.  import("ORG.Util.Page");
上面两个方法分别导入了Think基类库的Util/Session.class.php文件和ORG扩展类库包的Util/Page.class.php文件。
要导入项目的应用类库文件也很简单,使用下面的方式就可以了,和导入基类库的方式看起来差不多:
  1. import("MyApp.Action.UserAction");
  2.  import("MyApp.Model.InfoModel");
上面的方式分别表示导入MyApp项目下面的Lib/Action/UserAction.class.php和Lib/Model/InfoModel.class.php类文件。 通常我们都是在当前项目里面导入所需的类库文件,所以,我们可以使用下面的方式来简化代码
  1. import("@.Action.UserAction");
  2.  import("@.Model.InfoModel");

二,别名导入

除了命名空间的导入方式外,import方法还可以支持别名导入,要使用别名导入,首先要定义别名,我们可以在项目配置目录下面增加alias.php 用以定义项目中需要用到的类库别名,例如:
  1. return array(
  2.     'rbac' =>LIB_PATH.'Common/Rbac.class.php',
  3.     'page' =>LIB_PATH.'Common/Page.class.php',
  4.  );
那么,现在就可以直接使用:
  1. import("rbac");
  2.  import("page");
导入Rbac和Page类,别名导入方式禁止使用import方法的第二和第三个参数,别名导入方式的效率比命名空间导入方式要高效,缺点是需要预先定义相关别名。

导入第三方类库

第三方类库统一放置在系统扩展目录下的Vendor 目录,并且使用vendor 方法导入,其参数和 import 方法是 一致的,只是默认的值有针对变化。 例如,我们把 Zend 的 Filter\Dir.php 放到 Vendor 目录下面,这个时候 Dir 文件的路径就是  Vendor\Zend\Filter\Dir.php,我们使用vendor 方法导入只需要使用:
  1. Vendor('Zend.Filter.Dir');
就可以导入Dir类库了。
Vendor方法也可以支持和import方法一样的基础路径和文件名后缀参数,例如:
  1. Vendor('Zend.Filter.Dir',dirname(__FILE__),'.class.php');

自动加载

在大多数情况下,我们无需手动导入类库,而是通过配置采用自动加载机制即可,自动加载机制是真正的按需加载,可以很大程度的提高性能。自动加载有三种情况,按照加载优先级从高到低分别是:别名自动加载、系统规则自动加载和自定义路径自动加载。

一、别名自动加载

在前面我们提到了别名的定义方式,并且采用了import方法进行别名导入,其实所有定义别名的类库都无需再手动加载,系统会按需自动加载。

二、 系统规则自动加载

果你没有定义别名的话,系统会首先按照内置的规则来判断加载,系统规则仅针对行为类、模型类和控制器类,搜索规则如下:
类名 规则 说明
行为类 规则1 搜索系统类库目录下面的Behavior目录
规则2 搜索系统扩展目录下面的Behavior目录
规则3 搜索应用类库目录下面的Behavior目录
规则4 如果启用了模式扩展,则搜索模式扩展目录下面的Behavior目录
模型类 规则1 如果启用分组,则搜索应用类库目录的Model/当前分组 目录
规则2 搜索应用类库下面的Model目录
规则3 搜索系统扩展目录下面的Model目录
控制器类 规则1 如果启用分组,则搜索应用类库目录的Action/当前分组 目录
规则2 搜索项目类库目录下面的Action目录
规则3 搜索系统扩展目录下面的Action目录
注意:搜索的优先顺序从上至下 ,一旦找到则返回,后面规则不再检测。如果全部规则检测完成后依然没有找到类库,则开始进行第三个自定义路径自动加载检测。

三、 自定义路径自动加载

当你的类库比较集中在某个目录下面,而且不想定义太多的别名导入的话,可以使用自定义路径自动加载方式,这种方式需要在项目配置文件中添加自动加载的搜索路径,例如:
  1. 'APP_AUTOLOAD_PATH' =>'@.Common,@.Tool',
表示,在当前项目类库目录下面的Common和Tool目录下面的类库可以自动加载。多个搜索路径之间用逗号分割,并且注意定义的顺序也就是自动搜索的顺序。
注意:自动搜索路径定义只能采用命名空间方式,也就是说这种方式只能自动加载项目类库目录和基类库目录下面的类库文件。 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

控制器:

1. URL模式

传统方式的文件入口访问会变成由URL的参数来统一解析和调度。 ThinkPHP支持四种URL模式,可以通过设置URL_MODEL参数来定义,包括普通模式、PATHINFO、REWRITE和兼容模式。 一、普通模式:设置URL_MODEL 为0
采用传统的URL参数模式
  1. http://serverName/appName/?m=module&a=action&id=1
二、PATHINFO模式(默认模式):设置URL_MODEL 为1
默认情况使用PATHINFO模式,ThinkPHP内置强大的PATHINFO支持,提供灵活和友好URL支持。PATHINFO模式自动识别模块和操作,例如
  1. http://serverName/appName/module/action/id/1/或者
  1. http://serverName/appName/module,action,id,1/
三、REWRITE模式: 设置URL_MODEL 为2
该URL模式和PATHINFO模式功能一样,除了可以不需要在URL里面写入口文件,和可以定义.htaccess 文件外。在开启了Apache的URL_REWRITE模块后,就可以启用REWRITE模式了,具体参考下面的URL重写部分。 四、兼容模式: 设置URL_MODEL 为3
兼容模式是普通模式和PATHINFO模式的结合,并且可以让应用在需要的时候直接切换到PATHINFO模式而不需要更改模板和程序,还可以和URL_WRITE模式整合。兼容模式URL可以支持任何的运行环境。 兼容模式的效果是:
  1. http://serverName/appName/?s=/module/action/id/1/
并且也可以支持参数分割符号的定义,例如在URL_PATHINFO_DEPR为~的情况下,下面的URL有效:
  1. http://serverName/appName/?s=module~action~id~1
其实是利用了VAR_PATHINFO参数,用普通模式的实现模拟了PATHINFO的模式。但是兼容模式并不需要自己传s变量,而是由系统自动完成URL部分。正是由于这个特性,兼容模式可以和PATHINFO模式之间直接切换,而不需更改模板文件里面的URL地址连接。我们建议的方式是采用PATHINFO模式开发,如果部署的时候环境不支持PATHINFO则改成兼容URL模式部署即可,程序和模板都不需要做任何改动。

2. 模块和操作

http://域名/项目名/分组名/模块名/操作名/其他参数
Dispatcher会根据URL地址来获取当前需要执行的项目、分组(如果有定义的话)模块、操作以及其他参数,在某些情况下,项目名可能不会出现在URL地址中(通常情况下入口文件则代表了某个项目,而且入口文件可以被隐藏)。
每一个模块就是一个控制器类,通常位于项目的Lib\Action目录下面。
3.1版本开始,增加ACTION_SUFFIX配置参数,用于设置操作方法的后缀。
例如,如果设置:
  1. 'ACTION_SUFFIX'=>'Act'
那么访问某个模块的add操作对应读取模块类的操作方法则由原来的add方法变成addAct方法。

3. 定义控制器和空操作,空模块

一个应用如果不需要和数据库交互的时候可以不需要定义模型类,但是必须定义Action控制器,一般位于项目的Lib/Action目录下面。
Action控制器的定义非常简单,只要继承Action基础类就可以了,例如:
  1. Class UserAction extends Action{}
控制器文件的名称是UserAction.class.php。
空操作是指系统在找不到指定的操作方法的时候,会定位到空操作(_empty)方法来执行,利用这个机制,我们可以实现错误页面和一些URL的优化。
空模块的概念是指当系统找不到指定的模块名称的时候,系统会尝试定位空模块(EmptyAction),利用这个机制我们可以用来定制错误页面和进行URL的优化。

4. 模块分组

模块分组相关的配置参数包括:
配置参数 说明
APP_GROUP_LIST 项目分组列表(配置即表示开启分组)
DEFAULT_GROUP 默认分组(默认值为Home)
TMPL_FILE_DEPR 分组模板下面模块和操作的分隔符,默认值为“/”
VAR_GROUP 分组的URL参数名,默认为g(普通模式URL才需要)
例如我们把当前的项目分成Home和Admin两个组,分别表示前台和后台功能,那么只需要在项目配置中添加下面的配置:
  1. 'APP_GROUP_LIST' => 'Home,Admin', //项目分组设定
  2.  'DEFAULT_GROUP'  => 'Home', //默认分组
多个分组之间用逗号分隔即可,默认分组只允许设置一个。

5. URL伪静态

ThinkPHP支持伪静态URL设置,可以通过设置URL_HTML_SUFFIX参数随意在URL的最后增加你想要的静态后缀,而不会影响当前操作的正常执行。例如,我们设置
  1. 'URL_HTML_SUFFIX'=>'shtml'
的话,我们可以把下面的URL
  1. http://serverName/Blog/read/id/1
变成
  1. http://serverName/Blog/read/id/1.shtml
后者更具有静态页面的URL特征,但是具有和前面的URL相同的执行效果,并且不会影响原来参数的使用。
注意:伪静态后缀设置时可以不包含后缀中的“.”。所以,下面的配置其实是等效的:
  1. 'URL_HTML_SUFFIX'=>'.shtml'
伪静态设置后,如果需要动态生成一致的URL,可以使用U方法在模板文件里面生成URL。 3.1版本开始,默认情况下,可以支持所有的静态后缀,并且会记录当前的伪静态后缀到常量__EXT__,但不会影响正常的页面访问。如果要获取当前的伪静态后缀,通过常量__EXT__获取即可。 如果只是希望支持配置的伪静态后缀,可以直接设置成可以支持多个后缀,例如:
  1. 'URL_HTML_SUFFIX'=>'html|shmtl|xml' // 多个用 | 分割
如果设置了多个伪静态后缀的话,使用U函数生成的URL地址中会默认使用第一个后缀,也支持指定后缀生成url地址。

6. URL路由

ThinkPHP支持URL路由功能,要启用路由功能,需要设置URL_ROUTER_ON 参数为true。开启路由功能后,并且配置URL_ROUTE_RULES参数后,系统会自动进行路由检测,如果在路由定义里面找到和当前URL匹配的路由名称,就会进行路由解析和重定向。
详情见:http://doc.thinkphp.cn/manual/url_route.html

7. URL重写

详情见:http://doc.thinkphp.cn/manual/url_rewrite.html

<code><code><code><code><code><code><code><code><code><code><code><code>

8. URL生成

<code><code><code><code><code><code><code><code><code><code><code><code>为了配合所使用的URL模式,我们需要能够动态的根据当前的URL设置生成对应的URL地址,为此,ThinkPHP内置提供了U方法,用于URL的动态生成,可以确保项目在移植过程中不受环境的影响。
U方法的定义规则如下(方括号内参数根据实际应用决定):<code>
  1. U('[分组/模块/操作]?参数' [,'参数','伪静态后缀','是否跳转','显示域名'])
<code><code><code><code><code><code><code><code><code><code><code><code><code>如果不定义项目和模块的话 就表示当前项目和模块名称,下面是一些简单的例子:<code>
  1. U('User/add') // 生成User模块的add操作的URL地址
  2. U('Blog/read?id=1') // 生成Blog模块的read操作 并且id为1的URL地址
  3. U('Admin/User/select') // 生成Admin分组的User模块的select操作的URL地址
<code><code><code><code><code><code><code><code><code><code><code><code><code>U方法的第二个参数支持数组和字符串两种定义方式,如果只是字符串方式的参数可以在第一个参数中定义,例如:<code>
  1. U('Blog/cate',array('cate_id'=>1,'status'=>1))
  2. U('Blog/cate','cate_id=1&status=1')
  3. U('Blog/cate?cate_id=1&status=1')
<code><code><code><code><code><code><code><code><code><code><code><code><code>三种方式是等效的,都是 生成Blog模块的cate操作 并且cate_id为1 status为1的URL地址
但是不允许使用下面的定义方式来传参数<code>
  1. U('Blog/cate/cate_id/1/status/1')
<code><code><code><code><code><code><code><code><code><code><code><code> 根据项目的不同URL设置,同样的U方法调用可以智能地对应产生不同的URL地址效果,例如针对
  1. U('Blog/read?id=1')这个定义为例。
如果当前URL设置为普通模式的话,最后生成的URL地址是: 
http://serverName/index.php?m=Blog&a=read&id=1
如果当前URL设置为PATHINFO模式的话,同样的方法最后生成的URL地址是: 
http://serverName/index.php/Blog/read/id/1
如果当前URL设置为REWRITE模式的话,同样的方法最后生成的URL地址是: 
http://serverName/Blog/read/id/1
如果当前URL设置为REWRITE模式,并且设置了伪静态后缀为.html的话,同样的方法最后生成的URL地址是: 
http://serverName/Blog/read/id/1.html
U方法还可以支持路由,如果我们定义了一个路由规则为:
  1.  'news/:id\d'=>'News/read'
那么可以使用
  1. U('/news/1')
最终生成的URL地址是:
  1. http://serverName/index.php/news/1

注意:如果你是在模板文件中直接使用U方法的话,需要采用 {:U('参数1', '参数2'…)} 的方式,具体参考模板引擎章节的8.3 使用函数内容。

如果你的应用涉及到多个子域名的操作地址,那么也可以在U方法里面指定需要生成地址的域名,例如:<code>

  1. U('Blog/read@blog.thinkphp.cn','id=1');

<code>@后面传入需要指定的域名即可。

此外,U方法的第5个参数如果设置为true,表示自动识别当前的域名,并且会自动根据子域名部署设置APP_SUB_DOMAIN_DEPLOY和APP_SUB_DOMAIN_RULES自动匹配生成当前地址的子域名。
如果开启了URL_CASE_INSENSITIVE,则会统一生成小写的URL地址。

 

9. URL大小写

只要在项目配置中,增加:<code>

 

  1. 'URL_CASE_INSENSITIVE' =>true

<code>就可以实现URL访问不再区分大小写了。

这里需要注意一个地方,如果我们定义了一个UserTypeAction的模块类,那么URL的访问应该是:<code>

  1. http://serverName/index.php/user_type/list
  2.  //而不是
  3. http://serverName/index.php/usertype/list

<code>利用系统提供的U方法可以为你自动生成相关的URL地址。
如果设置<code>

  1. 'URL_CASE_INSENSITIVE' =>false

<code>的话,URL就又变成:<code>

  1. http://serverName/index.php/UserType/list

<code>注意:URL不区分大小写并不会改变系统的命名规范,并且只有按照系统的命名规范后才能正确的实现URL不区分大小写。

 

10. 前置和后置操作

系统会检测当前操作是否具有前置和后置操作,如果存在就会按照顺序执行,前置和后置操作的方法名是在要执行的方法前面加 _before_和_after_,例如:<code>

 

  1. class CityAction extends Action{
  2.     //前置操作方法
  3.     public function _before_index(){
  4.         echo 'before
    ';
  5.     }
  6.     public function index(){
  7.         echo 'index
    ';
  8.     }
  9.     //后置操作方法
  10.     public function _after_index(){
  11.         echo 'after
    ';
  12.     }
  13.  }
对于任何操作方法我们都可以按照这样的规则来定义前置和后置方法。
<code><code><code><code><code><code><code><code><code><code><code><code> 如果当前的操作并没有定义操作方法,而是直接渲染模板文件,那么如果定义了前置 和后置方法的话,依然会生效。真正有模板输出的可能仅仅是当前的操作,前置和后置操作一般情况是没有任何输出的。
需要注意的是,在有些方法里面使用了exit或者错误输出之类的话 有可能不会再执行后置方法了。
例如,如果在当前操作里面调用了系统Action的error方法,那么将不会再执行后置操作,但是不影响success方法的后置方法执行。
<code><code><code><code><code><code><code><code><code><code><code><code> <code><code>

11. 跨模块调用

<code><code>例如,我们在Index模块调用User模块的操作方法<code>
  1. class IndexAction extends Action{
  2.     public function index(){
  3.         //实例化UserAction
  4.         $User = new UserAction();
  5.         //其他用户操作
  6.          //...
  7.         $this->display(); //输出页面模板
  8.     }
  9.  }
<code><code> 因为系统会自动加载Action控制器,因此 我们不需要导入UserAction类就可以直接实例化。
并且为了方便跨模块调用,系统内置了A方法和R方法。    $User = A('User');
<code> <code><code>事实上,A方法还支持跨分组或者跨项目调用,默认情况下是调用当前项目下面的模块。
跨项目调用的格式是:
A('[项目名://][分组名/]模块名')
例如:<code>
  1. A('User') //表示调用当前项目的User模块
  2. A('Admin://User') //表示调用Admin项目的User模块
  3. A('Admin/User') //表示调用Admin分组的User模块
  4. A('Admin://Tool/User') //表示调用Admin项目Tool分组的User模块
<code><code> R方法表示调用一个模块的某个操作方法,调用格式是:
R('[项目名://][分组名/]模块名/操作名',array('参数1','参数2'…))
例如:
  1. R('User/info') //表示调用当前项目的User模块的info操作方法
  2. R('Admin/User/info') //表示调用Admin分组的User模块的info操作方法
  3. R('Admin://Tool/User/info') //表示调用Admin项目Tool分组的User模块的info操作方法
R方法还支持对调用的操作方法需要传入参数,例如User模块中我们定义了一个info方法:
  1. class UserAction extends Action{
  2.     protected function info($id){
  3.         $User = M('User');
  4.         $User->find($id);
  5.         //...
  6.     }
  7.  }
接下来,我们可以在其他模块中调用:
  1. R('User/info',array(15))
表示调用当前项目的User模块的info操作方法,并且id参数传入15 <code> <code><code><code>

12. 页面跳转

<code><code><code>系统的Action类内置了两个跳转方法success和error,用于页面跳转提示,而且可以支持ajax提交。使用方法很简单,举例如下:<code>
  1. $User = M('User'); //实例化User对象
  2. $result = $User->add($data); 
  3. if($result){
  4.     //设置成功后跳转页面的地址,默认的返回页面是$_SERVER['HTTP_REFERER']
  5.     $this->success('新增成功', 'User/list');
  6. } else {
  7.     //错误页面的默认跳转页面是返回前一页,通常不需要设置
  8.     $this->error('新增失败');
  9. }
<code><code><code> Success和error方法都有对应的模板,并且是可以设置的,默认的设置是两个方法对应的模板都是:
  1. //默认错误跳转对应的模板文件
  2. 'TMPL_ACTION_ERROR' => THINK_PATH . 'Tpl/dispatch_jump.tpl';
  3. //默认成功跳转对应的模板文件
  4. 'TMPL_ACTION_SUCCESS' => THINK_PATH . 'Tpl/dispatch_jump.tpl';
也可以使用项目内部的模板文件
  1. //默认错误跳转对应的模板文件
  2. 'TMPL_ACTION_ERROR' => 'Public:error';
  3. //默认成功跳转对应的模板文件
  4. 'TMPL_ACTION_SUCCESS' => 'Public:success';
模板文件可以使用模板标签,并且可以使用下面的模板变量:
$msgTitle 操作标题
$message 页面提示信息
$status 操作状态 1表示成功 0 表示失败 具体还可以由项目本身定义规则
$waitSecond 跳转等待时间 单位为秒
$jumpUrl 跳转页面地址
success和error方法会自动判断当前请求是否属于Ajax请求,如果属于Ajax请求则会调用ajaxReturn方法返回信息,具体可以参考后面的AJAX返回部分。 <code><code><code> 3.1版本开始,error和success方法支持传值,无论是跳转模板方式还是ajax方式 都可以使用assign方式传参。例如:
  1. $this->assign('var1','value1');
  2. $this->assign('var2','value2');
  3. $this->error('错误的参数','要跳转的URL地址');
当正常方式提交的时候,var1和var2变量会赋值到错误模板的模板变量。
当采用AJAX方式提交的时候,会自动调用ajaxReturn方法传值过去(包括跳转的URL地址url和状态值status) <code><code><code>

13. 重定向

Action类的redirect方法可以实现页面的重定向功能。
redirect方法的参数用法和U函数的用法一致(参考上面的URL生成部分),例如:
  1. //重定向到New模块的Category操作
  2. $this->redirect('New/category', array('cate_id' => 2), 5, '页面跳转中...');
上面的用法是停留5秒后跳转到News模块的category操作,并且显示页面跳转中字样,重定向后会改变当前的URL地址。
如果你仅仅是想重定向要一个指定的URL地址,而不是到某个模块的操作方法,可以直接使用redirect方法重定向,例如:
  1. //重定向到指定的URL地址
  2. redirect('/N
관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 추천
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿