Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

基于mvc设计模式的php框架,单入口的设计,底层组件化的操作,IoC控制实现低耦合。代码简约冗余度小,可读性强,功能稳定可依赖。:rocket:

Notifications You must be signed in to change notification settings

fantiq/phpframework

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

13 Commits

Repository files navigation

<title></title>

概览

1、MVC

主流的设计模式,分离界面与逻辑,高效的协作开发。

2、模块化

采用模块形式组织控制器。解决大型项目文件多、难管理的难题,避免后期迭代文件增多导致代码管理混乱的场面。小项目同样可以采用简单的但模块化的架构组织。

3、组件式

框架所有功能都是以组件形式工作,您可以根据自己的需求对各个组件进行改进升华,以适应自己的开发规则,同时可以添加自己的功能组件到框架中,过程简单易用。

4、易使用

在控制器以及模型中可以自动的加载所有组件,省去配置、加载等繁琐的操作,一切都是如此简单$this->组件名

5、高效率

框架根据php语言的特性,在特定部分采用单例的设计模式以节省内存的使用。采用控制反转(IoC)的设计模式实例化类,以降低模块之间的耦合度。

6、数据库

数据库提供了三种操作形式。1 直接执行sql,2 通过连贯操作组合sql语句,3 ORM操作数据库,简单快捷。

7、安全性

1、用户发送的数据全部进行初步检测,并且销毁全局数组,防止一句话脚本的攻击
2、提供数据过滤,清除非打印字符,文件名不合法,XSS字符串。让网站免受跨站攻击
3、防止sql注入,数据库在执行前都会对sql字符串进行合法性检测
4、提供数据格式验证组件,对用户提交的数据类型进行检测,防止数据表字段溢出 5、提供验证码以及表单CSRF防御机制,以应对互联网的'洪水'攻击

项目

创建项目文件夹

我们创建自己的项目文件夹叫做 App,然后在文件夹里面创建我们项目需要的各个文件
  • App
  • controllers放置控制器类
  • models 放置模型类
  • config 放置配置文件
  • view 放置模版文件
  • index.php 入口文件
其他文件夹用户可自行创建,建议您将项目文件(控制器、模型类、配置文件)与可访问的文件(入口文件、图片、css、js)分开存放,并且设置不同的读写权限。

入口文件

入口文件内容仅仅需要下面的三行代码
<?php
	define('APP_PATH', dirname(__DIR__));	 		// 指定项目文件夹
	include dirname(__DIR__).'/src/App.php'; 			// 加载框架入口文件
	App::run();									// 执行框架
					

配置

设置你的配置文件 参照这里 配置文件

配置

配置文件

下面是配置文件夹的内容:
  • config
  • configs.php核心配置文件
  • rules.php表单自动验证定义的规则
  • hooks.php定义的钩子程序
  • datas.php自定义的配置参数
具体请参阅源代码中configs.php文件的注释

主配置文件

