実際のSymfony 2内部

投稿はこの質問に触発されています。 標準のSymfonyイベントを使用して、コントローラーの出力をオーバーライドします。 だから、一般的に、これはすべてどのように機能します:

  1. Ajaxアノテーションを作成して、コントローラーのコンテンツタイプを処理します
  2. イベントを通じてこの注釈を処理します。
  3. 注釈で選択されたタイプに従ってコンテンツタイプを再定義します


私はすぐに警告します、コードは完璧であるふりをせず、キャッシングは使用されません(これについては後で説明します)が、主なアイデアは理解できると思います。 また、 公式ドキュメントでSymfony2 Internalsの詳細を読むことができます



それでは、違反してみましょう。

最初に、注釈クラスを定義します。

namespace SomeNamespace\SomeBundle\Annotations; /** @Annotation */ class Ajax { /** * @var array @contentType */ public $contentType; /** * @var array @parameters */ public $parameters; public function __construct($data) { if (isset($data['value'])) { $this->contentType = $data['value']; } if (isset($data['parameters'])) { $this->parameters = $data['parameters']; } } /** * @param array $contentType */ public function setContentType($contentType) { $this->contentType = $contentType; } /** * @return array */ public function getContentType() { return $this->contentType; } }
      
      





この注釈は、コントローラーが提供するコンテンツのタイプを決定します。

