> php教程 > PHP开发 > 본문

Symfony2에서 페이지 인스턴스 생성에 대한 자세한 설명

高洛峰
풀어 주다: 2016-12-26 11:49:15
원래의
1387명이 탐색했습니다.

이 기사의 예에서는 Symfony2를 사용하여 페이지를 만드는 방법을 설명합니다. 참조용으로 모든 사람과 공유하세요. 세부 사항은 다음과 같습니다.

Symfony2에서 페이지를 생성하려면 두 단계만 필요합니다.

1 경로 생성: 경로는 페이지의 URI를 정의합니다. (예: /about)을 지정하고 실행할 컨트롤러(PHP 함수)를 지정합니다. 들어오는 요청 URL이 이 경로와 일치하면 Symfony2는 지정된 컨트롤러를 실행합니다.

2. 컨트롤러 만들기: 컨트롤러는 들어오는 요청을 받아들이고 이를 Symfony2 응답 개체로 변환하는 PHP 함수입니다.

우리는 웹 작동 방식에 적합하기 때문에 이 간단한 구현을 좋아합니다. 모든 웹 상호작용은 HTTP 요청으로 시작되며, 단순히 요청을 해석하고 해당 HTTP 응답을 반환하는 것이 애플리케이션의 임무입니다. Symfony2는 이 원칙을 따르며 사용자와 복잡성이 증가함에 따라 애플리케이션이 잘 조직된 상태를 유지하도록 보장하는 도구를 제공합니다.

“Hello Symfony2” 페이지

기본 “hello, world” 프로그램부터 시작하겠습니다. 완료되면 사용자는 다음 URL을 방문하여 인사를 받을 수 있습니다.

http://localhost/app_dev.php/hello/Symfony
로그인 후 복사

실제로 Symfony를 인사말의 다른 이름으로 변경할 수 있습니다. 이 페이지를 만들려면 다음 두 가지 간단한 단계만 따르면 됩니다.

이 튜토리얼에서는 Symfony2를 다운로드하고 웹 서버를 구성했다고 가정했습니다. 위의 URL은 localhost가 새 Symfony2 프로젝트를 가리키는 것으로 가정합니다. 설치 세부사항은 Symfony2 설치를 참조하십시오.

번들 만들기

시작하기 전에 번들을 만들어야 합니다. Symfony2에서 Bundle은 플러그인과 동일하며 애플리케이션의 모든 코드는 Bundle에 배치되어야 합니다. 번들은 특정 기능과 관련된 내용을 포함하는 디렉토리(PHP 네임스페이스 포함)입니다(번들 시스템 참조). 다음 명령을 실행하여 AcmeStudyBundle(이 장에서 빌드한 게임)을 생성합니다.

php app/console Acme/StudyBundle[/]
로그인 후 복사

다음으로, Acme 네임스페이스가 부트스트랩되었는지 확인하기 위해 app/autoloader.php 파일에 다음 명령문을 추가했습니다(자동 로딩 장 참조). :

$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;
}
로그인 후 복사

이제 번들을 설정했으며 번들에서 애플리케이션을 빌드할 수 있습니다.

경로 만들기

기본적으로 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 }
로그인 후 복사

경로는 두 가지 기본 부분으로 구성됩니다. 패턴은 이 경로와 일치하는 URI를 결정하고, 기본 배열은 실행할 컨트롤러를 지정합니다. 패턴의 자리 표시자 {name}은 /hello/Ryan, /hello/Fabien 또는 기타 유사한 URI가 이 경로와 일치함을 나타내는 와일드카드입니다. {name} 자리 표시자 매개변수도 컨트롤러로 전송되므로 해당 값을 사용하여 사용자에게 인사할 수 있습니다.

라우팅 시스템에는 강력하고 유연한 애플리케이션용 URL 구조를 생성하는 데 있어 많은 놀라운 기능이 있습니다. 자세한 내용은 "Symfony2 연구 노트: 시스템 라우팅에 대한 자세한 설명"

컨트롤러 생성<🎜을 참조하세요. >

/hello/Ryan과 같은 URI가 애플리케이션에 의해 처리되면 hello 경로가 일치되고 AcmeStudyBundle:Hello:index 컨트롤러가 Symfony2 프레임워크를 통해 실행됩니다. 페이지 생성 과정의 두 번째 단계는 이 컨트롤러를 생성하는 것입니다

사실 컨트롤러는 Symfony2를 통해 생성하고 실행하는 PHP 함수에 지나지 않습니다. 모든 것을 구축하고 준비하려면 리소스가 필요합니다. 일부 고급 사례를 제외하고 컨트롤러의 최종 출력은 동일합니다: 응답 객체.

// src/Acme/StudyBundle/Controller/HelloController.php
namespace Acme\StudyBundle\Controller;
use Symfony\Component\HttpFoundation\Response;
class HelloController
{
  public function indexAction($name)
  {
    return new Response(&#39;<html><body>Hello &#39;.$name.&#39;!</body></html>&#39;);
  }
}
로그인 후 복사

컨트롤러는 매우 간단합니다. 새 응답 개체를 생성합니다. 개체의 첫 번째 매개변수는 반환되는 응답 콘텐츠입니다. 예는 작은 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(&#39;AcmeStudyBundle:Hello:index.html.twig&#39;, array(&#39;name&#39; => $name));
    // 渲染PHP模板
    // return $this->render(&#39;AcmeStudyBundle:Hello:index.html.php&#39;, array(&#39;name&#39; => $name));
  }
}
로그인 후 복사

render() 메서드를 사용하려면 몇 가지 일반적인 작업에 대한 바로 가기 메서드를 추가하는 Controller 클래스를 상속해야 합니다.

render() 메소드는 특정 콘텐츠로 채워지고 템플릿을 통해 렌더링되는 응답 객체를 생성합니다. 다른 컨트롤러와 마찬가지로 최종 결과는 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 &#39;::layout.html.twig&#39; %}
{% 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__.&#39;/../app/bootstrap.php&#39;;
require_once __DIR__.&#39;/../app/AppKernel.php&#39;;
use Symfony\Component\HttpFoundation\Request;
$kernel = new AppKernel(&#39;prod&#39;, 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(
  &#39;Acme&#39; => __DIR__.&#39;/../src&#39;,
  // ...
));
로그인 후 복사
로그인 후 복사

在这个配置中,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(&#39;dev&#39;, &#39;test&#39;))) {
    $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: [&#39;twig&#39;] } #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(&#39;prod&#39;, false));
로그인 후 복사

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

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

rm -rf app/cache/*
로그인 후 복사

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

环境配置

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

// app/AppKernel.php
public function registerContainerConfiguration(LoaderInterface $loader)
{
  $loader->load(__DIR__.&#39;/config/config_&#39;.$this->getEnvironment().&#39;.yml&#39;);
}
로그인 후 복사

我们已经知道.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程序设计有所帮助。


更多Symfony2创建页面实例详解相关文章请关注PHP中文网!


관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 추천
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!