<?php
return [
	// 全局的应用程序配置项
	'application'=>[
		'id'=>'app',												// 应用程序的id,项目的命名空间会用到
		'timezone'=>'RPC', 											// 设置时区
		'hooks'=>['file'=>'config/hooks.php','class'=>'Hooks'], 	// 指定钩子程序的位置
	],
	// 路由配置
	// 'router'=>[
	// 0 自动识别url
	// 1 ?m=Admin&c=Access&a=login&arg1=1....
	// 2 Admin/Access/login/arg1/arg2...
	// 3 ?r=Admin/Access/login/arg1/arg2...
	// 4 
		'urlmode'=>0,
		'defaultController'=>'Index',		// 默认控制器
		'defaultAction'=>'index',			// 默认方法
		'modules'=>['Api/WeiXin','Admin'],	// 存在的模块名
		'regex'=>[							// 正则匹配url规则
			'pattern'=>'Index',
		],
	],
	// 数据库配置
	'database'=>[
		// 'dsn'=>'pdo-mysql://root@127.0.0.1/test',
		'dsn'=>'mysql://root@127.0.0.1/test', 	// dsn形式
		'scheme'=>'pdo-mysql',					// 数据库类型(pdo类型的要以 pdo-模型 的形式指定)
		'host'=>'127.0.0.1',					// 地址
		'port'=>3306,							// 端口
		'dbname'=>'test',						// 数据库名称
		'user'=>'root',							// 帐号
		'passwd'=>'321321',						// 密码
		'charset'=>'utf8',						// 数据表编码
		'prefix'=>''							// 数据表前缀
	],
	// 内存缓存
	'cache'=>[
		'dsn'=>'memcache://127.0.0.1:11211', 	// dsn字符串形式定义
		'scheme'=>'memcache',					// 缓存类型
		'host'=>'127.0.0.1',					// 地址
		'port'=>11211,							// 端口
	],
	// session
	'session'=>[
		'auto_start'=>false,	// 自动加载
		'passwd'=>'321321',	// 连接store的密码
		// 'dsn'=>'pdo-mysql://root:@127.0.0.1:3306/session/sess_tab',
		'dsn'=>'memcache://127.0.0.1:11211', 	// 字符串形式
		'scheme'=>'pdo-mysql', 					// 存储session数据库的模型
		'host'=>'127.0.0.1',					// 地址
		'port'=>3306,							// 端口号
		'user'=>'root',							// 用户名
		'dbname'=>'session',					// 存储session的数据库名称
		'tbname'=>'sess_tab',					// 存储session的数据表名称
		'charset'=>'utf8',						// 编码方式
		'prefix'=>'',							// 表前缀
		'sess_name'=>'__SESSIONID__',			
		'sess_expire'=>3600*24,		// 默认session过期时间
		'alive_time'=>3600,			// 用户活跃时间间隔 这个时间内没有任何操作视为下线
		'cookie_path'=>'/',			// cookie 路径
		'cookie_domain'=>'',		// cookie 域名
	],
	// 模版配置
	'view'=>[
		'drive'=>'template',			// 模板引擎
		'skin'=>'default',				// 默认皮肤
		'tpl_ext'=>'php',				// 模版文件后缀
		'form_hash_name'=>'__hash__',	// 表单hash字段名
		'form_hash_keys'=>'fantasy',	// 表单hash的key
	],
	/////////////
	// 加载用户数据 //
	/////////////	
	'datas'=>include 'datas.php',
	'alias'=>[],
];
					

钩子程序

