пятница, 2 октября 2015 г.

Symfony2: fix error on dumping assets with using less, in Ubuntu 14.04

If you got an error on dumping assets
 
$ php app/console assetic:dump 
 
[Assetic\Exception\FilterException]         
  An error occurred while running:            
  '/usr/sbin/node' '/tmp/assetic_lessl9esGS
 
Check anf fix all paths for nodejs and less, or just try this configuration
 
assetic:
    debug:          "%kernel.debug%"
    use_controller: false
    bundles:        [ ]
    #java: /usr/bin/java
    filters:
        less:
            bin: /usr/local/bin/lessc
            node: /usr/bin/nodejs
            node_paths: ["/usr/local/lib/node_modules"]
            apply_to: "\.less$"

вторник, 15 января 2013 г.

Phalcon: Set output format for Phalcon-based RESTful API

1. edit .htaccess

AddDefaultCharset UTF-8

<ifmodule mod_rewrite.c="mod_rewrite.c">
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^([^.]+)$ index.php?_url=/$1&format=json [QSA,L]
    RewriteRule ^([^.]+)\.(\w+)$ index.php?_url=/$1&format=$2 [QSA,L]
</ifmodule>


2. edit index.php
  ...
  $app = new \Phalcon\Mvc\Micro();
  ...
  $app->finish(function () use ($app) {
    // check format
    $format = $app->request->getQuery('format', 'string', 'json');
    switch ($format) {
      case 'json': 
        print(json_encode($app->result->data));
        break;
      case 'xml':
        $xml = new SimpleXMLElement('');
        array_walk_recursive($app->result->data, array ($xml, 'addChild'));
        print $xml->asXML();
        break;
    }
  });
  
3. use api with .json | .xml

$ curl http://site/api/user/1
$ {json: data}
$ curl http://site/api/user/1.json
$ {json: data}
$ curl http://site/api/user/1.xml
$ <xml>data</xml>

четверг, 22 ноября 2012 г.

mysql: set utf-8 by default

$ cd /etc/mysql
$ sudo nano my.cnf

[client]
default-character-set   = utf8

[mysqld]
init_connect='SET collation_connection = utf8_general_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_general_ci
skip-character-set-client-handshake

[mysql]
default-character-set   = utf8

$ sudo service mysql restart

понедельник, 17 сентября 2012 г.

mysql: Cannot delete or update a parent row: a foreign key constraint fails


Если при удалении пустой таблицы, не содержащей никаких связей и индексов из БД все же возникает ошибка
MySQL said: Documentation #1217 - Cannot delete or update a parent row: a foreign key constraint fails

Выполняем следующий запрос:

SET FOREIGN_KEY_CHECKS =0;
DROP TABLE IF EXISTS table_name;
SET FOREIGN_KEY_CHECKS =1;




четверг, 19 января 2012 г.

Symfony: short commands in terminal

Save time and reduce typing errors on working in console

$ nano /usr/local/bin/sf

if [ -z "$1" ]; then
    echo "Missing parameters."
    echo "Usage: $0 --midiff, --mi or --buall"
    exit 0
fi

case "$1" in
    "--cc" )
    php symfony cc
    ;;

    "--midiff" )
    php symfony doctrine:generate-migrations-diff
    ;;

    "--mi" )
    php symfony doctrine:migrate
    ;;

    "--buall" )
    php symfony doctrine:build --all-classes
    ;;

    "--busql" )
    php symfony doctrine:build-sql
    ;;

    "--inssql" )
    php symfony doctrine:insert-sql
    ;;

    "--puass" )
    php symfony plugin:publish-assets
    ;;

esac
exit 0

Usage:

For example: you need modify your schema, migrate, rebuild all classes and clear cache.

$ sf --midiff
$ sf --mi
$ sf --buall
$ sf --cc

вторник, 22 февраля 2011 г.

Symfony: doctrine custom sql order

    $this->data = Doctrine_Query::create()
