nodejs에서 Koa는 무엇입니까?

青灯夜游
풀어 주다: 2021-10-29 15:00:12
원래의
2914명이 탐색했습니다.

koa는 Express와 유사한 Node 기반의 웹 프레임워크를 말하며, 웹 애플리케이션 및 API 개발 분야에서 더 작고, 더 표현력이 풍부하며, 더 강력한 초석이 되기 위해 노력하고 있습니다. Koa는 미들웨어를 번들로 제공하지 않지만 사용자가 서버 측 애플리케이션을 빠르고 즐겁게 작성할 수 있도록 돕는 우아한 방법 세트를 제공합니다.

nodejs에서 Koa는 무엇입니까?

이 튜토리얼의 운영 환경: windows7 시스템, nodejs 버전 12.19.0&&koa2.0, Dell G3 컴퓨터.

KoaExpress와 유사한 웹 개발 프레임워크입니다. 창립자도 동일합니다. 주요 특징은 <code>ES6 생성기 기능을 사용하고 아키텍처를 재설계한다는 것입니다. 즉, Koa의 원리와 내부 구조는 Express와 매우 유사하지만 구문과 내부 구조가 업그레이드되었습니다. Koa是一个类似于Express的Web开发框架,创始人也是同一个人。它的主要特点是,使用了ES6的Generator函数,进行了架构的重新设计。也就是说,Koa的原理和内部结构很像Express,但是语法和内部结构进行了升级。

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

官方faq有这样一个问题:”为什么koa不是Express 4.0?“,回答是这样的:”Koa与Express有很大差异,整个设计都是不同的,所以如果将Express 3.0按照这种写法升级到4.0,就意味着重写整个程序。所以,我们觉得创造一个新的库,是更合适的做法。“

1 Koa应用

一个Koa应用就是一个对象,包含了一个middleware数组,这个数组由一组Generator函数组成。这些函数负责对HTTP请求进行各种加工,比如生成缓存、指定代理、请求重定向等等。

