services.yml
services: gedmo.listener.timestampable: class: Gedmo\Timestampable\TimestampableListener tags: - { name: doctrine.event_subscriber, connection: default } calls: - [ setAnnotationReader, [ @annotation_reader ] ]
こんなんで詰まるなんて。
services.yml
services: gedmo.listener.timestampable: class: Gedmo\Timestampable\TimestampableListener tags: - { name: doctrine.event_subscriber, connection: default } calls: - [ setAnnotationReader, [ @annotation_reader ] ]
こんなんで詰まるなんて。
またかよ。って感じだけども。
下記もymlで定義してあげないとだめっぽい。
doctrine: orm: auto_generate_proxy_classes: %kernel.debug% entity_managers: default: auto_mapping: true mappings: StofDoctrineExtensionsBundle: ~ filters: softdeleteable: class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter enabled: true stof_doctrine_extensions: default_locale: ja_JP orm: default: #timestampもやるならココ timestampable: true softdeleteable: true
雰囲気こんな感じ。
/** * @Route("/media/{id}.{_format}", name="hoge_fuga_admin_media_show", defaults={"_format" = "json"}) * @View(serializerEnableMaxDepthChecks=true, serializerGroups={"adminShow"}) * @ParamConverter("entity", class="HogeFugaBundle:Image") * @Method("GET") */ public function showAction(Image $entity, Request $request, $id) { $path = $this->getStragePath($entity->getPath()).'/'.$entity->getPath(); $finfo = new \finfo(FILEINFO_MIME_TYPE); $response = new Response(); $response->headers->set('Content-type', $finfo->file($path)); $response->sendHeaders(); $response->setContent(readfile($path)); return $response; }
ちなみにgetStragePath()の実装はこんな感じ。
※upload_root_dirをparameters.ymlで定義しとく。
/** * create dir and get file path by file name * * @param unknown $fileName * @throws \Symfony\Component\HttpFoundation\File\Exception\UploadException * @return string */ private function getStragePath($fileName) { $fs = new Filesystem(); $path = substr($fileName, 0, 1).'/'.substr($fileName, 0, 2); $dir = $this->get('kernel')->getRootDir().$this->container->getParameter('upload_root_dir').'/'.$path; if (!$fs->exists($dir)) { try { $fs->mkdir($dir, 0777); } catch (IOException $e) { throw new \Symfony\Component\HttpFoundation\File\Exception\UploadException('Cannot create directory.'); } } return $dir; }
ちなみにImageエンティティはこんな感じ。
namespace Hoge\FugaBundle\Entity; use Doctrine\ORM\Mapping as ORM; use JMS\Serializer\Annotation as JMS; use Gedmo\Mapping\Annotation as Gedmo; /** * Image * * @ORM\Table(name="images") * @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false) * @ORM\Entity(repositoryClass="Hoge\FugaBundle\Repository\ImageRepository") */ class Image { /** * @var integer * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") * @JMS\Groups({"adminIndex", "adminShow"}) */ private $id; /** * @var string * * @ORM\Column(name="name", type="string", length=255) * @JMS\Groups({"adminIndex", "adminShow"}) */ private $name; /** * @var string * * @ORM\Column(name="path", type="string", length=255) * @JMS\Groups({"adminIndex", "adminShow"}) */ private $path; /** * @var integer * * @ORM\Column(name="size", type="integer") * @JMS\Groups({"adminIndex", "adminShow"}) */ private $size; /** * Get id * * @return integer */ public function getId() { return $this->id; } /** * Set name * * @param string $name * @return Image */ public function setName($name) { $this->name = $name; return $this; } /** * Get name * * @return string */ public function getName() { return $this->name; } /** * Set path * * @param string $path * @return Image */ public function setPath($path) { $this->path = $path; return $this; } /** * Get path * * @return string */ public function getPath() { return $this->path; } /** * Set size * * @param integer $size * @return Image */ public function setSize($size) { $this->size = $size; return $this; } /** * Get size * * @return integer */ public function getSize() { return $this->size; } }
自分用。
namespaceはお好きに。(てか任意の位置に)
Gedmo\Timestampableを使っているので各エンティティからuseすること。
namespace Hoge\FugaBundle\Traits; trait EntityCommonProperties { /** * @var smallint * * @ORM\Column(name="status", type="smallint", options={"unsigned"=true}) */ protected $status = 1; /** * @var \DateTime * * @ORM\Column(name="created_at", type="datetime") * @Gedmo\Timestampable(on="create") */ protected $createdAt; /** * @var \DateTime * * @Gedmo\Timestampable(on="update") * @ORM\Column(name="updated_at", type="datetime", nullable=true) */ protected $updatedAt; /** * @var \DateTime * * @ORM\Column(name="deleted_at", type="datetime", nullable=true) */ protected $deletedAt; /** * @var integer * * @ORM\Column(name="created_uid", type="integer", nullable=true) */ protected $createdUid; /** * @var integer * * @ORM\Column(name="updated_uid", type="integer", nullable=true) */ protected $updatedUid; /** * @var integer * * @ORM\Column(name="deleted_uid", type="integer", nullable=true) */ protected $deletedUid; /** * Set status * * @param integer $status */ public function setStatus($status) { $this->status = $status; return $this; } /** * Get status * */ public function getStatus() { return $this->status; } /** * Set createdAt * * @param \DateTime $createdAt */ public function setCreatedAt($createdAt) { $this->createdAt = $createdAt; return $this; } /** * Get createdAt * */ public function getCreatedAt() { return $this->createdAt; } /** * Set updatedAt * * @param \DateTime $updatedAt */ public function setUpdatedAt($updatedAt) { $this->updatedAt = $updatedAt; return $this; } /** * Get updatedAt * */ public function getUpdatedAt() { return $this->updatedAt; } /** * Set deletedAt * * @param \DateTime $deletedAt */ public function setDeletedAt($deletedAt) { $this->deletedAt = $deletedAt; return $this; } /** * Get deletedAt * */ public function getDeletedAt() { return $this->deletedAt; } /** * Set createdUid * * @param integer $createdUid */ public function setCreatedUid($createdUid) { $this->createdUid = $createdUid; return $this; } /** * Get createdUid * */ public function getCreatedUid() { return $this->createdUid; } /** * Set updatedUid * * @param integer $updatedUid */ public function setUpdatedUid($updatedUid) { $this->updatedUid = $updatedUid; return $this; } /** * Get updatedUid * */ public function getUpdatedUid() { return $this->updatedUid; } /** * Set deletedUid * * @param integer $deletedUid */ public function setDeletedUid($deletedUid) { $this->deletedUid = $deletedUid; return $this; } /** * Get deletedUid * */ public function getDeletedUid() { return $this->deletedUid; } }
エンティティクラスのアノテーションで下記のような感じで指定してやればOK。
例)
deletedAtカラムを論理削除管理カラムとして使用する場合。
use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; /** * User * * @ORM\Table(name="users") * @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false) */ class User { }
... "require": { ... "jms/di-extra-bundle": "1.4.*@dev", "friendsofsymfony/user-bundle": "2.0.*@dev", "friendsofsymfony/rest-bundle": "1.5.*@dev", "stof/doctrine-extensions-bundle": "1.2.*@dev", "doctrine/doctrine-fixtures-bundle": "2.2.*@dev", "doctrine/migrations": "1.0.*@dev", "doctrine/doctrine-migrations-bundle": "2.1.*@dev", "jms/serializer-bundle": "0.13.*@dev", "knplabs/knp-paginator-bundle": "2.4.*@dev" }, ...
説明が皆無なのでかなり不親切です。超絶自分用まとめ。もしくはある程度解る人用。
「/」以下をフロント、「/admin」以下を管理画面(要ログイン)とする、よくあるアプリケーションの構築テンプレート。
□□サーバーサイド
■SecurityController.php
namespace Hoge\FugaBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Hoge\FugaBundle\Entity\User; /** * @Route("api") */ class SecurityController extends Controller { protected function getUserManager() { return $this->get('fos_user.user_manager'); } protected function loginUser(User $user) { $security = $this->get('security.context'); $providerKey = $this->container->getParameter('fos_user.firewall_name'); $roles = $user->getRoles(); $token = new UsernamePasswordToken($user, null, $providerKey, $roles); $security->setToken($token); } protected function logoutUser() { $security = $this->get('security.context'); $token = new AnonymousToken(null, new User()); $security->setToken($token); $this->get('session')->invalidate(); } protected function checkUser() { $security = $this->get('security.context'); if ($token = $security->getToken()) { $user = $token->getUser(); if ($user instanceof User) { return $user; } } return false; } protected function checkUserPassword(User $user, $password) { $factory = $this->get('security.encoder_factory'); $encoder = $factory->getEncoder($user); if(!$encoder){ return false; } return $encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt()); } /** * @Route("/login.{_format}", name="hoge_fuga_admin_api_security_login", defaults={"_format" = "json"}) * @Method("POST") */ public function loginAction() { $request = $this->getRequest(); $username = $request->get('username'); $password = $request->get('password'); $um = $this->getUserManager(); $user = $um->findUserByUsername($username); if(!$user){ $user = $um->findUserByEmail($username); } if(!$user instanceof User){ throw new NotFoundHttpException('ユーザーIDが存在しません'); } if(!$this->checkUserPassword($user, $password)){ throw new AccessDeniedException('パスワードが不正です'); } $this->loginUser($user); return [ 'success' => true, 'user' => $user ]; } /** * @Route("/logout.{_format}", name="hoge_fuga_admin_api_security_logout", defaults={"_format" = "json"}) * @Method("POST") */ public function logoutAction() { $this->logoutUser(); return [ 'success' => true ]; } /** * @Route("/loginCheck.{_format}", name="hoge_fuga_admin_api_security_login_check", defaults={"_format" = "json"}) * @Method("POST") */ public function loginCheckAction() { if ($user = $this->checkUser()) { return [ 'success' => true, 'user' => $user ]; } else { throw new AccessDeniedException(); } } }
■app/config/config.yml
fos_user: db_driver: orm firewall_name: admin_area user_class: Hoge\FugaBundle\Entity\User fos_rest: view: view_response_listener: force force_redirects: html: true formats: jsonp: true json: true xml: true rss: false failed_validation: HTTP_BAD_REQUEST default_engine: twig
■app/config/security.yml
security: encoders: Symfony\Component\Security\Core\User\User: plaintext FOS\UserBundle\Model\UserInterface: sha512 role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ] providers: in_memory: memory: users: user: { password: userpass, roles: [ 'ROLE_USER' ] } admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] } fos_userbundle: id: fos_user.user_provider.username_email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false admin_area: pattern: ^/admin anonymous: ~ form_login: login_path: /admin/login check_path: /admin/login_check access_control: - { path: ^/admin/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/admin/api/login.*$, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/admin, roles: [ ROLE_ADMIN ] }
■app/config/routing.yml
※FOSUserBundleのインストールに必要
fos_user: resource: "@FOSUserBundle/Resources/config/routing/all.xml"
■src/Hoge/FugaBundle/Resources/config/routing.yml
hoge_fuga_admin_index: resource: "@HogeFugaBundle/Controller/DefaultController.php" type: annotation prefix: /admin hoge_fuga_admin_security: resource: "@HogeFugaBundle/Controller/SecurityController.php" type: annotation prefix: /admin
■DefaultController.php
namespace Hoge\FugaBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; class DefaultController extends Controller { /** * @Route("") * @Route("/{_all}", name="affiliate_cms_admin_index_all", requirements={"_all" = "^(?!.*(api)).*$"}) * @Method("GET") */ public function indexAction() { return $this->render('HogeFugaBundle:Default:index.html.twig'); } }
「api」を含まないURLを全てDefaultControllerのIndexActionで拾い、あとはAngularのルーターに任せる。
□□フロントサイド
■adminLoginController.js(ログイン画面で用いるコントローラー)
void function() { var adminLoginController = function(securityResource, $state, UserService) { var that = this; this.data = {}; this.loginFailure = false; this.login = function() { securityResource.save({action: 'login.json'}, { username: that.data.username, password: that.data.password }, function(response) { UserService.setUser(response.user); $state.go('admin.main.dashboard'); }, function(response) { if (response.status >= 400) { that.loginFailure = true; } }); } } angular .module('hogeControllers') .controller('adminLoginController', ['securityResource', '$state', 'UserService', adminLoginController]) ; }();
■adminController.js(ログイン画面を含むログイン後の全てのコントローラーが継承している基底コントローラー)
void function() { var adminController = function(securityResource, $state, UserService) { this.user = null; var that = this; this.logout = function() { securityResource.save({action: 'logout.json'}, {}, function() { UserService.clear(); $state.go('admin.login'); }); } this.initUser = function() { that.user = UserService.getUser(); if (!that.user) { securityResource.save({action: 'loginCheck.json'}, {}, function(response) { that.user = response.user; }, function() { UserService.clear(); $state.go('admin.login'); }); } } } angular .module('hogeControllers') .controller('adminController', ['securityResource', '$state', 'UserService', adminController]) ; }();
■SecurityResource.js
void function() { angular .module('hogeResources') .factory('securityResource', ['$resource', function($resource) { return $resource('/admin/api/:action', { action: '@action', }, { } )}]) ; }();
■UserService.js
void function() { var UserService = function() { this.user = null; this.getUser = function() { return this.user; } this.setUser = function(user) { this.user = user; } this.clear = function() { this.user = null; } } angular .module('hogeServices') .service('UserService', [UserService]) ; }();
■app.js
void function() { angular.module('hogeApp', [ // コア 'ngAnimate', 'ngResource', 'ngSanitize', 'ngTouch', // 必要に応じて 'ui', 'ui.utils', 'ui.router', 'ui.bootstrap', 'ui.bootstrap.datetimepicker', 'kendo.directives', 'ngCookies', 'angular-loading-bar', 'angularFileUpload', 'cgNotify', // 自分のやつら 'hogeConstants', 'hogeControllers', 'hogeDirectives', 'hogeFactories', 'hogeFilters', 'hogeResources', 'hogeServices' ]); angular.module('hogeConstants', []); angular.module('hogeControllers', []); angular.module('hogeDirectives', []); angular.module('hogeFactories', []); angular.module('hogeFilters', []); angular.module('hogeResources', []); angular.module('hogeServices', []); /** * config section */ angular .module('hogeApp') .config(['$urlRouterProvider', '$stateProvider', '$locationProvider', function($urlRouterProvider, $stateProvider, $locationProvider) { $stateProvider /** * admin */ .state('admin', { url: '/admin', views: { main: { templateUrl: '/admin.html', controller: 'adminController', controllerAs: 'admin' } } }) /** * admin login */ .state('admin.login', { url: '/login', views: { main: { templateUrl: '/login.html', controller: 'adminLoginController', controllerAs: 'adminLogin' } } }) /** * admin main (base template) */ .state('admin.main', { url: '/', views: { main: { templateUrl: '/main.html', controller: 'adminMainController', controllerAs: 'adminMain' } } }) /** * admin main dashboard */ .state('admin.main.dashboard', { url: 'dashboard', views: { main: { templateUrl: '/dashboard/index.html', controller: 'adminMainDashboardController', controllerAs: 'adminMainDashboard' } } }) ; $urlRouterProvider.otherwise('/admin/dashboard'); $locationProvider.html5Mode(true).hashPrefix('!'); }]); }();
こんな感じでベースが完成。
めでたしめでたし。
AppKernelクラスを下記のように拡張してやれば解決する。
class AppKernel extends Kernel { public function registerBundles() { // ... } public function registerContainerConfiguration(LoaderInterface $loader) { // ... } protected function initializeContainer() { parent::initializeContainer(); if (PHP_SAPI == 'cli') { $this->getContainer()->enterScope('request'); $this->getContainer()->set('request', new \Symfony\Component\HttpFoundation\Request(), 'request'); } } }