symfony advent calendar day five: forms and pager ================================================= 지난 줄거리 ----------- [지난 시간](4.txt) 에는 코드를 해당 코드의 속성에 맞는 다른 파일들로 이동시키는 작업을 통해 리펙토링을 살펴보았습니다. 또한 모델의 속성에 관한 코드를 액션에서 모델로 옮기는 것도 살펴보았습니다. 지금까지 개발된 코드는 깔끔하지만 아직 더 개발해야 할 기능이 많습니다. 오늘은 askeet 사이트를 사용자와 상호작용할 수 있도록 만들어 보겠습니다. 하이퍼링크 (hyperlink) 이외에 HTML 을 사용자와 상호작용하도록 하기 위해서는 form 을 사용합니다. 오늘의 목표는 사용자가 로그인을 할 수 있도록 하고, 메인페이지의 질문 목록을 페이지를 나누어 보여주는 것입니다. 오늘은 오래 걸리지 않을 것이기 때문에, 어제의 긴 튜토리얼에 지치셨다면 걱정하지 않으셔도 좋을 것입니다. 로그인 폼 --------- 테스트 데이터들에는 사용자 정보가 포함되어 있습니다만 시스템이 이를 인식할 수 있는 방법은 사실 없습니다. 어플리케이션의 모든 페이지에서 로그인 폼을 사용할 수 있도록 하겠습니다. 글로벌 레이아웃 파일인 `askeet/apps/frontend/templates/layout.php` 파일을 열고 `about` 링크 앞에 다음을 추가합니다. [php]
>**참고**: 현재의 레이아웃은 웹 디버그 툴바 아래에 가려져 있습니다. 방금 추가한 링크를 보시기 위해서는 'Sf' 아이콘을 클릭하셔서 툴바를 닫으셔야 합니다. 이제 `user` 모듈을 만들도록 하겠습니다. 두번째날에 만들었던 `question` 모듈과는 달리, 이번에는 빈 모듈을 만들고 코드를 직접 채워넣도록 할 것입니다. $ symfony init-module frontend user >**참고**: 위 명령을 통해 `index` 액션과 `indexSuccess.php` 템플릿이 생성됩니다. 하지만 우리는 둘 모두가 필요없으므로 액션과 템플릿 파일을 지우도록 합니다. ### `user/login` 액션 만들기 `askeet/apps/frontend/modules/` 디렉토리의 `user/action/action.class.php` 파일에 `login` 액션을 추가합니다. [php] public function executeLogin() { $this->getRequest()->setAttribute('referer', $this->getRequest()->getReferer()); return sfView::SUCCESS; } 위 액션에서는 사용자 요청 값으로 레퍼러 (referer) 값을 저장하고 있습니다. 이는 로그인시 레퍼러를 폼의 숨겨진 값들 중 하나로 사용하여, 사용자가 어느 페이지에서 로그인을 하였는지를 확인하여 로그인 후 사용자를 원래 있었던 페이지로 돌려 보내기 위함입니다. `return sfView::SUCCESS` 는 액션의 결과를 `loginSuccess.php` 템플릿으로 전달합니다. 이 문장은 액션이 아무것도 반환하지 않는 경우에도 자동으로 실행됩니다. 따라서 만약 액션이 반환값없이 종료된다면, `<액션이름>Success.php` 가 자동으로 호출될 것입니다. 액션을 더 고치기 전에, 템플릿을 살펴보도록 하겠습니다. ### `loginSuccess.php` 템플릿 만들기 인간과 컴퓨터가 상호작용하기 위해서 웹에서는 폼이 사용됩니다. 심포니는 폼의 생성과 관리를 돕기 위해서 **폼 헬퍼** 를 제공합니다. `askeet/apps/frontend/modules/user/templates/` 디렉토리에서 `loginSuccess.php` 템플릿 파일을 생성합니다. [php] getAttribute('referer')) ?> 위 템플릿에서는 몇가지 기본적인 폼 헬퍼들이 사용되고 있습니다. 위 폼 헬퍼들을 통해 폼 생성을 자동화할 수 있습니다. `form_tag()` 헬퍼는 기본 매쏘드로 POST 를 사용하고, 폼 액션값으로 인자로 받은 값을 사용하는 폼을 시작합니다. `input_tag()` 헬퍼는 `` 테크를 생성하고 첫번째 인자를 `id` 값을 같도록 합니다. 두번째 인자가 있을 경우에는 이를 `` 테그의 기본값으로 설정합니다. 온라인 문서중 [폼 헬퍼 부분](http://www.symfony-project.com/book/1_0/10-Forms) 에서 폼 헬퍼 및 폼 헬퍼가 생성하는 HTML 코드들에 대해 자세히 알아보실 수 있습니다. 여기서 중요한 것은 사용자가 폼의 확인버튼을 눌렀을때, `form_tag()` 가 인자로 받은 `login` 액션이 실행될 것이라는 것입니다. 이제 액션으로 돌아가보겠습니다. ### Handle the login form submission `login` 액션을 아래와 같이 수정합니다. [php] public function executeLogin() { if ($this->getRequest()->getMethod() != sfRequest::POST) { // display the form $this->getRequest()->setAttribute('referer', $this->getRequest()->getReferer()); } else { // handle the form submission $nickname = $this->getRequestParameter('nickname'); $c = new Criteria(); $c->add(UserPeer::NICKNAME, $nickname); $user = UserPeer::doSelectOne($c); // nickname exists? if ($user) { // password is OK? if (true) { $this->getUser()->setAuthenticated(true); $this->getUser()->addCredential('subscriber'); $this->getUser()->setAttribute('subscriber_id', $user->getId(), 'subscriber'); $this->getUser()->setAttribute('nickname', $user->getNickname(), 'subscriber'); // redirect to last page return $this->redirect($this->getRequestParameter('referer', '@homepage')); } } } } 로그인 액션은 로그인 폼을 보여주는 역할도 하고, 로그인을 처리하기도 하도록 만들어졌습니다. 어떤 경우에 폼을 보여주고, 어떤 경우에 로그인을 처리해야 하는지 알기 위해서, 어떤 메소드를 통해 해당 액션이 호출되었는지를 파악합니다. 만약 액션이 POST 메쏘드로 호출되지 않았다면, 이는 하이퍼링크를 통해 호출되었다는 의미가 되고 이 경우에는 폼을 출력합니다. 만약 액션이 POST 메쏘드로 호출되었다면, 사용자가 폼의 확인 버튼을 누른 것이고 이 경우에는 로그인을 처리합니다. 액션은 사용자 요청값들 중에서 `nickname` 필드의 값이 있는지 확인하고, 이 `nickname` 값이 `User` 테이블에 존재하는지 확인합니다. 추후에는 암호를 통해서 사용자의 권한을 확인하도록 할 것입니다. 지금은 단지 사용자의 `id` 와 `nickname` 을 세션값에 저장하도록 하고 있습니다. 마지막으로, 액션은 처음 우리가 숨겨진 값으로 저장했던 `referer` 값을 사용하여 사용자를 사용자가 로그인을 호출한 URL 로 돌려보냅니다. 만약 이 값이 비어있다면, 우리가 두번째로 지정한 `@homepage`, 즉 라우팅 규칙 중 하나인 `question/list`, 이 사용될 것입니다. 위의 코드에서는 두가지 다른 속성값들이 사용되었습니다. 하나는 **사용자 요청값** 으로 (`$this->getRequest()->setAttribute()`), 이는 템플릿에서 사용하기 위해 값을 잠시 기억하는 용도로 사용됩니다. 이 값들은 페이지가 표시된 이후에는 모두 사라집니다. 또 다른 하나는 **세션 값** 으로 (`$this->getUser()->setAttribute()`) 사용자의 세션이 유지되는 동안에는 계속 보존되며, 이후 다른 액션들에서 이 값들을 사용가능하게 됩니다. 이에 대해 좀 더 알고 싶으시다면 온라인 문서들 중 [파라미터 관련 부분](http://www.symfony-project.com/book/1_0/02-Exploring-Symfony-s-Code) 을 참조하시기 바랍니다. ### 권한 설정 사용자들이 askeet 웹사이트에 로그인 할 수 있다는 것은 좋은 일이지만, 사용자들이 그냥 하지는 않겠지요? 로그인된 사용자만이 새로운 질문을 쓰거나, 질문에 대한 흥미도를 추가하거나, 또는 답변들을 평가할 수 있도록 할 계획입니다. 다른 모든 액션들은 로그인하지 않은 사용자들도 사용할 수 있도록 할 것입니다. 사용자의 권한을 설정하기 위해서는 `sfUser` 객채의 `->setAuthenticated()` 메쏘드를 호출해 주어야 합니다. 이 객체는 세밀한 권한을 설정하기 위해서 증명서 체계 (credential mechanism) 을 사용합니다. 좀 더 자세한 내용은 [사용자 증명서 부분](http://www.symfony-project.com/book/1_0/06-Inside-the-Controller-Layer) 을 참조하시기 바랍니다. 다음 두 줄을 통해 권한을 설정합니다. [php] $this->getContext()->getUser()->setAuthenticated(true); $this->getContext()->getUser()->addCredential('subscriber'); 사용자 이름이 확인 된 이후에 세션값들에 사용자의 정보만을 저장하는 것이 아니라, 사이트의 제한된 구역을 사용할 수 있는 권한을 부여합니다. 사용자의 권한에 따라 사이트의 이용을 제한하는 것에 대해서는 내일 계속해서 살펴볼 것입니다. ### `user/logout` 액션 `->setAttribute()` 메쏘드에는 우리가 설명하지 않고 넘어간 부분이 있습니다. `->setAttribute()` 메쏘드의 마지막 인자는 (위의 경우에 `subscriber`) 값들이 저장될 **이름공간** 을 정의합니다. 이름공간은 다른 여러개의 같은 이름을 갖는 값들을 저장하기 위한 것 이외에도, 해당 이름공간이 가지는 값들을 쉽게 지울 수 있게 합니다. [php] public function executeLogout() { $this->getUser()->setAuthenticated(false); $this->getUser()->clearCredentials(); $this->getUser()->getAttributeHolder()->removeNamespace('subscriber'); $this->redirect('@homepage'); } 이름공간을 사용함으로써 여러개의 값들을 하나씩 지우는 대신 하나의 명령으로 모두 지울 수가 있습니다. 게으름은 좋은 것입니다! ### 레이아웃 변경 사용자가 로그인을 한 이후에도 레이아웃을 'login' 링크를 표시하고 있습니다. 이를 고쳐보도록 하겠습니다. `askeet/apps/frontend/templates/layout.php` 파일을 열고 오늘 수정한 부분을 다음과 같이 고칩니다. [php] isAuthenticated()): ?>