->from('Category c')
->innerJoin('c.Product p')
->whereIn('c.slug', array('cpu', 'mainboards', 'ram', 'hdd', 'videocards', 'cases'))
->addWhere('p.is_published = ?', true)
->addOrderBy('FIELD(c.slug, "cpu", "mainboards", "ram", "hdd", "videocards", "cases")')
->execute()
;

понедельник, 7 февраля 2011 г.

среда, 2 февраля 2011 г.

Symfony: Doctrine IndexBy field


$posts = Doctrine_Query::create()
->from('Post p INDEXBY id')
->fetchArray()
;

на выходе получаем массив индексированный по реальным значениям id,
а не [0 => array(), 1 => array() ... ]

p.s. спасибо develop7 за наводку

суббота, 29 января 2011 г.

Symfony: doctrine raw sql, get alphabet from i18n model

Задача: получить алфавит из i18n модели, для разных языков.
Показать только те буквы на которые начинаются имена.

in actions.class.php

$lang = $request->getParameter('lang', 'ru');

$query = "
SELECT substring(`name`,1,1) as firstletter
FROM `xxx_translation`
WHERE `lang` = '".$lang."' AND
substring(`name`,1,1) <> ''
GROUP BY substring(`name`,1,1)
";

$pdo = Doctrine_Manager::getInstance()->getCurrentConnection()->getDbh();
$stmt = $pdo->prepare($query);
$stmt->execute();

$this->abc = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);


in template

<?php if ($abc): ?>
<ul id="abcList">
<?php foreach ($abc as $letter): ?>
<li><?php echo link_to($letter, '@user_list?search='.$letter.'&pos=start')?> </li>
<?php endforeach; ?>
</ul>
<br clear="all" />
<?php endif; ?>


Css

ul#abcList li {
float: left;
display: inline;
list-style: none;
padding: 4px;
}

вторник, 21 сентября 2010 г.

Symfony: backend pager



Что бы получше выделить текущую страницу в пейджере далаем небольшие изменения:

1. CSS
.currentPage {
border: 1px solid #ff9900;
padding: 4px;
background: #eeeeff;
}


2. layout.php