默认的钩子程序是在 项目文件夹/config/hooks.php,你也可以在配置文件中指定其他的文件作为钩子程序。
# 方法名 作用
1 preSystem 框架初始化之后调用执行
2 preRoute 在路由解析url之前调用
3 preController 在url解析之后,调用执行指定的控制器之前调用
4 preResponse 在发送内容到用户客户端之前调用
5 endSystem 整个交互过程完成之后执行
钩子程序中的类继承了Base.class 你在钩子程序中是可以调用所有绑定过的组件的,但是你不能修改钩子程序类中的方法名。
<?php
use framework\base\Base;
class Hooks extends Base{
	public function __construct(){
		$this->preSystem();
	}
	// 框架开始执行之前 初始化之前
	public function preSystem(){}
	// 路由开始解析之前
	public function preRoute(){
		// echo 'hello';
	}
	// 路由解析之后 调用用户控制器之前
	public function preController(){
		// echo "你请求的控制器是:".$this->dispatch->getControllerName()."
"; // echo "你请求的控制器方法是:".$this->dispatch->getActionName()."
"; } //发送内容到用户浏览器之前 public function preResponse(){} 框架结束 public function endSystem(){} }

路由解析规则

框架支持多种的url风格
  • ?m=模块名&c=控制器名&a=方法&args....
  • /模块名/控制器名/方法/args....
  • ?r=模块名/控制器名/方法/args....

模块名是为了指明你的控制器所在的文件夹目录,未指定则指定是 项目/controllers/ 这个目录,指定的话需要你事先在配置文件中 ['router']['modules'] => [模块1,模块2,模块3] 声明。指定模块名的话,框架所调用的控制器文件路径就是 项目/controllers/模块名/ 这个目录了。
比如我们需要访问一个控制器,这个控制器文件所在目录路径 App/controllers/Pay/Taobao/CashController.php 如下图:
  • App
  • controllers
  • Pay 嵌套的模块
  • Taobao 嵌套的模块
  • CashController.php 控制器类文件
  • models
  • config
我们可以知道,要访问的控制器 CashController.php 在文件夹 controllers/Pay/Taobao 下,而 Pay/Taobao 则是这个控制器的模块名。URL中的参数部分应该是这样的 /Pay_Taobao/Cash/index Pay_Taobao 会被框架自动转义成 Pay/Taobao ,URL中的模块名 控制器名都会被自动转义,规则如下
URL部分 原来的 转义后 转义规则
模块名 foo Foo 在没有 '-' '_' 字符串时,将模块名字符串首字母大写
模块名 foo_bar Foo/Bar 对于中间有下划线的,会依据下划线分割字符串,再将每个字符串首字母大写然后用 '/' 将字符串拼接
模块名 foo-bar FooBar 对于中间有横线的,会依据横线分割字符串,再将每个字符串首字母大写然后直接将字符串拼接
控制器 bar Bar 在没有 '-' 字符串时,将控制器的首字母大写。(所以在类以及类文件的命名规则都要首字母大写)
控制器 foo-bar FooBar 对于中间有横线的,会依据横线分割字符串,再将每个字符串首字母大写然后直接将字符串拼接

控制器

命名规则

我们采用流行的驼峰命名法对文件名、类名进行命名规范
控制器文件名首字母要大写且要连接上字符串Controller,后缀为php。比如我们的控制器名称为Index,则文件名为 IndexController.php。若我们要定义的控制器名称是两个单词(customer 、analysis)组成的 则文件名应该是 CustomerAnalysisController.php 。值得注意的是 文件名要和类名一致,包括大小写

命名空间

我们采用命名空间对用户的类进行管理,这样您就不必担心类命名冲突的问题啦!如何使用命名空间?
1、在文件的最开始定义命名空间 namespace 自定义的名字
2、需要使用一个类的时候 use 自定义的命名空间\类名 更多关于命名空间的用法可以到这里查看 php5增加的命名空间以及异常
3、命名空间的名称是以 foo\bar\class 这样的形式来命名的,一般这个字符串要反映出所属模块以及文件路径的 模块\路径\路径..
创建我们的第一个控制器 IndexController.php
在controllers目录创建一个文件名称为 IndexController.php 文件内容如下:
<?php
namespace app\controllers;					// 命名空间
use framework\base\Controller;				// 继承框架的控制器
class IndexController extends Controller{		// 定义类名
	public function show(){ 				// 创建一个方法
		echo "Hello World!";
	}
}
					
对于命名空间 app\controllers app指明模块是我们的项目 ,controllers是我们文件的存放路径。注意,这里的 app 是你在配置文件中定义的 ['application']['id'] 项。在你的项目中的所有类定义命名空间都要以这个id为开头,来表示文件所属的模块。 框架的命名空间是以 framework 为开始的。并且控制器要继承框架的控制器,这样你就能方便的调用各个组件啦!
// 常用的页面显示函数
$this->assign('key','val') 为模版赋值
$this->display('tpl_name') 显示模版

数据库

连贯操作

无论是在控制器还是在模型中进行编码,我们都可以随时使用数据库,只需要你简单的直接使用 $this->db
  • $this->db->select(查询的字段)->where(查询条件)->limit(条数限制)->all()
  • $this->db->insert(表名,存储的数据)
  • $this->db->where(条件)->update(表名,修改的数据)
  • $this->db->update(表名,修改的数据,条件)
  • $this->db->where(条件)->delete(表名)
  • $this->db->delete(表名,条件)
1、查询
一个查询
$this->db->select()->from('tests')->where('level',2)->order('name','DESC')->limit(10,15)->all();
最终执行的sql语句是这样的
select * from `tests` where level=2 order by name desc limit 10,15
对于where条件语句值得注意,你可以用三种形式构造你个查询条件(或简单或复杂)
  • 1、where(field,value) 简单的就是两个值,field=value
  • 2、where(field=>value,比较符,连接符) 多个条件的情况
  • 3、where('field1=? and field2>?',['name',3]) 以statement的形式传递参数
举例如下:
$this->db->where([
	['name'=>'fantasy','=','and'],
	['date'=>'12-01','>','or'],
	['date'=>'12-31','<'],
])
对应的SQL语句: where name='fantasy' and date>'12-01' or date<'12-31'
2、添加
添加数据就是 $this->db->insert(tablename,data) tablename 就是表名成,对于data是添加的数据,形如 [field1=>val1,field2=>val2,field3=>val3.....]。若要增加多条记录data需要是多维数组的形式
[
	[field1=>val1-1,field2=>val1-2,field3=>val1-3.....], 	// 第一条数据
	[field1=>val2-1,field2=>val2-2,field3=>val2-3.....],	// 第二条数据
	[field1=>val3-1,field2=>val3-2,field3=>val3-3.....], 	// 第三条数据
	[field1=>val4-1,field2=>val4-2,field3=>val4-3.....], 	// 第四条数据
	........ 											// 更多
]
3、修改
更新数据 $this->update(tablename,data[,condition])
tablename 要更新的表名称,data 更新的数据,更新的条件(这里可以不填,在$this->db->where() 处指明条件)。 数据部分的形式多为这样
data = [field1=>newValue1,field2=>newValue2,field3=>newValue3,.......]
4、删除
$this->delete(表名称,条件) or $this->where(条件)->delete(表名称)

ORM

值得注意的是目前的ORM仅限于在model层使用,ORM的操作会让你的开发效率更上一层楼。当我们想要映射一张表的时候只需要在模型(model)的方法里这样写 $表的实例化对象 = $this->orm(表名称)
我们创建这样一张表
字段名称 类型 注释
id unsigned int 记录id
name varchar(255) 名称
email varchar(255) 邮箱
addr text 地址
add_time unsigned int 添加记录的时间
ip unsigned int 用户的ip
1、添加数据
public function testOrm(){
	$tests = $this->orm('tests'); // 声明一个表的映射
	$tests->name='orm'; 		// 赋值
	$tests->email='orm@oa1024.com';
	$tests->addr='jinhua';
	$tests->add_time=time();
	$tests->ip = sprintf('%u',ip2long('192.168.0.12'));
	var_dump($tests->add()); /// 调用add方法添加数据 成功返回true 失败返回false
}
2、修改数据
$orm->save(条件) 这个条件跟非orm模式的条件格式一样。不同的是,为了安全起见,如果在更新的时候不想设置条件,需要在条件的部分填写false,否则方法返回false
$tests = $this->orm('tests');
$tests->name = 'fantiq';
$tests->email = 'fantiq@163.com';
var_dump($tests->save(['id',1]));
3、删除
$orm->delete(条件) 条件跟save方法的用法一样。
4、查询数据
ORM模块提供了方法的查询方法,满足您各种的查询需求。查询结果都是以对象的形式返回,默认是只去结果集的前20条的,你可以通过setLimit(...)来修改这个参数;查询条件中如果只传递一个只的话,框架会将这个值使用在表的主键字段上,默认的主键字段是 id ,你可以通过 setPrimaryKey 来修改这个参数。下面是ORM查询方面的方法:
  • get([fields...]) 获取指定字段的所有数据
  • getOne([fields...]) 获取指定字段的一条数据
  • getWhere(条件) 根据条件查询数据
  • getBy(排序规则[order/group],排序字段) 对查询结果进行排序
  • getWhereBy(条件,排序规则,排序字段) 根据条件查询数据并根据排序规则将结果排序
  • setLimit() 修改limit的参数
  • setPrimaryKey() 设置主键

模版

控制器调用模版

在控制器里面我们常用的是assign(key,val) 方法,对模版进行赋值,display([path]) 方法进行显示页面信息,若display指定参数则会调用指定路径的页面,若未指定这调用默认页面 项目/views/皮肤/控制器名称/方法名.模版后缀。 cache(时间min) 方法可以设置页面静态缓存,参数单位为分钟,表示缓存过期时间。
public function index(){
	$this->assign('name','fantasy');
	$this->assign('lists',['fantasy','addr','time']);
	// $this->cache(1); // 一分钟的缓存
	$this->display();
	// $this->display('ad/main');
}

模版输出

你可以通过 setLeftTag() setRightTag() 这些方法定义模版标签的界定符,默认是 "<{" "}>" 。模版支持皮肤功能功能,你可以通过 setSkin() 方法切换皮肤,默认的皮肤是 'default' 。
输出
<{变量名}> 									<!-- 这样会直接输出变量 -->

<{foreach $data->$val}> <!-- 遍历数字索引的数组 --> <{val}> <{/foreach}> <{foreach $data=$key->$val}> <!-- 遍历字符串索引的数组 --> <{key}>---><{val}> <{/foreach}>

<{if 判断条件}> <!-- 分支判断 --> .... <{else}> .... <{/if}>

模版功能
页面可被分为多个模块,可以通过模版提供的命令加载模版
@@
layout 模版路径(不要写文件后缀)
css css资源
js js资源
@@
layout 可以让你指定模版文件,然后当前模版的内容会填充到指定模版的位置( 指定模版的标签 <{tag-content}>)处。css 、js 会将指定的资源引用一并注入到模版中(模版中对应的标签是 <{tag-assets}>)。

组件

Session

框架中session的使用非常简单,你只需要 $this->session->start() session数据就加载成功并且能够提供给你使用了,并且框架会在最后更新数据并持久化存储,常用的方法有如下几种,使用非常简单:
  • $this->session->set(key,val) 设置或修改session数据
  • $this->session->get(key) 获取session数据,若设置key值,则返回所有的session数据
  • $this->session->del(key) 删除一项session数据
  • $this->session->isOnline(user_id) 查询某个uid是否在线
  • $this->session->countOnline() 返回总在线用户数
值得注意的是你需要在配置文件中指明你需要存储session的数据库(mysql memcache redis),建议您使用memcache redis的内存型缓存数据库效率会高,mysql或者php文件型的存储在大访问量下过多的IO使项目效率下降。如若你使用php原生的session机制,可以直接使用 $_SESSION 数据进行操作。

数据验证

框架提供了web应用中最常用的几个数据格式验证 ,你可以通过 $this->verify->验证函数(参数) 直接使用,下面列出这些方法以及用法
1、字段不能为空
$this->verify->required(string $str); 方法会先过滤用户提交的不可打印的字符,其次过滤掉空格,最后检测是否是空字符串。
2、邮箱格式验证
$this->verify->email(string $email) 内部采用的是php的过滤函数进行的验证,字符串太长(超过1000字符)也会返回false
3、url链接格式验证
$this->verify->url(string $url) 由于php自带的验证不太灵活(不带协议会返回false),采取新的验证格式。
4、ip地址验证
$this->verify->ip(string $ip) 采用php内部过滤函数的验证方法。
5、身份证号码验证
$this->verify->id(string/int $id) 采用身份证的数据验证算法对身份证数字格式进行验证。
6、手机号(中国)格式验证
$this->verify->phone(string/int $phone) 手机号码验证,目前只支持中国的手机号。
7、数组验证
$this->verify->number(int/string number,int min,int max) 检测给定的数字是否在 min max的范围内,如果仅需要检测数字是否小于某个数只需要,$num=10;$this->verify->number($num,null,100) 这个是检测数字$num是否小于 100。
8、检测字符串格式
$this->verify->string(string $str,string $rule) 其中$rule 可以是 alpha、num、zh 以及其他的。alpha表示仅允许字符串,num仅允许数字,zh仅允许汉字。若我们需要字符串允许数字级字母就要这样写 $rule = "alpha,num" $this->verify->string($str,"alpha,num")。若我们需要允许其他的字符只需要在$rule字符串后面跟上需要允许的字符并用 ',' 隔开即可。比如 $this->verify->string('fanyilong@sina.com','alpha,num,@,.') 这个将返回true
9、检测字符串长度
$this->verify->len(string $str,int min,int max)
10、检测是否存在一个列表中
$this->verify->in(mixed $var,string $lists) 变量 $lists 是一列数据的字符串,这些数据用 ',' 隔开。如同这样的数据 $lists = "android,iOS,linux,centos,windows"; 方法就是检测给定的数值是否存在于这个列中。
11、匹配
$this->verify->match(mixed $var,string $field) 这个功能最常用在在注册的时候检测两次密码是否一致。$var 是值,$field是需要检测值是否一致的字段名称。

配置

配置的用法最简单 当我我们想要获取某项值的时候 $this->config->get(key); 同样在需要设置修改某项值的时候 $this->config->set(key,val);

HTTP数据

在web交互方面这个用的很频繁,但是又十分容易遭到攻击(XSS),框架最这些数据进行了初步的过滤并删除全局变量,防止在不正当的使用中导致攻击。
  • $this->request->get(key) 获取GET形式的数据
  • $this->request->post(key) 获取POST形式的数据
  • $this->request->cookie(key) 获取COOKIE数据
  • $this->request->files(key) 获取上传文件 $_FILES 数据
  • $this->setGet(key,val) 设置修改GET的某项数据
  • $this->setPost(key,val) 设置修改POST的某项数据
  • $this->setCookie(key,val) 设置修改COOKIE的某项数据

工具

工具类组件中提供了丰富的类供您使用,您只需要 $upload = $this->utils->getUpload(); 就可以使用上传类了。其他的有图片,验证码,文件上传,分页,文件系统 等等...

工具-上传文件

下面一段代码是文件上传,非常简单:
public function upload(){
$upload = $this->utils->getUpload(); 	// 实例化上传组件
$config = [								// 设置配置项
'type'=>['txt','jpg','png','gif'],
'size'=>2048,
'path'=>'./uploads',
'rename'=>true
];
if($upload->run(field)){				// run(field,config) 开始上传
// 上传成功 返回成功相关信息(上传后的文件路径)
print_r($upload->getInfo());
}else{
// 上传失败 获取失败信息
print_r($upload->getError());
}
}
配置部分: type =>[...] 指定允许的文件后缀;size=>2048 指定文件最大尺寸 单位是KB;path=>'./....' 指定上传文件的存储位置,注意这个文件夹要可写(chmod 777);rename=>true/false 是否将文件名重命名

工具-图片 / 验证码

同样通过 $img = $this->utils->getImage(); 实例化图片组件,这个组件主要有三个功能: captcha()生成验证码 ;thumb(缩放比例,保存路径,缩放参照)生成图片缩略图;crop()截取图片;
验证码: $image->captcha(string $h,number $n,array $bg,array $color)
你可以参考源代码里面的代码示例 验证码示例

参数列表:

  • $h 验证码的图片高度
  • $n 验证码字符数量
  • $bg 验证码图片背景色(rgb)
  • $color 验证码字符颜色(rgb)
图片缩放:$image->thumb(int $refer,string $savePath,int $order)

参数说明:

  • $refer缩放比例
  • $savePath处理后的图片保存路径
  • $order参照标准,0 参照宽度缩放,1 参照高度缩放
图片截取:$image->crop(int $start_x,int $start_y,int $x_len,int $y_len,int $dst_w,int $dst_h,string $savePath)
  • $start_xx轴开始截取的位置
  • $start_yy轴开始截取的位置
  • $x_lenx轴截取长度
  • $y_leny轴截取长度
  • $dst_w粘贴到的图片宽度
  • $dst_h粘贴到的图片高度
  • $savePath图片保存地址

工具-分页

使用分页工具的时候我们需要这样 $page = $this->utils->getPagination(int count,int listNum,int page) 解释下方法 getPagination的三个参数:
  • count 要分页的数据的总条数
  • listNum 每页显示的数据的条数
  • page 要显示哪页的数据
每页显示的条数我们可以通过 $page->setListNum(int n) 来动态设置,也可以在配置文件中设置
['application'=>[
	.....
	'page_lists'=> 每页显示的条数,
	....
	]
]
分页中的数据在技术上是反映在sql语句的limit部分的参数的写法,这个分页类通过计算为你提供的这些参数,$page->getStart() 返回limit要开始的位置,$page->getLimitNum() 返回取出显示的条数,在使用中就是下面这样:
public function list($p=1){
	$page = $this->utils->getPagination($count,$listNum,$p);
	sql..... limit .$page->getStart.','.$page->getListNum();
	// 或者 用getLimit() 方法,这个方法直接返回的是limit参数的组合
	sql..... limit $page->getLimit();
}
下面是前端框架的分页css的一个示例:
  • 上一页
  • 1
  • 2
  • 3
  • 4
  • 5
  • 下一页

工具-文件

About

基于mvc设计模式的php框架,单入口的设计,底层组件化的操作,IoC控制实现低耦合。代码简约冗余度小,可读性强,功能稳定可依赖。:rocket:

Resources

Stars

Watchers

Forks

Packages

No packages published

AltStyle によって変換されたページ (->オリジナル) /