var koa = require(&#39;koa&#39;);
var app = koa();

app.use(function *(){
  this.body = &#39;Hello World&#39;;
});

app.listen(3000);
로그인 후 복사
  • 上面代码中,变量app就是一个Koa应用。它监听3000端口,返回一个内容为Hello World的网页。
  • app.use方法用于向middleware数组添加Generator函数
  • listen方法指定监听端口,并启动当前应用。
    它实际上等同于下面的代码。
var http = require(&#39;http&#39;);
var koa = require(&#39;koa&#39;);
var app = koa();
http.createServer(app.callback()).listen(3000);
로그인 후 복사

2 中间件

Koa的中间件很像Express的中间件,也是对HTTP请求进行处理的函数,但是必须是一个Generator函数
而且,Koa的中间件是一个级联式(Cascading)的结构,也就是说,属于是层层调用,第一个中间件调用第二个中间件第二个调用第三个,以此类推。上游的中间件必须等到下游的中间件返回结果,才会继续执行,这点很像递归。
中间件通过当前应用的use方法注册。

app.use(function* (next){
  var start = new Date; // (1)
  yield next;  // (2)
  var ms = new Date - start; // (3)
  console.log(&#39;%s %s - %s&#39;, this.method, this.url, ms); // (4)
});
로그인 후 복사

上面代码中,app.use方法的参数就是中间件,它是一个Generator函数最大的特征就是function命令与参数之间,必须有一个星号。Generator函数的参数next,表示下一个中间件。
Generator函数内部使用yield命令,将程序的执行权转交给下一个中间件,即yield next,要等到下一个中间件返回结果,才会继续往下执行。

  • 上面代码中,Generator函数体内部,第一行赋值语句首先执行,开始计时,
  • 第二行yield语句将执行权交给下一个中间件,当前中间件就暂停执行
  • 等到后面的中间件全部执行完成,执行权就回到原来暂停的地方,继续往下执行,这时才会执行第三行,
  • 计算这个过程一共花了多少时间,第四行将这个时间打印出来。
    下面是一个两个中间件级联的例子。
app.use(function *() {
  this.body = "header\n";
  yield saveResults.call(this);
  this.body += "footer\n";
});

function *saveResults() {
  this.body += "Results Saved!\n";
}
로그인 후 복사

上面代码中,第一个中间件调用第二个中间件saveResults,它们都向this.body写入内容。最后,this.body的输出如下。

header
Results Saved!
footer
로그인 후 복사

只要有一个中间件缺少yield next语句,后面的中间件都不会执行,这一点要引起注意。

app.use(function *(next){
  console.log(&#39;>> one&#39;);
  yield next;
  console.log(&#39;<< one&#39;);
});

app.use(function *(next){
  console.log(&#39;>> two&#39;);
  this.body = &#39;two&#39;;
  console.log(&#39;<< two&#39;);
});

app.use(function *(next){
  console.log(&#39;>> three&#39;);
  yield next;
  console.log(&#39;<< three&#39;);
});
로그인 후 복사

上面代码中,因为第二个中间件少了yield next语句,第三个中间件并不会执行。
如果想跳过一个中间件,可以直接在该中间件的第一行语句写上return yield next。

app.use(function* (next) {
  if (skip) return yield next;
})
로그인 후 복사

由于Koa要求中间件唯一的参数就是next,导致如果要传入其他参数,必须另外写一个返回Generator函数Koa는 Express를 개발한 사람들이 만든 새로운 웹 프레임워크로, 웹 애플리케이션과 API 개발의 더 작고 표현력이 풍부하며 강력한 초석이 되기 위해 노력하고 있습니다. Koa는 비동기 기능을 활용하여 콜백 기능을 폐기하고 오류 처리를 크게 향상시킵니다. Koa는 미들웨어를 번들로 제공하지 않지만, 서버 측 애플리케이션을 빠르고 즐겁게 작성하는 데 도움이 되는 우아한 방법 세트를 제공합니다.

공식 faq에 "koa는 Express 4.0이 아닌 이유는 무엇입니까?"라는 질문이 있고, 대답은 다음과 같습니다. "Koa는 Express와 매우 다르며 전체 디자인도 다릅니다. 따라서 이런 식으로 Express 3.0을 4.0으로 업그레이드한다면 전체 프로그램을 다시 작성한다는 의미이므로 새로운 라이브러리를 만드는 것이 더 적절하다고 생각합니다. "

1 Koa 애플리케이션 h2 >🎜Koa 애플리케이션생성기 함수 세트로 구성된 미들웨어 배열을 포함하는 객체입니다. 코드> >구성. 이러한 기능은 캐시 생성, 프록시 지정, 요청 리디렉션 등과 같은 다양한 HTTP 요청 처리를 담당합니다. 🎜
function logger(format) {
  return function *(next){
    var str = format
      .replace(&#39;:method&#39;, this.method)
      .replace(&#39;:url&#39;, this.url);

    console.log(str);

    yield next;
  }
}
app.use(logger(&#39;:method :url&#39;));
로그인 후 복사
  • 위 코드에서 변수 app은 Koa 애플리케이션입니다. 포트 3000에서 수신 대기하고 Hello World 콘텐츠가 포함된 웹페이지를 반환합니다.
  • app.use 메소드는 미들웨어 배열에 Generator 함수를 추가하는 데 사용됩니다.
  • listen 메소드는 수신 포트를 지정하고 현재 애플리케이션을 시작합니다.
    실제로는 아래 코드와 동일합니다.
function *random(next) {
  if (&#39;/random&#39; == this.path) {
    this.body = Math.floor(Math.random()*10);
  } else {
    yield next;
  }
};

function *backwards(next) {
  if (&#39;/backwards&#39; == this.path) {
    this.body = &#39;sdrawkcab&#39;;
  } else {
    yield next;
  }
}

function *pi(next) {
  if (&#39;/pi&#39; == this.path) {
    this.body = String(Math.PI);
  } else {
    yield next;
  }
}

function *all(next) {
  yield random.call(this, backwards.call(this, pi.call(this, next)));
}
app.use(all);
로그인 후 복사
로그인 후 복사

2 미들웨어

🎜Koa의 미들웨어는 Express의 미들웨어와 매우 유사하며 right HTTP 요청을 처리하는 함수이지만 생성기 함수여야 합니다.
게다가 Koa의 미들웨어는 Cascading 구조, 즉 레이어별로 호출됩니다. 첫 번째 미들웨어가 두 번째 미들웨어를 호출합니다. code>두 번째가 세 번째를 호출하는 식입니다. 업스트림 미들웨어는 실행을 계속하기 전에 다운스트림 미들웨어가 결과를 반환할 때까지 기다려야 합니다. 이는 재귀와 매우 유사합니다.
미들웨어는 현재 애플리케이션의 use 메소드를 통해 등록됩니다. 🎜
function compose(middleware){
  return function *(next){
    if (!next) next = noop();

    var i = middleware.length;

    while (i--) {
      next = middleware[i].call(this, next);
    }

    yield *next;
  }
}

function *noop(){}
로그인 후 복사
로그인 후 복사
🎜위 코드에서 app.use 메소드의 매개변수는 Generator 함수인 미들웨어입니다. 가장 큰 특징은 입니다. >기능 명령< /code>와 매개변수 사이에 <code>에는 별표🎜가 있어야 합니다. Generator 함수 next의 매개변수는 다음 미들웨어를 나타냅니다.
Generator 함수는 내부적으로 yield 명령을 사용하여 프로그램의 실행권한을 다음 미들웨어, 즉 yield next에 넘겨줍니다. >. 다음 미들웨어가 결과를 반환할 때만 실행이 계속됩니다. 🎜
  • 위 코드에서 Generator 함수 본문 내부에서는 할당문의 첫 번째 줄이 먼저 실행되고 타이밍이 시작됩니다.
  • 두 번째 줄 < code>yield code> 문은 다음 미들웨어에 실행권을 넘기고, 현재 미들웨어는 실행을 일시 중지합니다.
  • 이후 미들웨어가 모두 실행되면 실행권은 다음 미들웨어로 반환됩니다.
  • 이 프로세스에 소요된 총 시간을 계산하면 이번에는 네 번째 줄이 인쇄됩니다.
    다음은 두 개의 미들웨어 캐스케이드의 예입니다.
app.use(function* (next) {
  if (this.path === &#39;/&#39;) {
    this.body = &#39;we are at home!&#39;;
  }
})

// 等同于

app.use(function* (next) {
  if (this.path !== &#39;/&#39;) return yield next;
  this.body = &#39;we are at home!&#39;;
})
로그인 후 복사
로그인 후 복사
🎜위 코드에서 첫 번째 미들웨어는 두 번째 미들웨어 saveResults를 호출하고 둘 다 this.body에 콘텐츠를 씁니다. 마지막으로 this.body의 출력은 다음과 같습니다. 🎜
let koa = require(&#39;koa&#39;)

let app = koa()

// normal route
app.use(function* (next) {
  if (this.path !== &#39;/&#39;) {
    return yield next
  }

  this.body = &#39;hello world&#39;
});

// /404 route
app.use(function* (next) {
  if (this.path !== &#39;/404&#39;) {
    return yield next;
  }

  this.body = &#39;page not found&#39;
});

// /500 route
app.use(function* (next) {
  if (this.path !== &#39;/500&#39;) {
    return yield next;
  }

  this.body = &#39;internal server error&#39;
});

app.listen(8080)
로그인 후 복사
로그인 후 복사
🎜한 미들웨어에 yield next 문이 없으면 후속 미들웨어는 실행되지 않습니다. 🎜
var app = require(&#39;koa&#39;)();
var Router = require(&#39;koa-router&#39;);

var myRouter = new Router();

myRouter.get(&#39;/&#39;, function *(next) {
  this.response.body = &#39;Hello World!&#39;;
});

app.use(myRouter.routes());

app.listen(3000);
로그인 후 복사
로그인 후 복사
🎜위 코드에서는 두 번째 미들웨어에 yield next 문이 없기 때문에 세 번째 미들웨어는 실행되지 않습니다.
미들웨어를 건너뛰려면 미들웨어 첫 번째 줄 다음에 return Yield를 직접 입력하면 됩니다. 🎜
router.get(&#39;/&#39;, function *(next) {
  this.body = &#39;Hello World!&#39;;
});
로그인 후 복사
로그인 후 복사
🎜Koa에서는 미들웨어의 유일한 매개변수가 다음 매개변수여야 하므로, 다른 매개변수를 전달하려면 Generator 함수를 반환하는 다른 함수를 작성해야 합니다. 코드>. 🎜🎜
router.get(&#39;user&#39;, &#39;/users/:id&#39;, function *(next) {
 // ...
});
로그인 후 복사
로그인 후 복사
🎜위 코드에서 실제 미들웨어는 로거 함수의 반환 값이며 로거 함수는 매개변수를 받을 수 있습니다. 🎜

3 多个中间件的合并

由于中间件的参数统一为next(意为下一个中间件),因此可以使用.call(this, next),将多个中间件进行合并。

function *random(next) {
  if (&#39;/random&#39; == this.path) {
    this.body = Math.floor(Math.random()*10);
  } else {
    yield next;
  }
};

function *backwards(next) {
  if (&#39;/backwards&#39; == this.path) {
    this.body = &#39;sdrawkcab&#39;;
  } else {
    yield next;
  }
}

function *pi(next) {
  if (&#39;/pi&#39; == this.path) {
    this.body = String(Math.PI);
  } else {
    yield next;
  }
}

function *all(next) {
  yield random.call(this, backwards.call(this, pi.call(this, next)));
}
app.use(all);
로그인 후 복사
로그인 후 복사

上面代码中,中间件all内部,就是依次调用random、backwards、pi,后一个中间件就是前一个中间件的参数。
Koa内部使用koa-compose模块,进行同样的操作,下面是它的源码。

function compose(middleware){
  return function *(next){
    if (!next) next = noop();

    var i = middleware.length;

    while (i--) {
      next = middleware[i].call(this, next);
    }

    yield *next;
  }
}

function *noop(){}
로그인 후 복사
로그인 후 복사

上面代码中,middleware是中间件数组。前一个中间件的参数是后一个中间件,依次类推。如果最后一个中间件没有next参数,则传入一个空函数。

4 路由

可以通过this.path属性,判断用户请求的路径,从而起到路由作用。

app.use(function* (next) {
  if (this.path === &#39;/&#39;) {
    this.body = &#39;we are at home!&#39;;
  }
})

// 等同于

app.use(function* (next) {
  if (this.path !== &#39;/&#39;) return yield next;
  this.body = &#39;we are at home!&#39;;
})
로그인 후 복사
로그인 후 복사

下面是多路径的例子。

let koa = require(&#39;koa&#39;)

let app = koa()

// normal route
app.use(function* (next) {
  if (this.path !== &#39;/&#39;) {
    return yield next
  }

  this.body = &#39;hello world&#39;
});

// /404 route
app.use(function* (next) {
  if (this.path !== &#39;/404&#39;) {
    return yield next;
  }

  this.body = &#39;page not found&#39;
});

// /500 route
app.use(function* (next) {
  if (this.path !== &#39;/500&#39;) {
    return yield next;
  }

  this.body = &#39;internal server error&#39;
});

app.listen(8080)
로그인 후 복사
로그인 후 복사

上面代码中,每一个中间件负责一个路径,如果路径不符合,就传递给下一个中间件。
复杂的路由需要安装koa-router插件。

var app = require(&#39;koa&#39;)();
var Router = require(&#39;koa-router&#39;);

var myRouter = new Router();

myRouter.get(&#39;/&#39;, function *(next) {
  this.response.body = &#39;Hello World!&#39;;
});

app.use(myRouter.routes());

app.listen(3000);
로그인 후 복사
로그인 후 복사

上面代码对根路径设置路由。
Koa-router实例提供一系列动词方法,即一种HTTP动词对应一种方法。典型的动词方法有以下五种。

  • router.get()
  • router.post()
  • router.put()
  • router.del()
  • router.patch()
    这些动词方法可以接受两个参数,第一个是路径模式,第二个是对应的控制器方法(中间件),定义用户请求该路径时服务器行为。
router.get(&#39;/&#39;, function *(next) {
  this.body = &#39;Hello World!&#39;;
});
로그인 후 복사
로그인 후 복사

上面代码中,router.get方法的第一个参数是根路径,第二个参数是对应的函数方法。
注意,路径匹配的时候,不会把查询字符串考虑在内。比如,/index?param=xyz匹配路径/index。
有些路径模式比较复杂,Koa-router允许为路径模式起别名。
起名时,别名要添加为动词方法的第一个参数,这时动词方法变成接受三个参数。

router.get(&#39;user&#39;, &#39;/users/:id&#39;, function *(next) {
 // ...
});
로그인 후 복사
로그인 후 복사

上面代码中,路径模式\users\:id的名字就是user。路径的名称,可以用来引用对应的具体路径,比如url方法可以根据路径名称,结合给定的参数,生成具体的路径。

router.url(&#39;user&#39;, 3);
// => "/users/3"

router.url(&#39;user&#39;, { id: 3 });
// => "/users/3"
로그인 후 복사

上面代码中,user就是路径模式的名称,对应具体路径/users/:id。url方法的第二个参数3,表示给定id的值是3,因此最后生成的路径是/users/3。
Koa-router允许为路径统一添加前缀。

var router = new Router({
  prefix: &#39;/users&#39;
});

router.get(&#39;/&#39;, ...); // 等同于"/users"
router.get(&#39;/:id&#39;, ...); // 等同于"/users/:id"
로그인 후 복사

路径的参数通过this.params属性获取,该属性返回一个对象,所有路径参数都是该对象的成员。

// 访问 /programming/how-to-node
router.get(&#39;/:category/:title&#39;, function *(next) {
  console.log(this.params);
  // => { category: &#39;programming&#39;, title: &#39;how-to-node&#39; }
});
param方法可以针对命名参数,设置验证条件。

router
  .get(&#39;/users/:user&#39;, function *(next) {
    this.body = this.user;
  })
  .param(&#39;user&#39;, function *(id, next) {
    var users = [ &#39;0号用户&#39;, &#39;1号用户&#39;, &#39;2号用户&#39;];
    this.user = users[id];
    if (!this.user) return this.status = 404;
    yield next;
  })
로그인 후 복사

上面代码中,如果/users/:user的参数user对应的不是有效用户(比如访问/users/3),param方法注册的中间件会查到,就会返回404错误。
redirect方法会将某个路径的请求,重定向到另一个路径,并返回301状态码。

router.redirect(&#39;/login&#39;, &#39;sign-in&#39;);

// 等同于
router.all(&#39;/login&#39;, function *() {
  this.redirect(&#39;/sign-in&#39;);
  this.status = 301;
});
로그인 후 복사

redirect方法的第一个参数是请求来源,第二个参数是目的地,两者都可以用路径模式的别名代替。

5 context对象

  • 中间件当中的this表示上下文对象context,代表一次HTTP请求和回应,即一次访问/回应的所有信息,都可以从上下文对象获得。
  • context对象封装了request和response对象,并且提供了一些辅助方法。每次HTTP请求,就会创建一个新的context对象。
app.use(function *(){
  this; // is the Context
  this.request; // is a koa Request
  this.response; // is a koa Response
});
로그인 후 복사

context对象的很多方法,其实是定义在ctx.request对象或ctx.response对象上面
比如,ctx.typectx.length对应于ctx.response.typectx.response.length,ctx.path和ctx.method对应于ctx.request.path和ctx.request.method。
context对象的全局属性。

  • request:指向Request对象
  • response:指向Response对象
  • req:指向Node的request对象
  • req:指向Node的response对象
  • app:指向App对象
  • state:用于在中间件传递信息。
this.state.user = yield User.find(id);
로그인 후 복사

上面代码中,user属性存放在this.state对象上面,可以被另一个中间件读取。
context对象的全局方法。

  • throw():抛出错误,直接决定了HTTP回应的状态码。
  • assert():如果一个表达式为false,则抛出一个错误。
this.throw(403);
this.throw(&#39;name required&#39;, 400);
this.throw(&#39;something exploded&#39;);

this.throw(400, &#39;name required&#39;);
// 等同于
var err = new Error(&#39;name required&#39;);
err.status = 400;
throw err;
로그인 후 복사

6 错误处理机制

Koa提供内置的错误处理机制,任何中间件抛出的错误都会被捕捉到,引发向客户端返回一个500错误,而不会导致进程停止,因此也就不需要forever这样的模块重启进程。

app.use(function *() {
  throw new Error();
});
로그인 후 복사

上面代码中,中间件内部抛出一个错误,并不会导致Koa应用挂掉。Koa内置的错误处理机制,会捕捉到这个错误。
当然,也可以额外部署自己的错误处理机制。

app.use(function *() {
  try {
    yield saveResults();
  } catch (err) {
    this.throw(400, &#39;数据无效&#39;);
  }
});
로그인 후 복사

上面代码自行部署了try...catch代码块,一旦产生错误,就用this.throw方法抛出。该方法可以将指定的状态码和错误信息,返回给客户端。
对于未捕获错误,可以设置error事件的监听函数。

app.on(&#39;error&#39;, function(err){
  log.error(&#39;server error&#39;, err);
});
로그인 후 복사

error事件的监听函数还可以接受上下文对象,作为第二个参数。

app.on(&#39;error&#39;, function(err, ctx){
  log.error(&#39;server error&#39;, err, ctx);
});
로그인 후 복사

如果一个错误没有被捕获,koa会向客户端返回一个500错误“Internal Server Error”。
this.throw方法用于向客户端抛出一个错误。

this.throw(403);
this.throw(&#39;name required&#39;, 400);
this.throw(400, &#39;name required&#39;);
this.throw(&#39;something exploded&#39;);

this.throw(&#39;name required&#39;, 400)
// 等同于
var err = new Error(&#39;name required&#39;);
err.status = 400;
throw err;
this.throw方法的两个参数,一个是错误码,另一个是报错信息。如果省略状态码,默认是500错误。

this.assert方法用于在中间件之中断言,用法类似于Node的assert模块。

this.assert(this.user, 401, &#39;User not found. Please login!&#39;);
로그인 후 복사

上面代码中,如果this.user属性不存在,会抛出一个401错误。
由于中间件是层级式调用,所以可以把try { yield next }当成第一个中间件。

app.use(function *(next) {
  try {
    yield next;
  } catch (err) {
    this.status = err.status || 500;
    this.body = err.message;
    this.app.emit(&#39;error&#39;, err, this);
  }
});

app.use(function *(next) {
  throw new Error(&#39;some error&#39;);
})
로그인 후 복사

7 cookie

cookie的读取和设置。

this.cookies.get(&#39;view&#39;);
this.cookies.set(&#39;view&#39;, n);
로그인 후 복사

get和set方法都可以接受第三个参数,表示配置参数。其中的signed参数,用于指定cookie是否加密。
如果指定加密的话,必须用app.keys指定加密短语。

app.keys = [&#39;secret1&#39;, &#39;secret2&#39;];
this.cookies.set(&#39;name&#39;, &#39;张三&#39;, { signed: true });
로그인 후 복사

this.cookie的配置对象的属性如下。

  • signed:cookie是否加密。
  • expires:cookie何时过期
  • path:cookie的路径,默认是“/”。
  • domain:cookie的域名。
  • secure:cookie是否只有https请求下才发送。
  • httpOnly:是否只有服务器可以取到cookie,默认为true。

8 session

var session = require(&#39;koa-session&#39;);
var koa = require(&#39;koa&#39;);
var app = koa();
app.keys = [&#39;some secret hurr&#39;];
app.use(session(app));

app.use(function *(){
  var n = this.session.views || 0;
  this.session.views = ++n;
  this.body = n + &#39; views&#39;;
})

app.listen(3000);
console.log(&#39;listening on port 3000&#39;);
로그인 후 복사

【推荐学习:《nodejs 教程》】

위 내용은 nodejs에서 Koa는 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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