2018.2.5 PHP如何写好一个程序用框架
随着PHP标准和Composer包管理工具的面世,普通开发者撸一个框架已经不再是什么难事了。
无论是路由管理、ORM管理、还是视图渲染都有许许多多优秀的包可以使用。我们就像堆积木一样把这些包用composer一个个堆积起来。
接下来我们便是简单地实现一个MVC框架,来加深我们对框架的理解。
composer
创建一个空的 composer.json 文件。
{
}
或者在空目录下执行:
composer init
则可以生成一个类型如下的文件:
{
"name": "charlie/my_first_framework",
"description": "My First Framework",
"type": "project",
"license": "MIT",
"authors": [
{
"name": "Charlie",
"email": "demo@qq.com"
}
],
"minimum-stability": "dev",
"require": {}
}
安装第一个包
我们接下来安装一个管理路由的包: noahbuscher/macaw。 功能比这个更加强大的路由包有很多,但是为了简单起见,我们选择安装这个包。
composer require noahbuscher/macaw
当前目录结构为:
➜ demo tree
.
├── composer.json
├── composer.lock
└── vendor
├── autoload.php
├── composer
│ ├── ClassLoader.php
│ ├── LICENSE
│ ├── autoload_classmap.php
│ ├── autoload_namespaces.php
│ ├── autoload_psr4.php
│ ├── autoload_real.php
│ ├── autoload_static.php
│ └── installed.json
└── noahbuscher
└── macaw
├── LICENSE.md
├── Macaw.php
├── README.md
├── composer.json
├── composer.lock
└── config
├── Web.config
└── nginx.conf
public/index.php
我们在根目录下新建一个public文件夹,并在该文件夹下新建 index.php。index.php 文件类似于一个大厦的入口,我们所有的请求都运行 index.php。
下面是 index.php 文件的代码:
// 自动加载vendor目录下的包require '../vendor/autoload.php';
routes.php
此时我们观察 index.php,除了把vendor下面的包都 require 进来了外,其他啥都没干。那么如何响应各种各样的请求呢?
我们需要定义路由。路由就有点像快递分拣站,把写着不同地址的请求分拨给不同的控制器处理。
那么我们在根目录下创建一个 routes 文件夹,并在该文件夹下创建 web.php 文件。
文件内容:
<?php
use NoahBuscher\Macaw\Macaw;
Macaw::get('hello', function () {
echo "hello world";
});
Macaw::dispatch();
然后我们启动php内置的开发服务器:
> cd public
> php -S localhost:8001
我们访问 http://localhost:8001/hello 就能看到我们预期的 "hello world".
上面我们仅仅实现了访问一个地址,返回一个字符串。
下面我们来真正搭建MVC框架。MVC其实就是Model、View、Controller三个的简称。
不管怎么样,我们先新建三个文件夹再说,即 views、models、controllers。
新建 controllersHomeController.php 文件,代码如下:
<?php
namespace App\Controllers;
use App\Models\Article;
class HomeController extends BaseController{
public function home()
{
echo "<h1>This is Home Page</h1>";
}
}
另外我们在 routes/web.php 中添加一条路由:
Macaw::get('', 'App\\Controllers\\HomeController@home');
整体代码为:
<?php
use NoahBuscher\Macaw\Macaw;
Macaw::get('hello', function () {
echo "hello world";
});
Macaw::get('', 'App\\Controllers\\HomeController@home');
Macaw::dispatch();
此时已经绑定了一个路由至我们一个控制器的方法,但是我们去访问 http://localhost:8001 会出现 Uncaught Error: Class 'App\Controllers\HomeController' not found in 的错误。为什么呢?
因为此时我们还并没有将控制器加载进来,程序并不知道控制器在哪儿。我们可以用 composer 的 classmap 方法加载进来。
修改 composer.json 中添加:
"autoload": {
"classmap": [
"app/controllers",
"app/models"
]
}
我们顺便把models也加载进来。
composer dump-autoload
刷新自动加载
Model连接数据库
我们创建一个Article Model,这个 Model 对应数据库一张表。此时我们先用mysql 命令行工具新建一个 demo_database 的数据库,里面有一张表 articles , 表的结构大致如下:
mysql> desc articles;
+---------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(256) | YES | | NULL | |
| content | varchar(256) | YES | | NULL | |
+---------+--------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
我们再在表里面填入数据:
mysql> select * from articles;
+----+--------+--------------+| id | title | content |
+----+--------+--------------+
| 1 | hhhhh | heheheheheh || 2 | hhhhh2 | heheheheh2eh |
+----+--------+--------------+
2 rows in set (0.00 sec)
当然了,我们现在是直接通过 MySQL 的 insert 命令直接填入数据,后续我们可以通过我们的框架新建 model 。
接下来我们要做的就是怎么在 Model 中连接数据库取到数据库里面的数据啦! 本文使用的 php 7.1,我们使用 mysqli 来连接数据库查询数据:
<?php
namespace App\Models;
class Article {
public static function first()
{
//mysql_connect is deprecated
$mysqli = new \mysqli('localhost', 'root', 'w.x.c.123', 'demo_database');
if ($mysqli->connect_errno) {
echo "Failed to connect to Mysql: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error();
}
$mysqli->set_charset('utf-8');
$result = $mysqli->query("SELECT * FROM articles limit 0,1");
$article = $result->fetch_array();
if ($article) {
echo "<p>" . $article['title'] . "</p>";
echo "<p>" . $article['content'] . "</p>";
}
$mysqli->close();
}
}
这么一来我们就可以在控制器里面使用
Article::first();
来查询 articles 表里面的第一条article数据,然后我们再通过 echo 返回给浏览器。
<?php
namespace App\Controllers;
use App\Models\Article;
class HomeController extends BaseController{
public function home()
{
Article::first();
}
}
View层
看上面的代码,我们在 Article Model 中连续写了两条 echo 语句来格式化输出。如果后续我们的页面十分复杂的时候,我们把所有的格式化输出的代码都写在 Model 里面感觉是个灾难。我们应该把这些格式化输出的代码分离出来,这便是我们说的 MVC 层的 View 层。
我们在 views 目录下新建 home.php:
<?php
echo "<p>" . $article['title'] . "</p>";echo "<p>" . $article['content'] . "</p>";
我们再改写一下 Article.php,删除echo 那两行,直接
return article;
然后我们在 HomeController 中指定要使用的 view:
<?php
namespace App\Controllers;
use App\Models\Article;
class HomeController extends BaseController{
public function home()
{
$article = Article::first();
require dirname(__FILE__) . "/../views/home.php";
}
}
我们这里的 view 层仅仅是用 PHP 拼接了 html,后续我们需要实现更加复杂的视图的时候,我们可以引入模版引擎。
ORM
我们一路从一个空文件夹开始,打造一个自己的一个框架,感觉并没有写多少代码,唯一写了好几行代码感觉比较麻烦的就是连接数据库来查询数据了。我们我们有很多 Model,要实现 增删改查的话,我们岂不是要重复 连接,查询、插入、删除、更新,然后关闭连接?我们应该把这些功能分装一下。
怎么封装?有其他人写好的包了,直接拿来用吧~ 当然如果你想自己造轮子的话,也可以自己实现一下。
我们这里使用 illuminate/database:
composer require illuminate/database
然后我们在 public/index.php 中引入:
use Illuminate\Database\Capsule\Manager as Capsule;
require '../vendor/autoload.php';
// Eloquent ORM
$capsule = new Capsule();
$capsule->addConnection(require '../config/database.php');
$capsule->bootEloquent();
//路由配置require '../routes/web.php';
我们在连接数据的时候,使用了 config/database.php 的数据库配置文件。
<?php
return [
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'demo_database',
'username' => 'root',
'password' => 'secret',
'charset' => 'utf8',
'collation' => 'utf8_general_ci',
'prefix' => ''
];
接下来我们就可以删掉 models/Article.php 文件中我们写的大部分代码,而仅仅需要继承IlluminateDatabaseEloquentModel 来使用 Eloquent ORM 的功能:
<?php/**
* Created by PhpStorm.
* User: W
* Date: 24/03/2018
* Time: 22:23
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model{
public $timestamps = false;
}
更多Eloquent ORM的功能,您也可以自己查阅文档。
总结
好了,我们一个 MVC 框架基本上就搭建完了,我们回头看一下整个框架目录结构,是不是跟 Laravel 有点像呢?
➜ myFirstFramework git:(master) ✗ tree
.
├── README.md
├── app
│ ├── controllers
│ │ ├── BaseController.php
│ │ └── HomeController.php
│ ├── models
│ │ └── Article.php
│ └── views
│ └── home.php
├── composer.json
├── composer.lock
├── config
│ └── database.php
├── public
│ └── index.php
├── routes
│ └── web.php
└── vendor ...
2018.2.5 PHP如何写好一个程序用框架的更多相关文章
- 用Java写了一个程序,将一个Mysql库中的表,迁移到另外一个server上的Mysql库中
用Navicat做数据迁移,因为数据量比较大,迁移过过程中一个是进展不直观,另外就是cpu占用率高的时候,屏幕跟死机了一样点不动按钮,不好中断. 想了想,干脆自己写一个. 在网上找了一个sqllite ...
- 写的一个轻量级javascript框架的设计模式
公司一直使用jQuery框架,一些小的项目还是觉得jQuery框架太过于强大了,于是自己周末有空琢磨着写个自己的框架.谈到js的设计模式,不得不说说js的类继承机制,javascript不同于PHP可 ...
- 用java写的一个程序,可以调用windows系统中自带的各种工具,例如截图,便签等
由于图片资源没有上传,所以运行后不会有图片,感兴趣的同学可以@我,我打包上传, package SmallPrograme; import java.awt.*; import java.awt.ev ...
- 写出一个程序,接受一个由字母和数字组成的字符串,和一个字符,然后输出输入字符串中含有该字符的个数。不区分大小写。java算法
知识点一:equalsIgnore 1.使用equals( )方法比较两个字符串是否相等.它具有如下的一般形式: boolean equals(Object str) 这里str是一个用来与调用字符串 ...
- python 文件单行循环读取的坑(一个程序中,文件默认只能按行循环读取一次,即使写到另一个循环里,它也只读取一次)
本来写了一个程序,想获取a文件中有,但是b文件中没有的行: 想到的方法是:1.一行一行提取a文件中数据,然后用a文件中的每一行与b文件中的每一行比较, 2.如果找到相同行就继续查找a中的下一行,如果找 ...
- 用最新的版本,蹦最野的迪~~~IDE写大数据程序避坑指南
文章更新于:2020-04-05 注:本次实验使用的操作系统及各个程序版本号 类别 版本号 说明 操作系统 Ubuntu 16.04.6 LTS 代号 xenial jdk java version ...
- 写了一个Hy的vscode语法高亮插件
-------2018 8 3----------- 把函数名和参数改了,正则有点古怪,参考自带的lambda表达式才搞定 但彩色括号走了弯路,各种配图有彩色括号的插件其实很少是自己搞的,其实只要再装 ...
- 搞了我一下午竟然是web.config少写了一个点
Safari手机版居然有个这么愚蠢的bug,浪费了我整个下午,使尽浑身解数,国内国外网站搜索解决方案,每一行代码读了又想想了又读如此不知道多少遍,想破脑袋也想不通到底哪里出了问题,结果竟然是web.c ...
- 用C3中的animation和transform写的一个模仿加载的时动画效果
用用C3中的animation和transform写的一个模仿加载的时动画效果! 不多说直接上代码; html标签部分 <div class="wrap"> <h ...
随机推荐
- 洛谷P1311 选择客栈
P1311 选择客栈 题目描述 丽江河边有n 家很有特色的客栈,客栈按照其位置顺序从 1 到n 编号.每家客栈都按照某一种色调进行装饰(总共 k 种,用整数 0 ~ k-1 表示),且每家客栈都设有一 ...
- Gson本地和服务器环境不同遇到的Date转换问题 Failed to parse date []: Invalid time zone indicator
GoogleGson在处理Date格式时有个小陷阱,在不同环境中部署时可能会遇到问题. Gson默认处理Date对象的序列化/反序列化是通过一个SimpleDateFormat对象来实现的,通过下面的 ...
- c#之 quartz的学习
目录: 一. Quartz的API 二.Trigger 的使用 三.使用 JobDataMap 来往Job中传值 四. Calendars 五.SimpleTrigger 六.CronTrigger ...
- Luogu P2833 等式 我是傻子x2
又因为调一道水题而浪费时间...不过细节太多了$qwq$,暴露出自己代码能力的不足$QAQ$ 设$d=gcd(a,b)$,这题不是显然先解出来特解,即解出 $\frac{a}{d}x_0+\frac{ ...
- 最小生成树(prim算法和kruskal算法)
学习博客:https://www.cnblogs.com/zhangming-blog/p/5414514.html 其实就是加点法:从不属于这个集合的点中找从本集合可以找到的最小边,加入本集合 看代 ...
- Bazinga HDU - 5510 不可做的暴力
http://acm.hdu.edu.cn/showproblem.php?pid=5510 想了很久队友叫我用ufs + kmp暴力过去了. fa[x] = y表示x是y的子串,所以只有fa[x] ...
- POJ 1061青蛙的约会。求解(x+mT)%L=(y+nT)%L的最小步数T。
因为是同余,所以就是(x+mT)%L-(y+nT)%L=0.可以写成(x-y+(m-n)T)%L=0.就是这个数是L的倍数啦.那么我可以这样x-y+(m-n)T + Ls = 0.就可以了,s可正可负 ...
- 机器学习框架ML.NET学习笔记【3】文本特征分析
一.要解决的问题 问题:常常一些单位或组织召开会议时需要录入会议记录,我们需要通过机器学习对用户输入的文本内容进行自动评判,合格或不合格.(同样的问题还类似垃圾短信检测.工作日志质量分析等.) 处理思 ...
- 修改response,报错Cannot call getWriter(), getOutputStream() already called
往response里面改数据,然后系统报这个错 此时直接return null即可解决 但是,要想返回相应的页面呢? 可以直接在response里设置返回的页面
- SpringMVC03 ParameterMethodNameResolver(参数方法名称解析器) And XmlViewResolver(视图解析器)
参数方法名称解析器 1.配置依赖包 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="ht ...