Как я воевал с контекстами

    Как истинный консерватор, я долгое время использовал исключительно MODx Evolution. Меня устраивало прежде всего наличие исчерпывающей документации, кучи статей и предельно понятной архитектуры данной версии MODx CMF. О версии Revolution я периодически читал разные статьи, но мне не хотелось менять привычную для меня систему на что-либо другое. Однако, в один прекрасный момент количество таблиц в базе данных моего мультидоменного «хомячка» на хостинге достигло устрашающих размеров. Встал вопрос о мультидоменном решении. Когда-то я вычитал о возможности создать на MODx Revolution мультидоменный сайт. Я установил движок на тестовый поддомен и начал копать глубже. Как оказалось, в самом фреймворке как такового готового решения насчёт мультидоменности пока не существует. Существует некая система контекстов. Разные контексты можно определить на разные поддомены. Только для этого нужно править файл index.php.

    Первое, что приходит в голову — это использовать уже проверенный популярный код:

    switch($_SERVER['SERVER_NAME']) {
      case 'sub1.domain.tld': $modx->initialize('sub1'); break;
      case 'sub2.domain.tld': $modx->initialize('sub2'); break;
      case 'sub3.domain.tld': $modx->initialize('sub3'); break;
      default: $modx->initialize('web');
    }
    


    Если, скажем, у Вас на системе контекстов зиждется многоязычность, тогда можно сделать даже так:

    switch($_SERVER['SERVER_NAME']) {
      case 'domain.ru': case 'www.domain.ru': $modx->initialize('ru'); break; // Переключаем на русский
      case 'domain.fr': case 'www.domain.fr': $modx->initialize('fr'); break; // Переключаем на французский (для примера)
      default: $modx->initialize('web');
    }
    


    И для не сильно больших порталов, где число поддоменов контролируется администратором хостинга/домена и редко меняется, такого решения хватит за глаза. Однако, давайте с Вами пофантазируем. Есть у Вас свой сервер. Это может быть Ваш личный сервер или VDS-ка на любимом хостинге. У Вас есть возможность программно создавать поддомены. Предположим, что Вы пишете свой аналог livejournal.com…

    Создать контекст при помощи API не сильно сложно. Вдаваться в подробности я не стану, не слишком сильно пока что изучил API MODx Revolution. Тем не менее создать контекст и поддомен — это одно, а вот связать это воедино — другое. Здесь вышеуказанные решения не подойдут, ибо заранее неизвестно сколько будет поддоменов и как будут называться контексты для них. По идее, если алиасы контекстов совпадают с именами поддоменов, тогда вполне подойдёт решение:

    define("myRootDomain","domain.tld");
    $ctxKey = 'web';
    
    if (preg_match('#(\w+).'.myRootDomain.'#si',$_SERVER['SERVER_NAME']) > 0) {
      $ctxKey = preg_replace('#(\w+).'.myRootDomain.'#si','\1',$_SERVER['SERVER_NAME']);
      if ($ctxKey == 'www') $ctxKey = 'web';
    }
    


    Основная информация о контекстах в MODx хранится в БД в таблицах context, context_setting. В первой таблице находятся описания контекстов (ключ, описание, порядок отображения). Во второй — настройки контекста. Помните, в распространённых решениях нам нужно было прописывать страницы ошибок, хост и тому подобное? Вот это-то всё там и хранится. И первое, что приходит в голову — это SQL-запрос к этой таблице:

    $SQL = "SELECT * FROM ".$table_prefix." WHERE `key`='http_host' AND `value`='".$_SERVER['SERVER_NAME']."'";
    


    Если бы система контекстов была предусмотрена в старушке Evolution, тогда с алгоритмом всё было бы просто:

    $ctxKey = 'web';
    if ($result = $modx->db->query($SQL)) if ($row = mysql_fetch_assoc($result)) $ctxKey = $row['context_key'];
    


    Однако, в этом отношении разработчики MODx подложили разработчикам, использующим MODx, небольшую хрюшку, ибо архитектура MODx Revolution зиждется на xPDO. А это уже не привычное нам API, а совсем другой разговор.

    Изучив кучу результатов гугло-поиска, в том числе и официальную документацию на MODx Revolution API, я так и не смог понять, как проще сделать запрос к БД в MODx Revolution. Зато, копнув файл core/model/modx/modx.php, я обнаружил нечто следующее:

    $pluginEventTbl= $this->getTableName('modPluginEvent');
    $eventTbl= $this->getTableName('modEvent');
    $pluginTbl= $this->getTableName('modPlugin');
    $propsetTbl= $this->getTableName('modPropertySet');
    $sql= "
        SELECT
            Event.name AS event,
            PluginEvent.pluginid,
            PropertySet.name AS propertyset
        FROM {$pluginEventTbl} PluginEvent
            INNER JOIN {$pluginTbl} Plugin ON Plugin.id = PluginEvent.pluginid AND Plugin.disabled = 0
            INNER JOIN {$eventTbl} Event ON {$service} Event.name = PluginEvent.event
            LEFT JOIN {$propsetTbl} PropertySet ON PluginEvent.propertyset = PropertySet.id
        ORDER BY Event.name, PluginEvent.priority ASC
    ";
    $stmt= $this->prepare($sql);
    if ($stmt && $stmt->execute()) {
        while ($ee = $stmt->fetch(PDO::FETCH_ASSOC)) {
            $eventElementMap[$ee['event']][(string) $ee['pluginid']]= $ee['pluginid'] . (!empty($ee['propertyset']) ? ':' . $ee['propertyset'] : '');
        }
    }
    
    


    Это фрагмент метода getEventMap класса modX. Логично предположить, что вместо длиннющего запроса мы можем вставить свой запрос и он по идее должен отработать, как нужно. В результате рождается решение:

    $ctxCur = 'web';
    $ctxQur = "SELECT * FROM `".$table_prefix."context_setting` WHERE `key`='http_host' AND `value`='".$_SERVER['SERVER_NAME']."'";
    $ctxSQL = $modx->prepare($ctxQur);
    if ($ctxSQL && $ctxSQL->execute()) if ($ctxRes = $ctxSQL->fetch(PDO::FETCH_ASSOC)) $ctxCur = $ctxRes['context_key'];
    
    $modx->initialize($ctxCur);
    


    При использовании данного решения нам нужно заботиться лишь о правильном указании поля http_host в админке. И имя контекста в этом случае не обязательно должно совпадать с поддоменом. На сим всё. Спасибо за внимание к моему очередному велосипеду!
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 28

      +1
      Господи… Зачем прямой запрос в БД? xPDO в MODx Revo не зря вставлен! Если не понять сам принцип xPDO то лучше с MODx Revo вобще дел не иметь.

      Так вы же небось ещё и вставляли свой код в index.php?
      ИМХО, надо всё-таки как-то так:
      <?php
      if($modx->context->key!="mgr")
      {
      	$contexts = $modx->getCollection('modContext');
      	foreach($contexts as $context)
      	{
      		$currentContextSettings = $modx->getCollection('modContextSetting', array('context_key' => $context->get("key")));
      		$multisiteHttpHost=NULL;
      		foreach($currentContextSettings as $currentContextSetting)
      		{
      			if($currentContextSetting->get('key')=="multisite_http_host")
      			{
      				$multisiteHttpHost=$currentContextSetting->get('value');
      				break;
      			}
      		}	
      		if($multisiteHttpHost!=NULL && $modx->getOption('http_host')==$multisiteHttpHost)
      		{
      			$modx->switchContext($context->get("key"));
      			break;
      		}
      	}
      }
      

      Текст плагина OnHandleRequest. В свойстве контекста multisite_http_host как раз и хранится домен, к которому привязан контекст.
        0
        И то я почти уверен что это всё тоже можно оптимизировать — просто пока что не заморачивался.
          0
          Возможно можно оптимизировать примерно так, не пробовал:
          $object = $modx->getObject('modContextSetting', array('key' = 'multisite_http_host', 'value' = $modx->getOption('http_host')));
          if ($object) $context = $object->get('context');
          

          Идея появилась на форуме MODx
            0
            Ошибся, конечно же вот так:
            <?php
            if($modx->context->key!="mgr")
            {
            	$object = $modx->getObject('modContextSetting', array('key' => 'multisite_http_host', 'value' => $modx->getOption('http_host')));
            	if ($object) $modx->switchContext($object->get('context_key'));
            }
            


            Проверенный рабочий код. Всё в том же плагине OnHandleRequest размещаете.
              +1
              PS: спасибо, что заставили поработать над собой и оптимизировать то, что я наваял первым комментом. :)
                0
                Хм, странно. У меня почему-то нечто подобное не работало. Наверное я немного дятел %)
                  0
                  Не знаю как у вас, я столкнулся на старом modx с проблемой, что почему-то не срабатывал site_start. После обновления до 2.2.4 и создании нового контекста всё заработало.
                    0
                    Кстати, глупый вопрос: а зачем при этом использовать параметр multisite_http_host? Почему нельзя использовать поле http_host?
                      0
                      Я хотел в будущем сделать мод с соответствующим namespace-ом. Да и чтобы не пересекалось ничего нигде, ведь system settings замещаются context settings-ами, а те в свою очередь user settings-ами. То есть таким образом мы бы затёрли оригинальный http_host. Конечно, в данном примере этого и незаметно, но если делать wildcard-поиск (может быть, я хочу чтобы одним контекстом обрабатывались не только домен, но и все его поддомены!), то это может составить проблему.
            0
            Боже мой, ну Вы и наворотили! О, о
            Смысл прямого обращения к БД очень прост — экономия ресурсов. Представьте себе Ваш код, который отрабатывает на каждой странице. Я может быть чего-то не знаю об xPDO, но, ИМХО, Ваш код будет в разы дольше выполняться.
          0
          Все можно сделать просто и элегантно. Вот мой способ.
            0
            Для варианта «Предположим, что Вы пишете свой аналог livejournal.com» не подойдёт. Кстати, на тему языков у меня тоже были какие-то наработки, но там надо всё причесать. Быть может как нибудь выложу топик с парой встреченных фактов в MODx если кому интересно, впрочем там немного и всё равно говнокод)
              0
              К слову сказать. Вот здесь-то и может пригодиться xPDO.
                0
                Добавляете настройку http_host в контекст — и у вас разные домены.

                Очень даже подойдет, только надо немного задействовать голову.
              0
              Это вариант с языками. Его можно реализовать и без контекстов. Банально к каждому материалу добавляем доп.поле заголовка и доп.поле контента. Ну, это я пока что эмпирически сказал :)
                0
                Смысл контекстов в том, чтобы указывать для них разные настройки.

                Любые настройки, включая http_host, что и позволяет реализовывать разные сайты на одном движке.

                У меня пример для мультиязычного сайта с одним доменным именем, но ничто не мешает добавить разные домены.

                То что вы написали — жуткий велосипед: «ничего не понимаю, пишу запросы, как привык в Evolution».
                Далеко вы так не продвинетесь.
              0
              Перечитал еще два раза и вот мое мнение: от таких статей MODX один вред.

              Вы не разобрались в теме, написали кашу с прямыми запросами в БД вместо простого $modx->switchContext(), и это не смотря на официальную документацию.

              Если вы думаете, что кому то помогли, то я вас разочарую. Вы наоборот, отпугнете потенциальных юзеров MODX Revolution своим бредом.
                0
                Не стану с Вами спорить. Давайте проще — напишите свою статью об этом. А то захожу я на «хабрахабр», ищу что-то подобное, да не нахожу. И приведите, пожалуйста, в своей статье рабочее решение, позволяющее управлять мультидоменным и мультиязычным сайтом.
                  0
                  По-моему вполне нормальный вариант (последний код). $modx->switchContext() нужен, если это плагин, а если править index.php, то $modx->initialize(). Если переключать контекст через плагин, получается сначала инициализируется один контекст, потом переключается на другой. Зачем лишняя работа? И xPDO в данном случае бесполезен.
                    0
                    Единственное я не понял зачем там второй if(....
                      0
                      Затем что первый раз мы проверяем, выполнен ли запрос вообще, второй раз — вернул ли результат хотя бы один ряд.
                      0
                      Зачем, зачем править index.php?

                      Насколько я помню, править его (или добавлять директории со своим index.php) нужно только при адресах типа domain.ru/ru/ domain.ru/en/

                      Если же адреса типа sub1.domain.ru sub2.domain.ru, то все делается простейшим плагином:
                      <?php
                      if ($modx->context->get('key') == 'mgr') {return;}
                      if (strstr($_SERVER['HTTP_HOST'], 'sub2.domain.ru') != false) {
                        $modx->switchContext('sub2');
                      }


                      На всякий случай, вот черновик плагина для перовго случая, даже без редактирования index.php и создания директорий:
                      if ($modx->event->name == 'OnHandleRequest') {
                          if ($modx->context->get('key') == 'mgr') {return;}
                      
                      if (preg_match('/^\/en/i', $_SERVER['REQUEST_URI']) {
                              $modx->switchContext('en');
                              $context = 'en';
                              $uri = preg_replace('/^\/en\//i', '', $_SERVER['REQUEST_URI']);
                          }
                          else {
                              $context = 'web';
                              $uri = preg_replace('/^\/ru\//i', '', $_SERVER['REQUEST_URI']);
                          }
                      
                          if ($res = $modx->getObject('modResource', array('context_key' => $context, 'uri' => $uri))) {
                              $modx->sendForward($res->get('id'));
                          }
                      }
                      


                      Эту тему мы обсуждали тут, довольно бурно =)
                        0
                        Забыл сказать, что для первого случая контексты и не нужны, обычно.

                        Достаточно просто создать в дереве 2 контейнера ru и en и вносить туда ресурсы на этих языках. Плагин я набросал просто для примера, что все можно сделать без редактирования системных файлов.

                        Истинное назначение контекстов, на мой взгляд — это разные домены. Тут все работает с полпинка с плагином в 3-4 строки. Конечно, не нужно забывать про настройки контекста (см. скриншот для примера)
                          0
                          Простите, будет грубо, зато правда. Ваши изречения напоминают точку зрения ортодоксального программиста-процедурника. Вот ему обязательно надо запихнуть каждый чих в begin-end или фигурные скобки — кому как милее; написать программу уровня Hello-World на .NET Framework, притом последней версии. А ч0, это же удобно! Его не заботит тот факт, что каждый лищний ret — это лишние такты процессора. Ему важнее «кошерность» кода. Да, я из тех, кто знает цену ассемблерным вставкам в программах, написанных на языках высокого уровня.

                          Да, я в своём решении обращался напрямую к БД. Зато меньше процессорного времени тратится при таком подходе. Ладно у меня на сайте с посещаемостью порядка 10 человек в квартал. А на проектах с высокой степенью нагрузки? Особенно если на дешёвом хостинге/оборудовании. Там каждый процессорный такт на счету. А теперь просто представьте себе, что каждая страница выполняет код с обращением к xPDO, получением через него объектов, переключением контекстов вместо простой прямой инициализации нужного контекста, полученного прямым запросом к БД через резидентно выполняемый на сервере процессор MySQL.

                          Не, а ч0, давайте использовать возможности современных серверов на полную катушку! Авось не заблокируют за CPU Overload на хостинге.
                            0
                            Зачем вам MODX Нет, не так. Зачем вам CMS? Или даже PHP?

                            Вы или трусы наденьте, или крестик снимите. А то городите какую то херь на готовом движке, не самом быстром, кстати, и философствуете о процессорных тактах. Высокая посещаемость и дешевый хостинг.

                            Вас тут уже двое таких. Удачи и вам.
                              0
                              Спасибо за пожелание :)

                              .оО( Интересно, если бы каждое такое пожелание сбывалось, насколько я бы был удачлив?.. )

                    Only users with full accounts can post comments. Log in, please.