Symfony2创建页面实例详解


Posted in PHP onMarch 18, 2016

本文实例讲述了Symfony2创建页面的方法。分享给大家供大家参考,具体如下:

在Symfony2中创建页面只需要两步:

1、创建路由:路由定义你页面的URI(如/about)并指定要执行的控制器(PHP函数)。当传入的请求URL匹配该路由时,Symfony2将执行指定的控制器;

2、创建控制器:控制器是一个PHP函数,它接受传入的请求并将其转换成Symfony2的Response对象。

我们喜欢这样简单的实现,因为它符合Web的工作方式。每一个Web交互都是由HTTP请求开始,应用程序的任务就是简单地解释请求并返回相应的HTTP响应。Symfony2遵循这一原则,并为你提供工具,以保证在应用程序的用户和复杂性增长时仍保持良好地组织性。

“Hello Symfony2” 页

让我们从经典的“hello,world”程序开始,当我们完成后,用户可以通过访问下列URL来得到一声问候:

http://localhost/app_dev.php/hello/Symfony

其实,你可以将Symfony换成其它的名称来问候,要创建该页,我们只需简单地通过两个步骤来进行:

本教程已经假定你下载了Symfony2,并且配置好了Web服务器。上述URL假设localhost指向你新的Symfony2项目。安装详情参见安装Symfony2。

创建Bundle

在开始之前,你需要创建一个Bundle。在Symfony2中,Bundle相当于插件,你应用程序中的所有代码都需要放在Bundle中。Bundle只是一个目录(拥有PHP的名称空间),里面的内容都与某个特定功能相关(参见Bundle系统)。运行下列命令,创建AcmeStudyBundle(本章所建的游戏之作)。

php app/console Acme/StudyBundle[/]

接下来,在app/autoloader.php文件中添加了以下语句,以确保Acme名字空间被引导(参见自动加载章节):

$loader->registerNamespaces(array(
  'Acme' => __DIR__.'/../src',
  // ...
));

最后在app/AppKernel.php文件的registerBundles()方法中初始化Bundle。

// app/AppKernel.php
public function registerBundles()
{
  $bundles = array(
    // ...
    new Acme\StudyBundle\AcmeStudyBundle(),
  );
  // ...
  return $bundles;
}

现在你已经设置好你的Bundle,并可以在你Bundle中构建你的应用程序了。

创建路由

缺省状态下,Symfony2的路由配置文件放置在app/config/routing.yml目录中。在Symfony2中所有的配置文件也可以采用PHP或XML格式编写。

# app/config/routing.yml
homepage:
  pattern: /
  defaults: { _controller: FrameworkBundle:Default:index }
hello:
  resource: "@AcmeStudyBundle/Resources/config/routing.yml"

路由配置文件的前几行定义了用户请求”/”(首页)资源所调用的代码,更有趣的是最后一部分,它导入了位于AcmeStudyBundle中的其它路由配置文件。

# src/Acme/StudyBundle/Resources/config/routing.yml
hello:
  pattern: /hello/{name}
  defaults: { _controller: AcmeStudyBundle:Hello:index }

路由由两个基本部分组成,pattern(模式)确定哪个URI匹配本路由,defaults数组指定要运行的控制器。在pattern中的占位符{name}是个通配符,它表示诸如/hello/Ryan, /hello/Fabien或其他相似的URI匹配该路由。{name}占位参数也被发送到控制器,以便我们可以使用它的值去问候用户。

路由系统在创建应用程序强大、灵活的URL结构方面有着许多令人惊叹的功能,详情请参见《Symfony2学习笔记之系统路由详解》

创建控制器

当象/hello/Ryan这样的URI被应用程序处理时,hello路由被匹配,并且AcmeStudyBundle:Hello:index控制器通过Symfony2框架被执行。创建页面过程的第二步就是创建这个控制器

实际上控制器也不过是由你创建并通过Symfony2执行的PHP函数,这个定制的应用程序代码使用请求信息去构建和准备所需资源。除了一些高级案例外,控制器最终的输出都是相同的:一个Response对象。

