• Как оптимизировать сборку webpack html-webpack-plugin?

    @fattan Автор вопроса
    программист
    Решение для webpack 3:
    new webpack.optimize.CommonsChunkPlugin({
            name: 'common',
            minChunks: 2
          })


    Для 4-го другой такой же плагин.
    Ответ написан
    Комментировать
  • Как правильно организовать структуру постоянно изменяющегося проекта?

    @fattan Автор вопроса
    программист
    Забавно. Перечитал этот свой давнишний вопрос. Странно, что никто не упомянул из отвечающих про agile-разработку, SOLID, и книги и статьи по разработке от Robert Cecil Martin.
    Ответ написан
    Комментировать
  • Как в Symfony не показывать пароль User Entity?

    @fattan Автор вопроса
    программист
    Наилучшим образом эту проблему решает JMS Serializer ExclusionPolicy
    ссылка на документацию
    Ответ написан
    Комментировать
  • Как в symfony3 сделать form-types заменяемым?

    @fattan Автор вопроса
    программист
    Решил задачу посредством комментариев BoShurik
    Выкладываю код получившегося результата.
    Визуально форма выглядит так:
    4AklO3OTvWQ8rq.png

    Action создания нового документа:
    public function createAction(Request $request)
        {
            $doc = new Document();
    
            $roleCurator = $this->getRole('ROLE_FINANCE_CURATOR');
            $userApproverCurator = new UserApprover();
            $userApproverCurator->setRole($roleCurator);
            $userApproverCurator->setDocument($doc);
    
            $roleInitiator = $this->getRole('ROLE_FINANCE_INITIATOR');
            $userApproverInitiator = new UserApprover();
            $userApproverInitiator->setRole($roleInitiator);
            $userApproverInitiator->setDocument($doc);
    
            $doc->getApprovers()->add($userApproverInitiator);
            $doc->getApprovers()->add($userApproverCurator);
    
            $form = $this->createForm(DocumentType::class, $doc);
            $form->handleRequest($request);
    
            if ( $form->isSubmitted() && $form->isValid() ) {
    
                $em = $this->getDoctrine()->getManager();
    
                foreach ($doc->getApprovers() as $approver){
                    $em->persist($approver);
                }
    
    			// ... other code
    
                $em->persist($doc);
                $em->flush();
    
    
                return $this->redirectToRoute('finance_exp_view', ['id' => $doc->getId()]);
            }
    
            return $this->render('::doc.create.html.twig', [
              'form' => $form->createView()
            ]);
        }


    DocumentType - основная форма:
    class DocumentType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder
              ->add('name', TextType::class, [
                'label' => 'Предмет договора',
              ])
              ->add('price', NumberType::class, [
                'label' => 'Цена',
                'scale' => 2,
                'grouping' => NumberFormatter::GROUPING_USED
              ])
    
    		  // ... other fields
            
            ->add('approvers', CollectionType::class, [
              'entry_type' => ApproverType::class
            ]);
        }
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
              'data_class' => Document::class,
            ));
        }
    }


    ApproverType - вложенная форма выбора согласующих:
    class ApproverType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $listener = function (FormEvent $event) use($builder) {
    
                /** @var UserApprover $approver */
                if (!$approver = $event->getData()) {
                    return;
                }
    
                $form = $event->getForm();
                $form->add('user', EntityType::class, [
                  'class' => 'AppBundle:User',
                  'label' => $approver->getRole()->getTitle(),
                  'choice_label' => function ($user) {
                      /** @var User $user */
                      $absent = (!$user->getAbsent()) ? '' : ' (отсутствует)';
                      return $user->getFullName() . $absent;
                  },
                  'query_builder' => function (EntityRepository $er) use ($approver){
                      $qb = $er->createQueryBuilder('u')
                        ->innerJoin('u.userRoles','r')
                        ->where('r.name=?1')
                        ->setParameter(1, $approver->getRole()->getName())
                        ->orderBy('u.lastName', 'ASC')
                      ;
                      return $qb;
                  },
                  'placeholder' => '--- Выберите ---'
                ]);
    
            };
    
            $builder->addEventListener(FormEvents::PRE_SET_DATA, $listener);
        }
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults(array(
              'data_class' => Approver::class,
            ));
        }
    }


    Получается, слушатель $listener отрабатывает после загрузки из модели данных в форму ApproverType (событие FormEvents::PRE_SET_DATA):

    set_data_flow.png

    Слушатель $listener добавляет один единственно нужный select в форму ( $form->add('user', EntityType::class)). Из данных модели ($approver = $event->getData()), выбирается ('label' => $approver->getRole()->getTitle()) информация о роли (эта информация была занесена ранее в контроллере ($userApproverCurator->setRole($roleCurator);))
    Ответ написан
    Комментировать
  • В doctrine:mapping:import как сделать кастомизацию импорта на основе префиксов таблиц?

    @fattan Автор вопроса
    программист
    сама идея описанная в вопросе оказалась "не очень". Поменял подход на классический:
    1. Создание моделей, 2. описание связей в конфиге, 3. генерация таблиц в БД
    Ответ написан
    Комментировать
  • Как грамотно организовать в Symfony3 подгрузку ролей из БД?

    @fattan Автор вопроса
    программист
    Проблема решилась когда прибегнул к совету @BoShurik
    Но заметил такую вещь (мб кому пригодится) - если в БД изменить имя роли, добавить новую, переназначить роль у пользователя и т.п. то $this->get('security.token_storage')->getToken()->getRoles() всё равно выдаст старый список ролей юзера.
    Если очистить кэш через консоль, или даже удалить все папки кэша, это не поможет. Помогает ре-аутентификация (разлогиниться, залогиниться). Дело в том что, в конце каждого запроса symfony сериализует (serialize) объект user в $_SESSOIN, а в начале нового запроса десиарилизует (unserialize) объект из сессии. Как я понимаю, с ролями юзера он также поступает.
    Ответ написан
  • Как организовать Doctrine One-To-One, Unidirectional with Join Table?

    @fattan Автор вопроса
    программист
    Сделал как советовал Myroslav Berlad в комментарии.
    Подробный результат:
    документы связал с промежуточной таблицей двусторонней связью 1 к 1
    Промежуточную таблицу (документ-статус) связал с таблицей статусов двусторонней связью 1 ко многим
    Схема БД та же, см. шапку.

    Создал 3 Entity.

    Для документов:
    спойлер
    <?php
    
    namespace Finance\ExpBundle\Entity;
    
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * Document
     *
     * @ORM\Table(name="exp_document", indexes={@ORM\Index(name="tasks_user_id_index", columns={"initiator_id"})})
     * @ORM\Entity(repositoryClass="Finance\ExpBundle\Repository\DocumentRepository")
     */
    class Document
    {
        /**
         * @var integer
         *
         * @ORM\Column(name="initiator_id", type="integer", nullable=false)
         */
        private $initiatorId;
    
        /**
         * @var string
         *
         * @ORM\Column(name="name", type="string", length=255, nullable=false)
         */
        private $name;
    
        /**
         * @var \DateTime
         *
         * @ORM\Column(name="created_at", type="datetime", nullable=true)
         */
        private $createdAt;
    
        /**
         * @var \DateTime
         *
         * @ORM\Column(name="updated_at", type="datetime", nullable=true)
         */
        private $updatedAt;
    
        /**
         * @var integer
         *
         * @ORM\Column(name="id", type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="IDENTITY")
         */
        private $id;
    
    
        /**
         * One Customer has One Cart.
         * @var DocStatus
         * @ORM\OneToOne(targetEntity="DocStatus", mappedBy="document")
         */
        private $docStatus;
    
    
        /**
         * @return DocStatus
         */
        public function getDocStatus()
        {
            return $this->docStatus;
        }
    
        /**
         * @return Status
         */
        public function getStatus()
        {
            return $this->getDocStatus()->getStatus();
        }
    
        /**
         * Set initiatorId
         *
         * @param integer $initiatorId
         *
         * @return Document
         */
        public function setInitiatorId($initiatorId)
        {
            $this->initiatorId = $initiatorId;
    
            return $this;
        }
    
        /**
         * Get initiatorId
         *
         * @return integer
         */
        public function getInitiatorId()
        {
            return $this->initiatorId;
        }
    
        /**
         * Set name
         *
         * @param string $name
         *
         * @return Document
         */
        public function setName($name)
        {
            $this->name = $name;
    
            return $this;
        }
    
        /**
         * Get name
         *
         * @return string
         */
        public function getName()
        {
            return $this->name;
        }
    
        /**
         * Set createdAt
         *
         * @param \DateTime $createdAt
         *
         * @return Document
         */
        public function setCreatedAt($createdAt)
        {
            $this->createdAt = $createdAt;
    
            return $this;
        }
    
        /**
         * Get createdAt
         *
         * @return \DateTime
         */
        public function getCreatedAt()
        {
            return $this->createdAt;
        }
    
        /**
         * Set updatedAt
         *
         * @param \DateTime $updatedAt
         *
         * @return Document
         */
        public function setUpdatedAt($updatedAt)
        {
            $this->updatedAt = $updatedAt;
    
            return $this;
        }
    
        /**
         * Get updatedAt
         *
         * @return \DateTime
         */
        public function getUpdatedAt()
        {
            return $this->updatedAt;
        }
    
        /**
         * Get id
         *
         * @return integer
         */
        public function getId()
        {
            return $this->id;
        }
    }



    Для промежуточной таблицы (документ-статус):
    Спойлер
    <?php
    
    namespace Finance\ExpBundle\Entity;
    
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * DocStatus
     *
     * @ORM\Table(name="exp_doc_status", indexes={@ORM\Index(name="id_doc", columns={"id_doc"}), @ORM\Index(name="id_status", columns={"id_status"})})
     * @ORM\Entity
     */
    class DocStatus
    {
        /**
         * @var \DateTime
         *
         * @ORM\Column(name="event_date", type="datetime", nullable=false)
         */
        private $eventDate = 'CURRENT_TIMESTAMP';
    
        /**
         * @var integer
         *
         * @ORM\Column(name="event_day", type="integer", nullable=false)
         */
        private $eventDay = '0';
    
        /**
         * @var integer
         *
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="NONE")
         */
        private $idDoc;
    
    
        /**
         * @ORM\OneToOne(targetEntity="Document", inversedBy="docStatus")
         * @ORM\JoinColumn(name="id_doc", referencedColumnName="id")
         */
        private $document;
    
    
        /**
         * @ORM\ManyToOne(targetEntity="Status", inversedBy="docStatus")
         * @ORM\JoinColumn(name="id_status", referencedColumnName="id")
         */
        private $status;
    
    
        public function getStatus()
        {
            return $this->status;
        }
    
        public function getDocument()
        {
            return $this->document;
        }
    
        /**
         * @var integer
         * @ORM\Id
         * @ORM\Column(name="id_status", type="integer", nullable=false)
         */
        private $idStatus;
    
        /**
         * Set eventDate
         *
         * @param \DateTime $eventDate
         *
         * @return DocStatus
         */
        public function setEventDate($eventDate)
        {
            $this->eventDate = $eventDate;
    
            return $this;
        }
    
        /**
         * Get eventDate
         *
         * @return \DateTime
         */
        public function getEventDate()
        {
            return $this->eventDate;
        }
    
        /**
         * Set eventDay
         *
         * @param integer $eventDay
         *
         * @return DocStatus
         */
        public function setEventDay($eventDay)
        {
            $this->eventDay = $eventDay;
    
            return $this;
        }
    
        /**
         * Get eventDay
         *
         * @return int
         */
        public function getEventDay()
        {
            return $this->eventDay;
        }
    
        /**
         * Get idDoc
         *
         * @return \Finance\ExpBundle\Entity\Document
         */
        public function getIdDoc()
        {
            return $this->idDoc;
        }
    
    }



    Для статусов:
    Спойлер
    <?php
    
    namespace Finance\ExpBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * Status
     *
     * @ORM\Table(name="exp_status")
     * @ORM\Entity
     */
    class Status
    {
        /**
         * @var string
         *
         * @ORM\Column(name="name", type="string", length=255, nullable=false)
         */
        private $name;
    
        /**
         * @var integer
         *
         * @ORM\Column(name="day_by_reglament", type="integer", nullable=false)
         */
        private $dayByReglament = '0';
    
        /**
         * @var integer
         *
         * @ORM\Column(name="id", type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="IDENTITY")
         */
        private $id;
    
    
        /**
         * @ORM\OneToMany(targetEntity="DocStatus", mappedBy="status")
         */
        private $docStatus;
    
    
        public function getDocStatus()
        {
            return $this->docStatus;
        }
    
    
        /**
         * Set name
         *
         * @param string $name
         *
         * @return Status
         */
        public function setName($name)
        {
            $this->name = $name;
    
            return $this;
        }
    
        /**
         * Get name
         *
         * @return string
         */
        public function getName()
        {
            return $this->name;
        }
    
        /**
         * Set dayByReglament
         *
         * @param integer $dayByReglament
         *
         * @return Status
         */
        public function setDayByReglament($dayByReglament)
        {
            $this->dayByReglament = $dayByReglament;
    
            return $this;
        }
    
        /**
         * Get dayByReglament
         *
         * @return integer
         */
        public function getDayByReglament()
        {
            return $this->dayByReglament;
        }
    
        /**
         * Get id
         *
         * @return integer
         */
        public function getId()
        {
            return $this->id;
        }
    }



    В результате, получить информацию о статусе документа в Symfony3 можно таким образом:

    В action контроллера:

    $em = $this->getDoctrine();
    $repoDoc = $em->getRepository(Document::class);
    $doc = $repoDoc->find($id);
    
    dump($doc->getStatus()->getName());
    dump($doc->getStatus()->getDayByReglament());


    В шаблонизаторе twig:

    {{ dump(doc.status.name) }}
    {{ dump(doc.status.dayByReglament) }}
    Ответ написан
    Комментировать
  • Как грамотно организовать подгрузку html в сложной форме?

    @fattan Автор вопроса
    программист
    Ответ.
    Для подобных задач уже давно придумали AngularJS и прочие фреймворки.
    Также, если использовать PHP-фреймворки, типа Symfony, а не писать свой велосипед как я, вопрос отпадёт сам по себе.
    Там рендер шаблона (или чанка шаблона) происходит прозрачно. Вне зависимости от того, ajax это или GET-запрос
    Ответ написан
    Комментировать
  • Как правильно реализовать logout на php?

    @fattan Автор вопроса
    программист
    Переместил session_destroy(); как можно ближе к session_start(). Проблема решилась.

    session_start();
    
    if (isset($_GET['logout'])) {
        session_destroy();
        header('location: /bla/bla/bla/index.php?module=auth&action=start' );
    
    }
    Ответ написан
    Комментировать
  • PHP — Out of memory - хотя памяти выделили много, так почему?

    @fattan Автор вопроса
    программист
    Выяснилось вот что:
    Есть таблица. В таблице 70 000 записей. Общий объем таблицы = 7 Мб. Периодически происходит выборка всех этих записей, и запись их циклом через mysql_fetch_assoc() в массив. Out of memory происходит в основном во время работы этого цикла. Не справляется с записью такого объема данных в массив.

    Переписали код. Ошибки перестали валиться.
    Ответ написан
    Комментировать
  • Возможна ли работа с tray (системный трей) средствами JavaFX 8?

    @fattan Автор вопроса
    программист
    Сам спросил, сам ответил:
    Перелопатив кучу англоязычной мукулатуры:

    1. есть ль альтернатива AWT-шным средствам работы с треем в Java 8?
    Да. Через пень-колоду-нативные методы (обращающиеся к вызовам ОС). Как, я понял, копать нужно в сторону SWT и иже с ними. Да так что проще заюзать AWT (для моей задачи)

    2. можно ли средствами JavaFX 8 работать с треем не прибегая к awt/swing?
    Нет. JavaFX 8 не поддерживает работу с треем. Возможно, поддержка работы с треем будет добавлена в 9й версии. А пока - сосите палец, мишки.

    Итог:
    Приложение оставил на JavaFX 8, а работу с треем вынес в отдельный класс, который написан с использованием AWT SystemTray.
    Ответ написан
    Комментировать
  • Как JavaFX Separator заставить быть шириной в 1px?

    @fattan Автор вопроса
    программист
    Тимур натолкнул меня на размышления, которые в итоге решили проблему.
    И так, ответ на вопрос:

    .separator *.line {
    -fx-border-style: solid;
    -fx-border-width: 0 0 1 0; /* фишка в задании толщины только одному из бордюров */
    -fx-border-color: red;
    }


    Итог: полноценный бордер толщиной в 1px
    Ответ написан
  • Разумно ли использовать xampp в качестве боевого сервера?

    @fattan Автор вопроса
    программист
    И что такого там можно настроить, что существенно изменит работу xampp?
    Какого типа это будут настройки? Настройки по увеличению памяти отводимой серверам? (делаем такие для апача ThreadStack вроде называется). Настройки кеширования? Делаем такие для мускула. Для PHP разве что лимиты по работе с файлами и время выполнения скриптов и error_reporting. На этом наши настройки заканчиваются.
    Ответ написан
    Комментировать
  • Как объективно протестировать производительность node.js и PHP в запросах к mysql БД?

    @fattan Автор вопроса
    программист
    Итак. Спасибо за ответы. Я понял в чем дело. Т.к. программирую на node.js аж целых 2 недели, я неверно трактовал результаты. Изменил тест, и вышло вот что:

    var start = new Date();
    
    var connection = mysql.createConnection({
      host     : 'localhost',
      user     : 'root',
      password : '', 
      database : 'epr'
    });
    
    
    for (var i = 0; i < 1000; i++) {
    	connection.query(
    		'SELECT * FROM dreg_document', 
    		function(error, result, fields){
    			
    			var str = '';
    			
    			result.forEach(function(row){
    
    				str += ' ' + row.id + ' ';
    
    			});
    			console.log(str);
    		}
    	);
    }
    
    connection.end();
    
    var end = new Date();
    console.log('node.js %d мс', (end.getTime()-start.getTime()) / 1000);
    	
    client.send('qwerty');


    Если думать по php-шному, после отработки всех запросов, отправляется сообщение клиенту -
    client.send('qwerty');

    Но на деле, сообщение отправляется тогда когда node-server отправит к БД ВСЕ запросы. Затем (! не дожидаясь ответа от БД) сервак отправляет клиенту сообщение. Клиент (браузер) подсчитывает время (вышло, к примеру 2 секунды для 100 000 итераций). А в это время в ноду продолжают поступать ответы от БД. Эти ответы мы видим с помощью
    console.log(str);
    И тут уже получаются совсем другие результаты....

    Далее, изменяем скрипт следующим образом, чтобы подсчитать время выполнения от 1го до последнего асинхронного подключения к БД:
    // начало выполнения скрипта
    var start = new Date();
    // счетчик обращений к БД
    var ConnCountGlobal = 0;
    // Число обращений к БД
    var iLength         = 100;
    
    var connection = mysql.createConnection({
      host     : 'localhost',
      user     : 'root',
      password : '', 
      database : 'epr'
    });
    
    for (var i = 0; i < iLength; i++) {
    	connection.query(
    		'SELECT * FROM dreg_document', 
    		function(error, result, fields){
    			
    			// для последнего запроса вычисляем таймер
    			if (ConnCountGlobal == iLength-1){
    			
    				var dateObj   = new Date();
    				var currTime  = dateObj.getTime();
    			
    				console.log(' node.js - %d мс', (currTime-start.getTime()) / 1000);
    			}
    
    			ConnCountGlobal++;
    		}
    	);
    }
    
    connection.end();


    Результат
    Для 100 итераций 2.5 сек (что в 15 раз медленнее чем на PHP)

    Промежуточный ответ на топик:
    Node.js не быстрее PHP в 150 раз в вопросе доступа к БД.
    Напротив, Node.js в этом плане в 15 раз медленнее чем PHP.
    (это если брать оба сервера "из коробки", без улучшательств и настроек)

    Новый вопрос:
    Верен ли мой последний тест? Или я в нём тоже что-то не так сделал?
    Ответ написан
    9 комментариев
  • Заражение сайта, что делать дальше?

    @fattan Автор вопроса
    программист
    Резюмирую:
    Хостинг был Спайсвеб (sweb.ru). Бэкапы у них хранятся за последние пять дней максимум (!!!). Техподдержка молчит. Сказка!
    Сайт был на джумле 2.1 (или что-то типа того).
    Я ручками перенес материалы из базы джумлы 2.1 в Joomla! 3.4.1. (для 10ти страниц сайта-визитки это оказалось быстрее чем если бы я возился с их migrate-системой)
    ...А потом психанул и переписал сайт с нуля на modx evolution (он у меня в любимчиках).

    Естественно, сменил хостера.

    В процессе работы заглянул ради интереса в скрипты Joomla! 3.4.1 и скрипты самого свежего modx evolution (1.13 вроде)
    и.... был удивлен! В джумле уже давно все фичи php 5.4 исользуются - автозагрузка классов с помощью нэймспэйсов, привытные поля в классах... и двухфакторная авторизация из коробки и много чего. (я же останусь на modx evo потому что там можно быстро и гибко всё настроить)

    А вот разработчики моего любимого modx, похоже, до сих пор бегают с копьями в шкурах мамонта. Это объясняется основным упором на ветку REVO (подумал я), поэтому EVO медленно развивается.

    ВЫВОДЫ:

    1. Не используйте Спайсвеб. Вообще никогда.
    2. Не используйте джумлу. Старую джумлу.
    3. Не бойтесь новой Джумлы.
    Ответ написан
    Комментировать
  • Как увеличить скорость сборки android-приложения?

    @fattan Автор вопроса
    программист
    Резюмирую:
    нужно включить оффлайн-режим для gradle, по умолчанию в Android Studio 1.1.1 он выключен.
    нужно зайти в Settings -> Gradle и нажать offline work. Время сборки стало:
    Total time: 6.45 secs (вместо 4-х минут)

    84ad03df1dd4421cb0507c7b21ac8b52.png
    Ответ написан
    Комментировать
  • Как установить Maven зависимость mysql-connector-java?

    @fattan Автор вопроса
    программист
    Отлично! Данила помог.

    Всё заработало! Стоило написать в C:\apache-maven-3.3.1\conf\settings.xml

    <proxies>
         <proxy>
          <active>true</active>
          <protocol>http</protocol>
          <username>marchenko_am@domain.ru</username>
          <password>1</password>
          <host>192.168.0.31</host>
          <port>8080</port>
          <nonProxyHosts>local.net|domain.ru</nonProxyHosts>
        </proxy>
      </proxies>


    Замечу также что был еще один баг - в самой intellij idea. Она не хотела работать с 3м мавеном.
    помогает установка новой (14.1.1) версии
    https://youtrack.jetbrains.com/issue/IDEA-137783
    Ответ написан
    Комментировать
  • Как работать с Atom.xml + XSL?

    @fattan Автор вопроса
    программист
    Эврика:
    Вся магия в нэймспейсах.
    своровал решение отсюда

    В xls нужно объяснить что это Atom и юзать его пространство имён

    <?xml version="1.0" encoding="utf-8"?>
    <x:stylesheet version="1.0"
                    xmlns:atom="http://www.w3.org/2005/Atom"
                    xmlns:x="http://www.w3.org/1999/XSL/Transform">
        <x:output
                method="html"
                media-type="text/html"
                indent="yes"
                encoding="UTF-8"/>
    
        <x:template match="/atom:feed">
            <x:text disable-output-escaping='yes'>&lt;!DOCTYPE html></x:text>
            <html>
                <head>
                    <title>DOCTYPE html 5</title>
                </head>
                <body>
                    <x:for-each select="atom:entry">
                        <x:sort order="descending" select="."/>
                        <h2>бла бла бла</h2>
                        Опубликовано: <x:value-of select="atom:published"/><br /> 
                    </x:for-each>
    
                </body>
            </html>
        </x:template>
    
    </x:stylesheet>
    Ответ написан
    Комментировать
  • Баг. Smarty кэширует шаблоны из PHPStorm. Как быть?

    @fattan Автор вопроса
    программист
    Мистика. Пришел с обеда - снова всё работает (шаблоны НЕ кэшируются). Самая подлая проблема - когда нет постоянства в её воспроизведении...

    UPD Проблема решена так - просто очищаем папку с смарти-кешем. Их оказалось несколько , поэтому не сразу проблема решилась
    Ответ написан