jQuery(document).ready(function($){

// try to find pager
pager = $('.sf_admin_pagination').html();
if (pager) {
var re = /(\s+)(\d+)(\s)+/;
var newstr = pager.replace(re, "$2");
$('.sf_admin_pagination').html(newstr);
}

понедельник, 14 декабря 2009 г.

Symfony: settings

Нередко требуется часть настроек из app.yml сделать редактируемыми в backend, напишем простенький класс.
Схема:
Config:
tableName: sf_config
columns:
id: { type: integer(4), primary: true, autoincrement: true }
name: { type: string(32) }
value: { type: string(32) }
description: { type: string(1024) }


Класс /lib/myConfig.class.php:
class myConfig extends sfConfig
{
public static function get($name, $default = null, $description = null)
{
if (!sfConfig::get($name))
{
$res = Doctrine::getTable('Config')->findOneByName($name);
if (!$res)
{
$c = new Config();
$c->setName($name);
$c->setValue($default);
$c->setDescription($description);
$c->save();
}
}

return (isSet(self::$config[$name]) ? self::$config[$name] : $default);
}

}


Данный класс не находя в настройках требуемого параметра, проверяет его наличие в таблице, и в случаее отсутствия добавляет его.

Фильтр /lib/filters/myConfigFilter.class.php:
class myConfigFilter extends sfFilter
{
public function execute ($filterChain)
{
$configs = Doctrine::getTable('Config')->findAll();
foreach ($configs as $config)
{
sfConfig::set($config->getName(), $config->getValue());
}
$filterChain->execute();
}
}


Фильтр экспортирует из БД настройки в sfConfig.

Добавляем в fontend/config/filters.yml

myConfig:
class: myConfigFilter


Используем:
$postDirName = sfConfig::get('sf_upload_dir').'/'.myConfig::get('posts_dir', 'posts');
'max_size' => myConfig::get('post_max_icon_size', '10', 'size in KBytes')*1024, //(10*1024=10KB in bytes)


Сгенерируем управление из backend
$ symfony doctrine:generate-admin backend Config


Используем backend_dev.php/config

понедельник, 26 октября 2009 г.

Symfony: Doctrine slugify кириллица

/lib/Slugify.class.php

class SlugifyClass {
  static function Slugify($title) {
    $gost = array(
     "Є"=>"EH","І"=>"I","і"=>"i","№"=>"#","є"=>"eh",
     "А"=>"A","Б"=>"B","В"=>"V","Г"=>"G","Д"=>"D",
     "Е"=>"E","Ё"=>"JO","Ж"=>"ZH",
     "З"=>"Z","И"=>"I","Й"=>"JJ","К"=>"K","Л"=>"L",
     "М"=>"M","Н"=>"N","О"=>"O","П"=>"P","Р"=>"R",
     "С"=>"S","Т"=>"T","У"=>"U","Ф"=>"F","Х"=>"KH",
     "Ц"=>"C","Ч"=>"CH","Ш"=>"SH","Щ"=>"SHH","Ъ"=>"'",
     "Ы"=>"Y","Ь"=>"","Э"=>"EH","Ю"=>"YU","Я"=>"YA",
     "а"=>"a","б"=>"b","в"=>"v","г"=>"g","д"=>"d",
     "е"=>"e","ё"=>"jo","ж"=>"zh",
     "з"=>"z","и"=>"i","й"=>"jj","к"=>"k","л"=>"l",
     "м"=>"m","н"=>"n","о"=>"o","п"=>"p","р"=>"r",
     "с"=>"s","т"=>"t","у"=>"u","ф"=>"f","х"=>"kh",
     "ц"=>"c","ч"=>"ch","ш"=>"sh","щ"=>"shh","ъ"=>"",
     "ы"=>"y","ь"=>"","э"=>"eh","ю"=>"yu","я"=>"ya","«"=>"","»"=>"","—"=>"-"," "=>"-"
    );

    $iso = array(
     "Є"=>"YE","І"=>"I","Ѓ"=>"G","і"=>"i","№"=>"#","є"=>"ye","ѓ"=>"g",
     "А"=>"A","Б"=>"B","В"=>"V","Г"=>"G","Д"=>"D",
     "Е"=>"E","Ё"=>"YO","Ж"=>"ZH",
     "З"=>"Z","И"=>"I","Й"=>"J","К"=>"K","Л"=>"L",
     "М"=>"M","Н"=>"N","О"=>"O","П"=>"P","Р"=>"R",
     "С"=>"S","Т"=>"T","У"=>"U","Ф"=>"F","Х"=>"X",
     "Ц"=>"C","Ч"=>"CH","Ш"=>"SH","Щ"=>"SHH","Ъ"=>"'",
     "Ы"=>"Y","Ь"=>"","Э"=>"E","Ю"=>"YU","Я"=>"YA",
     "а"=>"a","б"=>"b","в"=>"v","г"=>"g","д"=>"d",
     "е"=>"e","ё"=>"yo","ж"=>"zh",
     "з"=>"z","и"=>"i","й"=>"j","к"=>"k","л"=>"l",
     "м"=>"m","н"=>"n","о"=>"o","п"=>"p","р"=>"r",
     "с"=>"s","т"=>"t","у"=>"u","ф"=>"f","х"=>"x",
     "ц"=>"c","ч"=>"ch","ш"=>"sh","щ"=>"shh","ъ"=>"",
     "ы"=>"y","ь"=>"","э"=>"e","ю"=>"yu","я"=>"ya","«"=>"","»"=>"","—"=>"-"," "=>"-"
    );

    $rtl_standard = sfConfig::get('rtl_standard', 'gost') ;

    //to lower case

    $title = mb_strtolower($title, 'UTF-8');
    
    switch ($rtl_standard) {
      case 'off':
          return $title;
      case 'gost':
          $out = trim(strtr($title, $gost), '-');
      default:
          $out = trim(strtr($title, $iso), '-');
    }
    
    // clean slug
    $out = preg_replace('/[^a-z0-9\-]+/i', '', $out);
    return $out;
  }
}



Использование

Post:
  actAs:
    Timestampable: ~
    Taggable: ~
    Sluggable:
      unique: true
      fields: [title]
      canUpdate: true
      builder: [SlugifyClass, Slugify]

вторник, 29 сентября 2009 г.

Symfony: Doctrine order by calculated field

Положим нам нужно вывести 10 наиболее комментируемых постов.
Кусок схемы:
Post:
tableName: blog_post
actAs:
Timestampable: ~
Taggable: ~
columns:
id: { type: integer(4), primary: true, autoincrement: true }
title: { type: string(255) }
extract: { type: string(1024) }
content: { type: string(4096) }
is_published: { type: boolean, default: false }

Comment:
tableName: blog_comment
actAs:
Timestampable: ~
columns:
id: { type: integer(4), primary: true, autoincrement: true }
post_id: { type: integer(4), notnull: true }
name: { type: string(100) }
email: { type: string(100) }
content: { type: string(4096) }
subscribe: { type: boolean, default: false }
relations:
Post:
class: Post
local: post_id
foreign: id
foreignAlias: PostComments
type: one
foreignType: many
onDelete: CASCADE


Получим популярные посты одним запросом:
  public function executeGetMostCommentedBlogPosts()
{
$q = Doctrine_Query::create()
->select('p.*')
->addSelect('(SELECT count(*) FROM PostComments pc WHERE pc.post_id = p.id) as comments_count')
->from('Post p')
->where('p.is_published', true)
->orderBy('comments_count DESC')
->addOrderBy('d.created_at DESC')
->limit(10);

$this->posts = $q->execute();
}

понедельник, 24 августа 2009 г.

Symfony: date selector (выбор даты рождения)

Нас не устраивает стандартный выбор даты в виде 3х листбоксов в формате месяц/день/год, да еще и месяцы указаны цифрами, что вызывает массу затруднений.
Мы хотим такой вид:


Решение:
  public function setup()
{
$years = range(1950, 2000); //Creates array of years between 1950-2000
$years_list = array_combine($years, $years); //Creates new array where key and value are both values from $years list

$this->setWidgets(array(
'id' => new sfWidgetFormInputHidden(),
'city' => new sfWidgetFormInput(),
'birthday' => new sfWidgetFormI18nDate(
array(
'culture' => 'ru',
'format' => '%day%.%month%.%year%',
'years' => $years_list,
),
array()
),

Symfony: sfWidgetFormInputFileEditable IE broken image

Положим, что мы хотим позволить пользователю прикреплять картинку к своему профилю.
Обратившись к JOBEET находим решение - виджет sfWidgetFormInputFileEditable
Используем его в своей форме:
    
$this->setWidgets(array(
'id' => new sfWidgetFormInputHidden(),
...
'avatar' => new sfWidgetFormInputFileEditable(array(
'label' => 'Ваш аватар',
'file_src' => '/uploads/avatars/'.$this->getObject()->getAvatar(),
'is_image' => true,
'edit_mode' => !$this->isNew(),
)),

Однако вскоре мы обнаруживаем, что когда пользователь не создает. а редактирует свой профиль, и при этом аватар у него не загружен, в форме появляется битый рисунок и предложение удалить несуществующую картинку:


Решение проблемы:
   'avatar'       => new sfWidgetFormInputFileEditable(array(
'label' => 'Avatar for forum, blog, comments, etc...',
'file_src' => '/uploads/avatars/'.$this->getObject()->getAvatar(),
'is_image' => true,
'edit_mode' => ($this->getObject()->getAvatar() OR $this->isNew() ? true: false),
)),

Ну и заинтересовавшимся для комплекта валидатор:
    $this->validatorSchema['avatar'] = new sfValidatorFile(array(
'required' => false,
'path' => sfConfig::get('sf_upload_dir').'/avatars',
'mime_types' => 'web_images',
'max_size' => '40960', //(40KB in bytes)
));

понедельник, 18 мая 2009 г.

Symfony: applications as subdomains

Мы хотим иметь такую конструкцию:
site.com             = frontend.php
www.site.com = frontend.php
dev.www.site.com = frontend_dev.php
backend.site.com = backend.php
dev.backend.site.com = backend_dev.php


Изменяем index.php

require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');

// get the domain parts as an array
@list($tld, $domain, $subdomain, $subdomain2) = array_reverse(explode('.', $_SERVER['HTTP_HOST']));

// determine which subdomain we're looking at
$app = ($subdomain == 'webadmin') ? 'backend' : $subdomain;
$app = (empty($app) || $app == 'www' ) ? 'frontend' : $app;
$env = (empty($subdomain2) || $subdomain2 == 'www') ? 'prod' : $subdomain2;

// determine which app to load based on subdomain
if (!is_dir(realpath(dirname(__FILE__).'/..').'/apps/'.$app))
{
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false);
}
else
{
$configuration = ProjectConfiguration::getApplicationConfiguration($app, $env, false);
}

sfContext::createInstance($configuration)->dispatch();


Использованные материалы: Dynamically Loading Symfony Applications Via Subdomains

четверг, 23 апреля 2009 г.

Symfony: raw query, get years list for active posts

public function executeIndex(sfWebRequest $request)
{
//get years
$connection = Propel::getConnection();
$query = '
SELECT YEAR( %s ) AS myYear
FROM %s
WHERE %s = 1
GROUP BY YEAR( %s )
ORDER BY myYear DESC
';

$query = sprintf($query,
PostPeer::PUBLISHED_AT,
PostPeer::TABLE_NAME,
PostPeer::IS_PUBLISHED,
PostPeer::PUBLISHED_AT
);
$statement = $connection->prepare($query);
$statement->execute();
$years = array();
while($resultset = $statement->fetch(PDO::FETCH_OBJ))
{
$years[] = $resultset->myYear;
}
$this->years = $years;
}


returns array(2009, 2008, 2007...)

понедельник, 13 апреля 2009 г.

Symfony: filter by 2 fields in backend

Простая ситуация: в таблице 2 поля
  sf_post:
_attributes: { phpName: Post }
id: ~
title: { type: varchar(255) }
content: { type: longvarchar }
...

Нам не важно в заголовке или в описании есть искомое поле, мы хотим в админке сделать фильтрацию по обоим полям. Берем исходник из кэша и модифицируем /backend/modules/post/actions/actions.class.php
  protected function buildCriteria()
{
$filters = $this->getFilters();
if (is_null($this->filters))
{
$this->filters = $this->configuration->getFilterForm($this->getFilters());
}

$criteria = $this->filters->buildCriteria($this->getFilters());

$this->addSortCriteria($criteria);

$event = $this->dispatcher->filter(new sfEvent($this, 'admin.build_criteria'), $criteria);
$criteria = $event->getReturnValue();

if (!empty($filters['title']['text']))
{
//добавим так же фильтр по полю CONTENT
$c1 = $criteria->getNewCriterion(PostPeer::TITLE, '%'.$filters['title']['text'].'%', Criteria::LIKE);
$c2 = $criteria->getNewCriterion(PostPeer::CONTENT, '%'.$filters['title']['text'].'%', Criteria::LIKE);
$c1->addOr($c2);
$criteria->add($c1);
}

return $criteria;
}