// src/Acme/StudyBundle/Controller/HelloController.php
namespace Acme\StudyBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
  public function indexAction($name)
  {
    return new Response('<html><body>Hello '.$name.'!</body></html>');
  }
}

控制器很简单,它创建一个新的Response对象,该对象的第一个参数是它返回的响应内容(在本例中是个小小的HTML页)。

恭喜你,仅仅只是在创建了一个路由和控制器之后,你就已经得到了一个全功能页!如果你的设置没有问题的话,你的应用程序就可以跟你打招呼了:

http://localhost/app_dev.php/hello/Ryan

一个可选但却被经常用到的步骤就是创建一个模板。

在创建页面时控制器是主入口和关键部分,更多信息可以在控制器章节找到。

创建模板

模板允许你把所有的展示(如HTML代码)都放到单个文件中,并且重用页面布局的不同区块。下面代码就是使用模板来替换控制器中的HTML代码。

// src/Acme/StudyBundle/Controller/HelloController.php
namespace Acme\StudyBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class HelloController extends Controller
{
  public function indexAction($name)
  {
    return $this->render('AcmeStudyBundle:Hello:index.html.twig', array('name' => $name));
    // 渲染PHP模板
    // return $this->render('AcmeStudyBundle:Hello:index.html.php', array('name' => $name));
  }
}

为了使用render()方法,你必须继承Controller类,该类添加了一些常见任务的快捷方法。

render()方法创建一个Response对象,该对象使用特定的内容填充并通过模板渲染的。与其它控制器一样,你最终得到的是一个Response对象。

注意,这里有两种不同渲染模板的例子,缺省情况下,Symfony2支持两种渲染模板的方式:传统的PHP模板和简洁强大的Twig模板。你可以随意选择使用其中的一种,也可以在同一项目中混用它们,这都不成问题。

控制器渲染AcmeStudyBundle:Hello:index.html.twig模板,该模板使用以下命名约定:

Bundle名:Controller名:Template名

在本例中,AcmeStudyBundle是Bundle名,Hello是控制器,index.html.twig是模板名。

