Спасибо, работает, но к сожалению регистрозависимо и не покрывает все задачи.
Полагаю, нужно добавить фильтры и анализаторы, есть пример из доки:
$params = array(
'index' => 'reuters',
'body' => array(
'settings' => array(
'number_of_shards' => 1,
'number_of_replicas' => 0,
'analysis' => array(
'filter' => array(
'shingle' => array(
'type' => 'shingle'
)
),
'char_filter' => array(
'pre_negs' => array(
'type' => 'pattern_replace',
'pattern' => '(\\w+)\\s+((?i:never|no|nothing|nowhere|noone|none|not|havent|hasnt|hadnt|cant|couldnt|shouldnt|wont|wouldnt|dont|doesnt|didnt|isnt|arent|aint))\\b',
'replacement' => '~$1 $2'
),
'post_negs' => array(
'type' => 'pattern_replace',
'pattern' => '\\b((?i:never|no|nothing|nowhere|noone|none|not|havent|hasnt|hadnt|cant|couldnt|shouldnt|wont|wouldnt|dont|doesnt|didnt|isnt|arent|aint))\\s+(\\w+)',
'replacement' => '$1 ~$2'
)
),
'analyzer' => array(
'reuters' => array(
'type' => 'custom',
'tokenizer' => 'standard',
'filter' => array('lowercase', 'stop', 'kstem', 'word_delimiter', 'snowball')
)
)
)
),
'mappings' => array(
'_default_' => array(
'properties' => array(
'title' => array(
'type' => 'string',
'analyzer' => 'reuters',
'term_vector' => 'yes',
'copy_to' => 'combined'
),
'body' => array(
'type' => 'string',
'analyzer' => 'reuters',
'term_vector' => 'yes',
'copy_to' => 'combined'
),
'combined' => array(
'type' => 'string',
'analyzer' => 'reuters',
'term_vector' => 'yes'
),
'topics' => array(
'type' => 'string',
'analyzer' => 'reuters',
),
'places' => array(
'type' => 'string',
'index' => 'not_analyzed'
)
)
),
'my_type' => array(
'properties' => array(
'my_field' => array(
'type' => 'string'
)
)
)
)
)
);
но не ясно, как потом в поиске использовать созданый фильтр - все примеры в json с запросом к HTTP серверу эластика.