次に、イベントリスナーを作成します。

 namespace SomeNamespace\SomeBundle\Event; use Symfony\Component\HttpKernel\Event\KernelEvent; use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; use Doctrine\Common\Annotations\Reader; use Symfony\Component\HttpFoundation\Response; /** * Controller Event listener */ class ControllerListener { /** * @var ServiceContainer */ private $container; /** * Parameters of Event Listener * * @var array */ private $parameters; /** * @var AnnotationsReader */ private $annotationReader; //     -   Core/ContentTypes public function __construct($c, $a) { $this->container = $c; $this->annotationReader = $a; //@TODO   ,        . ,    -      ,   . $classes = array(); $namespace = 'SomeNamespace\SomeBundle'; $namespace = str_replace('\\', '/', $namespace); $dir = opendir('../src/' . $namespace . '/Core/ContentTypes'); while ($classes[] = str_replace('.php', '', readdir($dir))) { ; } foreach ($classes as $key => $class) { if ($class == '') { unset($classes[$key]); continue; } if ($class[0] == '.') { unset($classes[$key]); } } $this->parameters['contentTypes'] = $classes; } /** * Controller event listener * * @param \Symfony\Component\HttpKernel\Event\KernelEvent $event */ public function onKernelController(KernelEvent $event) {//      .     .    ,     ,    ,        $controller = $event->getController(); $object = new \ReflectionObject($controller[0]); $method = $object->getMethod($controller[1]); $annotations = $this->annotationReader->getMethodAnnotations($method); $response = new Response(); $this->parameters['attributes'] = $event->getRequest()->attributes; foreach ($annotations as $annotation) { if ($annotation instanceof \ITE\JSBundle\Annotations\Ajax) { $this->parameters['annotation'] = $annotation; } } $class = NULL; $params = array(); if (isset($this->parameters['annotation'])) { if (isset($this->parameters['annotation']->parameters)) { $params = $this->parameters['annotation']->parameters; } foreach ($this->parameters['contentTypes'] as $contentType) { $className = '\ITE\JSBundle\Core\ContentTypes\\' . $contentType; $name = $className::getName(); if ($name == $this->parameters['annotation']->contentType) { $class = $className; } } if (!$class) { throw new \ITE\JSBundle\Core\Exception\ContentTypeException( 'ContentType "' . $this->parameters['annotation']->contentType . '" is not found!'); } //  -    .     . $contentType = new $class($this->container, $params); $this->parameters['contentType'] = $contentType; $contentType->hookPre($event->getRequest()); } } /** * Controller Response listener * * @param $event */ public function onKernelResponse($event) {//       .     javascript  , ,    Symfony Profiler.        $response = $event->getResponse(); $response = $this->addJavascript($response); $event->setResponse($response); } /** * Controller Request listener * * @param $event */ public function onKernelRequest($event) { //    .      $this->generateRoutes(); } /** * Controller response listener * * @param GetResponseForControllerResultEvent $event */ public function onKernelView(GetResponseForControllerResultEvent $event) { //     .    onKernelResponse if (isset($this->parameters['contentType'])) { $contentType = $this->parameters['contentType']; $response = new Response; $response->setContent($contentType->encodeParameters($event->getControllerResult())); $response = $contentType->hookPost($response); $event->setResponse($response); } } /** * Generating route array and move to javascript file */ private function generateRoutes() { //  ,       $routeCollection = $this->container->get('router')->getRouteCollection(); $routes = array(); foreach ($routeCollection->all() as $route) { $r = array(); $defaults = $route->getDefaults(); try { $method = new \ReflectionMethod($defaults['_controller']); } catch (\Exception $e) { continue; } $ann = $this->annotationReader->getMethodAnnotations($method); foreach ($ann as $a) { if ($a instanceof \Sensio\Bundle\FrameworkExtraBundle\Configuration\Route) { $r[$a->getName()] = $route->getPattern(); } } $routes += $r; } $path = __FILE__; $path = str_replace('Event' . DIRECTORY_SEPARATOR . 'ControllerListener.php', '', $path); $path .= 'Resources' . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'routing_template.js'; $content = file_get_contents($path); $route_string = json_encode($routes); $content = str_replace('__routes__', $route_string, $content); $kernel = $this->container->get('kernel'); $params = array( 'env' => $kernel->getEnvironment(), 'debug' => $kernel->isDebug(), 'name' => $kernel->getName(), 'startTime' => $kernel->getStartTime(), ); $content = str_replace('__params__', json_encode($params), $content); $path = str_replace('routing_template', 'routing', $path); file_put_contents($path, $content); } /** * Adding global Symfony javascript * * @param $response * * @return mixed */ private function addJavascript($response) {//       $content = $response->getContent(); $arr = explode('</head>', $content); if (count($arr) == 1) { return $response; } $twig = $this->container->get('templating'); $c = $twig->render('SomeNamespaceSomeBundle:Javascript:js.html.twig'); $content = $arr[0] . $c . "</head>" . $arr[1]; $response->setContent($content); return $response; } }
      
      





そして、システムに登録します:

 #SomeBundle\Resources\config\services.yml services: my.ajax.listener: class: "SomeNamespace\SomeBundle\Event\ControllerListener" tags: [{name: kernel.event_listener, event: kernel.response, method: onKernelResponse, priority: -128}, {name: kernel.event_listener, event: kernel.request, method: onKernelRequest}, {name: kernel.event_listener, event: kernel.view, method: onKernelView, priority: -128}, {name: kernel.event_listener, event: kernel.controller, method: onKernelController}] arguments: [@service_container, @annotation_reader]
      
      





もう1つの議論、優先度に注意してください。 イベントの優先度を設定します。 例として、Drupalが思い浮かびます。 これはモジュール重量の類似物であり、正反対です。 Drupalでは、重みが大きいほど、フックが後で呼び出されます。 また、Symfonyでは、優先度が高いほど、イベントが早く呼び出されます。



したがって、各コンテンツタイプの構造は次のようになります。

開始するには、インターフェースを作成します。



 namespace SomeNamespace\SomeBundle\Core; interface ContentTypeInterface { /** * Get the name of ContentType * @abstract * @return mixed */ public static function getName(); /** * Encoder * @abstract * @param $data * @return mixed */ public function encodeParameters($data); /** * Decoder * @abstract * @param $data * @return mixed */ public function decodeParameters($data); /** * Prepares request * @abstract * @param Request * @return mixed */ public function hookPre($request); /** * Changes response * @abstract * @param Response * @return mixed */ public function hookPost($response); }
      
      





そして今、私はより詳細に教えます:



次に、インターフェイスを実装するクラスを作成し、そこからすべてのコンテンツタイプが継承されます。

 namespace SomeNamespace\SomeBundle\Core; class ContentType implements ContentTypeInterface { /** * @var ServiceContainer */ protected $container; /** * @var array parameters */ protected $parameters; /** * Public constructor * @param $container */ public function __construct($container, $params = array()){ $this->container = $container; $this->parameters = $params; } /** * Get the name of ContentType * @return mixed */ public static function getName() { return 'contentType'; } /** * Encoder * @param $data * @return mixed */ public function encodeParameters($data) { return $data; } /** * Decoder * @param $data * @return mixed */ public function decodeParameters($data) { return $data; } /** * Prepares request * @param $data * @return mixed */ public function hookPre($request) { } /** * Changes response * @param $data * @return mixed */ public function hookPost($response) { return $response; } }
      
      





ご覧のとおり、ContentTypeInterfaceインターフェースを実装しています。

これで、独自のコンテンツタイプを作成できるようになりました。たとえば、jsonコンテンツタイプを提供します。

 namespace SomeNamespace\SomeBundle\Core\ContentTypes; use SomeNamespace\SomeBundle\Core\ContentType; class JSONContentType extends ContentType { private $params; /** * Get the name of ContentType * @return mixed */ public static function getName() { return "json"; } /** * Changes response * @param $data * @return mixed */ public function hookPost($response) { return $response; } /** * Encoder * @param $data * @return mixed */ public function encodeParameters($data) { return json_encode($data); } /** * Decoder * @param $data * @return mixed */ public function decodeParameters($data) { return json_decode($data); } }
      
      







結論として、ルートとパラメーターの生成に使用されるjavascriptコードを提供します。



 //SomeBundle\Resources\js\routing_template.js (function(){if(typeof SF!='undefined'){SF.sSet('routes',__routes__);SF.parameters = __params__;}})();
      
      





そして、この全体を保存して使用するjavascriptも:

 (function () { SF = function () { }; SF.prototype.fn = SF.prototype; SF = new SF(); SF.fn.Storage = {}; SF.fn.hasValue = function (name) { return this.Storage[name] !== undefined; }; SF.fn.getValue = function (name) { if (this.hasValue(name)) { return this.Storage[name]; } else { return void 0; } }; SF.fn.getAllValues = function () { return this.Storage }; SF.fn.loggingEnabled = function () { return this.parameters.debug; }; SF.fn.messagingEnabled = function () { return this.parameters.messaging !== undefined && this.parameters.messaging; }; SF.fn.getMessages = function () { return !this.framework || this.framework.messaging === undefined ? { } : this.framework.messaging; }; // framework SF.fn.getLocation = function (name) { if (this.hasLocation(name)) { return this.framework.ajax[name]; } else { return void 0; } }; SF.fn.hasLocation = function (name) { return this.framework !== null && this.framework.ajax !== undefined && this.framework.ajax[name] !== undefined; }; // Storage setter and getter SF.fn.sSet = function (key, val) { this.Storage[key] = val; }; SF.fn.sGet = function (key) { return this.Storage[key] ? this.Storage[key] : null; }; // log function with debug checking SF.fn.l = function (a, b) { if (!b) b = 'log'; if (this.parameters.debug) { switch (b) { case 'log': console.log('[SF]: ', a); break; case 'info': console.info('[SF]: ', a); break; case 'warning': console.warn('[SF]: ', a); break; case 'error': console.error('[SF]: ', a); break; } } }; // SF path function SF.fn.path = function (name, arguments) { if (this.Storage.routes[name]) { var path = this.Storage.routes[name]; for (var a in arguments) { path = path.replace('{' + a + '}', arguments[a]); } return path; } else { this.l('Route "' + name + '" is not found!', 'error'); return false; } }; })(window);
      
      





さて、今、最も興味深いのは仕事の例です。 コントローラーでアクションを作成します。

 //   use  ,  Symfony    /** * * @param key string * @Route("/ajax/{key}", name="JSBundle_ajax") * @Ajax("json") * @return array */ public function ajaxAction($key) { //do some work return array('a' => 'b', 'd' => 'c'); }
      
      





コントローラーの応答は次のようになります。

 { a: "b", d: "c" }
      
      





また、javascriptの例:

 SF.l(SF.path('JSBundle_ajax', {'key': 'asd'}));
      
      





Symfonyでデバッグが無効になっている場合、コンソールには何も印刷されませんが、そうでない場合は印刷されます:

/ ajax / asd



PSアドオンは大歓迎です。 賢い考えを聞いてうれしいです。



All Articles