{# src/Acme/StudyBundle/Resources/views/Hello/index.html.twig #}
{% extends '::layout.html.twig' %}
{% block body %}
  Hello {{ name }}!
{% endblock %}

让我们一行行地来:
第2行:extends定义了一个父模板,模板明确定义了一个将被替换的布局文件;
第4行:block表示其中的内容将会替换掉名为body的block,如我们所知,它在最终渲染时将负责layout.html.twig中名为body的block的渲染。
父模板::layout.html.twig省略了它的bundle名和控制器名(所以用两个冒号::代替),这意味着该模板在bundle外面,在app目录中。

{# app/Resources/views/layout.html.twig #}
<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>{% block title %}Hello Application{% endblock %}</title>
  </head>
  <body>
    {% block body %}{% endblock %}
  </body>
</html>

基本模板文件定义了HTML布局,并用我们在index.html.twig模板中定义的名为body的区块渲染。这里还定义了一个名为title的区块,我们也可以选择在index.html.twig模板中定义。由于我们没有在子模板中定义title区块,所以它还是使用缺省值”Hello Application”。

模板在渲染和组织页面内容方面的功能非常强大,它可以是HTML标识语言、CSS代码或者控制器可能需要返回的东东。模板引擎只是达到目标的手段。每个控制器的目标是返回一个Response对象,模板虽然强大,但它却是可选的,它只是为Response对象创建内容的工具而已。

目录结构

经过前面几段的学习,你已经理解了在Symfony2中创建和渲染页面的步骤,也开始明白了Symfony2的组织和结构,在本章的最后,你将学会在哪儿找到和放置不同类型的文件以及为什么这样做。

虽然Symfony2的目录结构相当灵活,但在缺省状态下,Symfony2还是有着相同的、被推荐的基本目录结构:

app/ : 该目录包含应用程序配置;
src/ : 所有项目的PHP代码都保存在该目录下;
vendor/ : 根据约定放置所有供应商的库文件;
web/ : 这是web根目录,包括一些公众可以访问的文件。

WEB目录

web根目录是所有静态的、公共文件的家目录,包括图像、样式表和javascript文件,这里也是前端控制器所在的地方。

// web/app.php
require_once __DIR__.'/../app/bootstrap.php';
require_once __DIR__.'/../app/AppKernel.php';
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel('prod', false);
$kernel->handle(Request::createFromGlobals())->send();

前端控制器(在这里是app.php)其实是一个PHP文件,在使用Symfony2应用程序时执行。它的功能就是使用内核类AppKernel,让应用程序自举。
使用前端控制器意味着要比使用传统的纯PHP程序有着更为灵活多变的URL,当使用前端控制器时,URL格式如下所示:

http://localhost/app.php/hello/Ryan

前端控制器app.php被执行,URI(/hello/Ryan)通过路由配置被内部路由。如果使用Apache的重写规则,你可以在不指定app.php的情况下强制执行它:

http://localhost/hello/Ryan

虽然前端控制器在处理请求时必不可少,但你很少会去修改甚至想到它,我们只是在环境一章中简要地提及它。

应用程序(app)目录

正如你在前端控制器所看到的那样,AppKernel类是整个应用程序的主入口,它负责所有的配置,它被保存在app/目录中。

这个类必须实现三个方法,这些方法是Symfony2需要让应用程序了解的。你甚至在一开始就无须担心这些方法,因为Symfony2会智能地为你填充它们:

1、registerBundles(): 返回所有需要在应用程序中运行的bundle数组 (参见Bundle系统 );
2、registerContainerConfiguration(): 引导应用程序的主配置资源文件 (参见应用程序配置章节);
3、registerRootDir(): 返回app根目录 (缺省是 app/)

在日常开发中,你会经常用到app/目录,你会在app/config/目录中修改配置和路由文件(参见应用程序配置),也会使用app/cache/目录做为应用程序的缓存目录、使用app/logs/目录做为日志目录、使用app/Resources/目录做为应用程序级别的资源目录。在下面的章节中你将会学到更多关于这些目录的内容。

自动加载

当应用程序自举时,将包含一个特殊的文件:app/autoload.php。该文件负责自动加载src/和vender/目录中的所有文件。

因为有自动加载器,你永远无须为使用include或require语句担心。Symfony2利用类的名称空间确定它的位置,并自动加载包含你所需的类文件。

$loader->registerNamespaces(array(
  'Acme' => __DIR__.'/../src',
  // ...
));

在这个配置中,Symfony2将查找src/目录下Acme名称空间(假想公司的名称空间)的所有类。为了能够自动加载,Class Name文件和Path必须遵循同一模式:

Class Name:
    Acme\StudyBundle\Controller\HelloController
Path:
    src/Acme/StudyBundle/Controller/HelloController.php

app/autoload.php配置自动加载器在不同的目录查找不同的PHP名称空间,也可以在必要时自定义。有关自动加载器的更多情况,参见如何自动加载类。

源(src)目录

简而言之,src/目录包括所有在应用程序中运行的PHP代码。实际上在开发时,大部分工作都是在该目录下完成的。缺省情况下,src/目录是空的,当你开始进行开发时,你将开始填充bundle所在的目录,该目录包含你应用程序的代码。
然而bundle究竟是什么呢?

Bundle系统

Bundle与其它软件中的插件类似,但比它们更好。关键的不同点在于在Symfony2中什么都是bundle,包括框架的核心功能和你为应用程序所写的代码。在Symfony2中,Bundle是一类公民,这让使用第三方Bundle的预建功能包或发布你自己的Bundle变得十分灵活。它也可以使你很容易地选择应用程序所需功能,并用你自己的方式去优化它们。

Bundle简单来说就是在一个目录里用来实现单一功能的结构化文件集。你可以创建BlogBundle、ForumBundle或用户管理的Bundle(许多都已经以开源Bundle的形式存在)。每个目录都包含与功能相关的内容,如PHP文件、模板、样式表、Javascripts、测试等。每个Bundle都包含某种功能的方方面面,而每种功能都必须在Bundle中实现。

应用程序由在AppKernel类中的registerBundles()方法中定义的Bundle组成:

// app/AppKernel.php
public function registerBundles()
{
  $bundles = array(
    new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
    new Symfony\Bundle\SecurityBundle\SecurityBundle(),
    new Symfony\Bundle\TwigBundle\TwigBundle(),
    new Symfony\Bundle\MonologBundle\MonologBundle(),
    new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
    new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
    new Symfony\Bundle\AsseticBundle\AsseticBundle(),
    new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
    new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
    // register your bundles
    new Acme\StudyBundle\AcmeStudyBundle(),
  );
  if (in_array($this->getEnvironment(), array('dev', 'test'))) {
    $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
  }
  return $bundles;
}

通过registerBundles()方法,你就拥有了应用程序所有Bundles的全部控制权(包含Symfony2的核心Bundle)

无论Bundle在什么地方,它都可以被Symfony2自动加载。举个例子,如果AcmeStudyBundle放在src/Acme目录中,请确保Acme的名称空间被添加到app/autoload.php文件中,并映射到src/目录,这样它就可以被Symfony2自动加载了。

创建Bundle

为了向你展示Bundle系统是如何之简单,让我们创建一个名为AcmeTestBundle的新Bundle,并激活它。

首先,创建一个src/Acme/TestBundle/ 目录,并添加一个名为AcmeTestBundle.php的新文件:

// src/Acme/TestBundle/AcmeTestBundle.php
namespace Acme\TestBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeTestBundle extends Bundle
{
}

AcmeTestBundle遵循Bundle命名约定

这个空类仅仅只是我们需要创建新Bundle的一部分。虽然是空的,但这个类已经足够强大,并能够用来自定义Bundle的行为。

现在我们已经创建了我们的Bundle,我们需要通过Appkernel类激活它:

// app/AppKernel.php
public function registerBundles()
{
  $bundles = array(
    // ...
    // register your bundles
    new Acme\TestBundle\AcmeTestBundle(),
  );
  // ...
  return $bundles;
}

虽然目前它还不能做任何事情,但AcmeTestBundle现在已经可以使用了。

同样方便的是,Symfony也提供命令行接口去生成Bundle的基本框架

php app/console init:bundle "Acme\TestBundle" src

生成的Bundle框架包括一个基本控制器、模板和可自定义的路由资源。接下来我们将会讨论更多的Symfony2命令行工具。

无论何时,创建一个新的Bundle或使用第三方Bundle,都是需要确保该Bundle在registerBundles()中被激活。

Bundle的目录结构

Bundle的目录结构是简单而灵活的。缺省状态下,Bundle系统遵循Symfony2所有Bundle之间保持代码一致性的约定集。让我们看看AcmeStudyoverBundle,因为它包含了Bundle的大多数元素:

1、Controller/目录:包含该Bundle的控制器(如:HelloController.php);
2、Resources/config/目录:配置目录,包括路由配置(如:routing.yml);
3、Resources/views/目录:通过控制器名组织的模板(如:Hello/index.html.twig);
4、Resources/public/目录:包含web资源(图片、样式表等),并被拷贝或软链接到项目的web/目录;
5、Tests/目录:存放该Bundle的所有测试。

根据Bundle实现的功能,它可小可大,它只包含你所需要的文件。

你在本书中还将学习到如何持久化对象到数据库、创建和验证表单、翻译你的应用程序和编写测试等等,它们在Bundle中都有自己的位置和所扮演的角色。

应用程序配置

应用程序由代表应用程序所有功能和特征的Bundle集构成。每个Bundle都可以通过YAML、XML或PHP编写的配置文件来自定义。缺省情况下,主配置文件放置在app/config/目录中,被命名为config.yml、config.xml或config.php,这取决于你所使用的格式:

# app/config/config.yml
framework:
  charset:     UTF-8
  secret:     xxxxxxxxxx
  form:      true
  csrf_protection: true
  router:     { resource: "%kernel.root_dir%/config/routing.yml" }
  validation:   { annotations: true }
  templating:   { engines: ['twig'] } #assets_version: SomeVersionScheme
  session:
    default_locale: en
    lifetime:    3600
    auto_start:   true
# Twig Configuration
twig:
  debug:      %kernel.debug%
  strict_variables: %kernel.debug%

我们将在下一节环境中展示如何准确地选择要引导的文件/格式。

每一个顶级条目,如framework或twig都被配置成一个特定的Bundle。例如,framework被配置成Symfony2的核心FrameworkBundle,并包含路由、模板和其它核心系统的配置。

现在别担心配置文件中各段中的特定配置选项,配置文件缺省值都是合理的。当你浏览Symfony2的各部分时,你将学到每个部分的特定配置选项。

配置格式

纵观整个章节,所有的配置示例都用三种格式(YAML、XML和PHP)展示。它们每个都有自己的优缺点,以下是三种格式的说明:

1、YAML:简单、干净和易读
2、XML:有时比YAML更强大且支持IDE的自动完成
3、PHP:非常强大,但与标准配置格式相比易读性差

环境

应用程序可以在不同的环境中运行。不同的环境共享相同的PHP代码(由前端控制 器区分),但却有着完全不同的配置。开发环境记录警告和错误,而生产环境只记录错误。在开发环境中一些文件在每次请求之后被重构,而在生产环境中却被缓存 。所有的环境都在同一机制中生活。

虽然创建新的环境是容易的,但Symfony2项目通常会从三个环境开始(开发、测试和生产)。通过在你浏览器中改变前端控制器,你可以很方便地让应用程序在不同的环境中切换。要将应用程序切换到开发环境,只需要通过开发前端控制器去访问应用程序即可。

http://localhost/app_dev.php/hello/Ryan

如果你想看看你的应用程序在生产环境中的表现 ,可以调用生产前端控制器:

http://localhost/app.php/hello/Ryan

如果你打开 web/app.php文件,你将发现它已经很明确地被配置成使用生产环境:

$kernel = new AppCache(new AppKernel('prod', false));

你可以为一个新的环境创建一个新的前端控制器,只需要拷贝该文件,并将prod修改成其它值。

因为生产环境是为速度优化的,配置、路由和Twig模板都被编译成纯的PHP类,同时被缓存 。在生产环境中改变视图时,你需要清除这些缓存文件,从而让它们重构:

rm -rf app/cache/*

当进行自动测试时使用测试环境,它并不能从浏览器直接访问。参见测试章节以得到更多细节。

环境配置

AppKernel类负责加载你所选的配置文件:

// app/AppKernel.php
public function registerContainerConfiguration(LoaderInterface $loader)
{
  $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
}

我们已经知道.yml扩展名可以转换成.xml或.php,只要你喜欢使用XML或PHP来写配置。注意每种环境也可以加载它们自己的配置文件。下面是为生产环境准备的配置文件。

# app/config/config_dev.yml
imports:
  - { resource: config.yml }
framework:
  router:  { resource: "%kernel.root_dir%/config/routing_dev.yml" }
  profiler: { only_exceptions: false }
web_profiler:
  toolbar: true
  intercept_redirects: true
zend:
  logger:
    priority: debug
    path:   %kernel.logs_dir%/%kernel.environment%.log

import关键词与PHP格式中include语句一样,都是首先引导主配置文件(config.yml),文件的其它部分是为了增长的日志和其它有利于开发环境的设置而对缺省配置进行的调整。

在生产环境和测试环境都遵循同样一个模型:每个环境导入基本配置文件,然后修改它们的配置值去适应特殊环境的需要。

小结

恭喜你,你现在已经明白了Symfony2的基本原理,并惊喜地发现它是那样的方便灵活。尽管有许多的功能,但我们可以牢记以下几个基本点:

1、创建页面需要三个步骤,包括路由、控制器和模板(可选);
2、每个应用程序都应该包含四个目录:web/(web资源和前端控制器)、app/(配置)、src/(你的Bundle)和vendor/(第三方代码);
3、Symfony2的每个功能(包括Symfony2框架核心)都被组织进一个Bundle,Bundle是该功能的结构化文件集;
4、每个Bundle的配置都存放在app/config目录中,可以使用YAML、XML和PHP编写;
5、通过不同的前端控制器(如:app.php或app_dev.php)和配置文件,每种环境都可以被访问。

希望本文所述对大家基于Symfony框架的PHP程序设计有所帮助。

PHP 相关文章推荐
一个用php实现的获取URL信息的类
Jan 02 PHP
php实现从ftp服务器上下载文件树到本地电脑的程序
Feb 10 PHP
php安全开发 添加随机字符串验证,防止伪造跨站请求
Feb 14 PHP
PHP多例模式介绍
Jun 24 PHP
ThinkPHP跳转页success及error模板实例教程
Jul 17 PHP
使用php方法curl抓取AJAX异步内容思路分析及代码分享
Aug 25 PHP
PHP远程调试之XDEBUG
Dec 29 PHP
php中简单的对称加密算法实现
Jan 05 PHP
PHP实现权限管理功能示例
Sep 22 PHP
搜索附近的人PHP实现代码
Feb 11 PHP
PHP获取当前系统时间的方法小结
Oct 03 PHP
laravel excel 上传文件保存到本地服务器功能
Nov 14 PHP
symfony2.4的twig中date用法分析
Mar 18 #PHP
Symfony2之session与cookie用法小结
Mar 18 #PHP
Symfony2实现从数据库获取数据的方法小结
Mar 18 #PHP
Symfony2实现在controller中获取url的方法
Mar 18 #PHP
Symfony2框架学习笔记之表单用法详解
Mar 18 #PHP
Symfony2框架学习笔记之HTTP Cache用法详解
Mar 18 #PHP
解读PHP的Yii框架中请求与响应的处理流程
Mar 17 #PHP
You might like
php 设计模式之 单例模式
2008/12/19 PHP
PHP动态编译出现Cannot find autoconf的解决方法
2014/11/05 PHP
javascript实现上传图片并预览的效果实现代码
2011/04/11 Javascript
jquery ajax请求方式与提示用户正在处理请稍等
2014/09/01 Javascript
js实现文字跟随鼠标移动而移动的方法
2015/02/28 Javascript
jQuery实现自定义checkbox和radio样式
2015/07/13 Javascript
九种原生js动画效果
2015/11/11 Javascript
javascript检测flash插件是否被禁用的方法
2016/01/14 Javascript
Javascript HTML5 Canvas实现的一个画板
2020/04/12 Javascript
javascript self对象使用详解
2016/10/18 Javascript
canvas绘制万花筒效果(代码分享)
2017/01/20 Javascript
vue项目中使用Hbuilder打包app 设置沉浸式状态栏的方法
2018/10/22 Javascript
vue中tab选项卡的实现思路
2018/11/25 Javascript
es6 symbol的实现方法示例
2019/04/02 Javascript
Node.js折腾记一:读指定文件夹,输出该文件夹的文件树详解
2019/04/20 Javascript
微信小程序开发常见问题及解决方案
2019/07/11 Javascript
Vue中遍历数组的新方法实例详解
2019/07/21 Javascript
详解Python使用simplejson模块解析JSON的方法
2016/03/24 Python
Python xlwt设置excel单元格字体及格式
2020/04/18 Python
一个Python最简单的接口自动化框架
2018/01/02 Python
Python利用WMI实现ping命令的例子
2019/08/14 Python
美味咖啡的顶级烘焙师:Cafe Britt
2018/03/15 全球购物
设计师珠宝:Ylang 23
2018/05/11 全球购物
新西兰网上购物,折扣店:BestDeals.co.nz
2019/03/20 全球购物
拉斯维加斯城市观光通行证:Las Vegas Pass
2019/05/21 全球购物
新浪微博实习心得体会
2014/01/27 职场文书
《难忘的泼水节》教学反思
2014/02/27 职场文书
坚定理想信念心得体会
2014/03/11 职场文书
气象学专业个人求职信
2014/03/15 职场文书
聘用意向书范本
2014/04/01 职场文书
给校长的一封检讨书
2014/09/20 职场文书
2014党委书记四风对照检查材料思想汇报
2014/09/21 职场文书
2015年财务试用期工作总结
2014/12/24 职场文书
2015年全民国防教育日活动总结
2015/03/23 职场文书
Node实现搜索框进行模糊查询
2021/06/28 Javascript
sql注入教程之类型以及提交注入
2021/08/02 MySQL