Сниппеты - очень полезная штука

Сомневаюсь что вы не знаете что это такое, но если не поняли, то это когда в js пишешь log, нажимаешь tab, и появляется console.log();

Все ими пользуются, но не все пишут. Так зачем о них говорить? Потому что они куда сильнее добавления нескольких символов.

Например, когда лень в пхп включать дебаг, выводить массив на страницу не очень красиво, а подключать либы для этого не хочется, то можно

jspre разворачивающийся в

?>
<script>
console.log(<?=json_encode($arResult, JSON_PARTIAL_OUTPUT_ON_ERROR)?>);
</script>
<?

И вот к вашим услугам просмотр хешмапа в консоли, допустим, хрома со всеми вытекающими. А это и возможность свернуть развернуть и весь js.

Кстати, сниппеты как правило зависят от языка. Значит закрытие этих пхп тегов идет только если нужно. А внутри html этого не надо, ну и внутри js не нужны теги скрипта.

Ну или еще маленький пример:

// ident_array
$arResult = array_combine(
	array_column($arResult, "KEY"),
	$arResult
);

Простенькая фигня делающая значение массива его ключом. В пхп этого не хватает, ну или не хватало, ведь все обновляется и улучшается, так ведь?)

Конечно всякие аяксы из jquery, или axios или даже удобная вам форма fetch.

Но это только сниппеты для языка, а когда нужно что-то для фреймворка, то тут как раз начинается самое приятное. Кривая вывела меня на bitrix, и чтобы не пугать народ примеров будет мало. Но некоторые стоит привести.

// bx_if_me - не делайте так в серьезных местах, но все же.
if ($USER->GetLogin() === "Тут мой логин") {
	 #CODE
}

// bx_prolog - эту фигню считается хорошим тоном (да) ставить почти во всех пхп файлах
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
	die();
}

// bx_includeModule - нужно очень часто, и хорошо бы переписать на новый стиль вместо CModule
/* зато повод сказать что в сниппетах есть возможность прописывать варианты по умолчанию а в некотоых IDE (sublime, vim) можно даже вставлять набор переводов в зависимости от выбранного значения */
if(!CModule::IncludeModule("${1|iblock,highloadblock,catalog,sale,pull,моя_компания.мой_модуль|}")) {
	ShowError("Модуль ${1} не установлен!");
	return;
}

// d7_iblockElementQuery - Вообще конкретно этой фигней пока пользоваться не стоит, но все юзают, так что развлекайтесь и вы
/* Кстати если все виснет, то вероятно xDebug ушел в бесконечную рекурсию */
$items = \Bitrix\Iblock\Elements\ElementCatalogTable::query() // гуглить bitrix orm инфоблоки
	->setSelect([
		"ID",
		"ALIAS" => "PROPERTY_CODE.VALUE",
	])
	->where("ID", "in", $arrayWithIds)
	->setLimit(20)
	->exec()->fetchAll();

// d7_file_reference - И все, теперь у модели будут и ссылка на модель файла и строка с src
/* это вставляется в getMap в описании модели в модуле. Просто элементы массива
Полные пути как раз потому что сниппет, но можно все выносить в use, я не против.
*/
(new \Bitrix\Main\ORM\Fields\Relations\Reference(
	"FILE",
	\Bitrix\Main\FileTable::class,
	\Bitrix\Main\ORM\Query\Join::on('this.UF_FILE', 'ref.ID')
))->configureJoinType('left'),
(new \Bitrix\Main\ORM\Fields\ExpressionField(
	'FILE_SRC',
	"CONCAT('/upload/', %s, '/', %s)",
	array('FILE.SUBDIR', 'FILE.FILE_NAME')
)),

Ну и понятно для стандартных вещей старого ядра весь CRUD сделан сниппетами, с комментариями где тут фильтр, а где сортировка. Ну и для разделов на d7 тоже сниппет. Или чтобы вставить включаемую область - сниппет. Или подключить самописный компонент обратной связи. Или меню. Ресайз картинки, ссылочное поле в d7 query, логику Or туда же. В общем статьи не хватит чтобы охватить все сокращенные нажатия на кнопочки.

А еще можно закидывать в сниппеты стандартные костыли:

// bx_ob 
/*
Один из старых способов держать некешируемое в кешируемом через замену вида 
SOME_PREFIX_123 на что-то
Сам сниппет содержит код для трех файлов, по которым его еще надо разнести
В общем костыль тот еще, но искать его в тех редких моментах, когда он нужен еще хуже.
*/
// Это идет в шаблон
ob_start();
	
$component->arResult["CACHED_TPL"] = ob_get_clean();

// Далее код для других файлов, который обычно выделен если сниппет правильно написан,
// чтобы удалить его одним del и просто использовать обычный ob
// в файл result_modifier
$this->__component->arResultCacheKeys = array_merge(
	$this->__component->arResultCacheKeys,
	['CACHED_TPL']
);

// в файл component_epilog
$arResult["CACHED_TPL"] = preg_replace_callback(
	"/#SOME_PREFIX_([\d]+)#/is".BX_UTF_PCRE_MODIFIER,
	function ($matches){
		ob_start();
			global $APPLICATION;
            // тут что-то использующее $matches и некешируемое,
            // только не надо запросы в базу, это ведь цикл ;)
		$returnStr = ob_get_clean();
		return $returnStr;
	},
	$arResult["CACHED_TPL"]
);

// Это все снова идет в шаблон
echo $this->__component->arResult["CACHED_TPL"];
// Это все был один сниппет

//------------------------------------------------------------------------

// bx_ciblock_cache
/* Если в компонент пихать не хочется, а кеш на инфоблоках все же нужен. 
Понятно что в сниппете расставлены места для правки, такие как cache_dir, iblock_id
*/
$obCache = new CPHPCache();
if ($obCache->InitCache(36000, "", "/iblock/cache_dir")) {
	$arResult = $obCache->GetVars();
} elseif ($obCache->StartDataCache()) {
	\Bitrix\Main\Loader::includeModule('iblock');

	if (defined("BX_COMP_MANAGED_CACHE")) {
		global $CACHE_MANAGER;
		$CACHE_MANAGER->StartTagCache("/iblock/cache_dir");
		$CACHE_MANAGER->RegisterTag("iblock_id_" . iblock_id);
	}

	$arResult = [];
    # Весь код тут и задает переменную $arResult, которая будет браться из кеша

	if (defined("BX_COMP_MANAGED_CACHE")) {
		$CACHE_MANAGER->EndTagCache();
	}
	$obCache->EndDataCache($arResult);
}

Плагины это как сниппеты, но лучше

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

В том же битриксе система папок и файлов даже в обычном строгом виде напоминает абстрактное искусство. Поэтому себе я писал плагин, открывающий компоненты и шаблоны по названию. Те самые, которые откроет код.
Потом в vscode делал даже меню чтобы открывать их.
Ну и заодно создавать все эти стандартные файлы, раз уж в битриксе нельзя делать просто
bitrix g component someName -component_epilog

Приводить вид всего этого я не буду, ибо в саблайм искать лень, а для всКода я нечаянно стер со всем wsl, и так и не написал заново.

Композер - тоже может расширять каждый проект

Вы ведь пользуетесь локалками? Или вам нужно чтобы кто-то поднял вам дев? Только не говорите что правите все на проде. В общем если вы пользуетесь локалками то

source:
        image: alpine:latest # не настаиваю
        container_name: ${PROJECT}source
        volumes:
          # ...
          - ${CONFIG_RELATIVE_PATH}/scripts/:/var/www/bitrix/scripts
          # ...

И в папку со скриптами вставляйте что нужно.
Например скрипт добавления вашего пользователя, авторизации под ним. Управления его группами.
А еще я туда запихнул для битрикса запекание моделей хайлоадов, но это что-то на языке Мордора.
Но если ваш фреймворк можно расширять таким способом, то это упростит жизнь. Сколько раз было, выкачиваешь проект и нужно залогиниться, и увы это не, например, рельсы и нельзя просто rails c и что-то вида users.add("login", "pass"). Точный синтаксис уточняйте в инете.

Но если у вас нету идей чего бы запихнуть в скрипты, то можно подключить несколько образов. Например

adminer: # ну или phpMyAdmin
        image: adminer
        container_name: ${PROJECT}adminer
        links:
            - db:db
        ports:
            - '${INTERFACE}:8080:8080'
        networks:
            - bitrix

# перехват почты, очень помогает в отладке писем,
# кстати можно ставить e2e тесты на письма
mailhog: 
        image: mailhog/mailhog
        container_name: ${PROJECT}mailhog
        logging:
          driver: 'none'  # disable saving logs
        networks:
            - bitrix
        ports:
          - '${INTERFACE}:1025:1025' # smtp server
          - '${INTERFACE}:8025:8025' # web ui
        restart: on-failure

Консоль - вообще огонь

Вам не поставили миграции?

function mysqlLoad {
  if [ "$REMOTE_TYPE" = "docker" ]; then
    ssh -C $PROD_SSH " cd $PROD_PATH && cd .. && docker-compose exec -T db mysqldump \"--no-tablespaces\" \"-u\" \"$MYSQL_USER\" \"-p$MYSQL_PASSWORD\" \"$MYSQL_DATABASE\" | gzip -c" | pv | gunzip | docker exec -i $PROJECT"db" "mysql" "-u$MYSQL_USER" "-p$MYSQL_PASSWORD" $MYSQL_DATABASE
  else
    ssh -C $PROD_SSH " mysqldump --no-tablespaces -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE | gzip -c" | pv | gunzip | docker exec -i $PROJECT"db" "mysql" "-u$MYSQL_USER" "-p${MYSQL_PASSWORD//\\/}" $MYSQL_DATABASE
  fi
}

Окей, понимаю страшно. Начнем попроще.

Начнем с просто backward. Или нет, рано нам еще с этого начинать, надо начать с rsync. Если вы пользуетесь гитом для выкачивания и закачивания кода, то вы наверное не прогаете на битриксе) У нас увы файлы могут редактировать люди, для которых божественные знаки G-+ (git) ничего не говорят. А затирать их правки - потом ругаться. Так что rsync поможет выкачать проект, без картинок, без кеша, без бекапа.

rsync --no-inc-recursive --info=progress2 -rza --exclude="cache" --exclude="managed_cache" --exclude="backup" "$PROD_SSH:$PROD_PATH" .

Быстро, и приятно с привычкой. Хотя бы для создания локалок.
К этому моменту меня перестали читать, но если кто-то все же прошелся по диагонали, то заметил что в моих консольных командах целая куча переменных.

Что же) Ежедневная функция oprj (open project)

function oprj {
 local files
 if [[ $1 ]]; then
   files="$1"
 else
   files=$(ls /mnt/g/work | fzf)
 fi
 if [ -n "$files" ]; then
   export BASE_DIR=/mnt/g/work/"$files"
   if [ -d /mnt/g/work/"$files"/site ]; then
     export SITE_DIR=/mnt/g/work/"$files"/site/
   else
     export SITE_DIR=/mnt/g/work/"$files"/ln/
   fi
   cd $SITE_DIR
 fi
 export WDIR="/mnt/g/work/$files/"
 if [ -f "$BASE_DIR/.env" ]; then
   echo "exporting vars from .env"
   # source "$BASE_DIR/.env"
   export MYSQL_USER=$(grep '^[\s\t]*MYSQL_USER=' "$BASE_DIR/.env" | awk -F '=' '{printf $2}' | tr -d '\r')
   export MYSQL_PASSWORD=$(grep '^[\s\t]*MYSQL_PASSWORD=' "$BASE_DIR/.env" | awk -F '=' '{printf $2}' | tr -d '\r')
   export MYSQL_DATABASE=$(grep '^[\s\t]*MYSQL_DATABASE=' "$BASE_DIR/.env" | awk -F '=' '{printf $2}' | tr -d '\r')
   export PROJECT=$(grep '^[\s\t]*PROJECT=' "$BASE_DIR/.env" | awk -F '=' '{printf $2}' | tr -d '\r')
   export REMOTE_TYPE=$(grep '^[\s\t]*REMOTE_TYPE=' "$BASE_DIR/.env" | awk -F '=' '{printf $2}' | tr -d '\r')
   export SITE_IP=$(grep '^[\s\t]*INTERFACE=' "$BASE_DIR/.env" | awk -F '=' '{printf $2}' | tr -d '\r')
   export PROD_PATH=$(cat "$BASE_DIR/remote_path" | sed -e 's/\(.*\):\(.*\)/\2/')
   export PROD_SSH=$(cat "$BASE_DIR/remote_path" | sed -e 's/\(.*\):.*/\1/') && \
   export PROD_SSH_USER=$(echo $PROD_SSH | sed -e "s/\(.*\)@.*/\1/") && \
   export PROD_SSH_HOST=$(echo $PROD_SSH | sed -e "s/.*@\(.*\)/\1/")
 fi
 echo "Opened $PROJECT prod on $PROD_SSH:$PROD_PATH"
 echo "local image is http://$SITE_IP/"
}

Думаю есть какой-то куда более простой и правильный способ собирать переменные из .env файла. Но такие штуки пишутся сначала для себя. И нейросеток тогда еще не было. В общем код показываю, а не говорю про него, и знаю как он пахнет.

Эта функция, который я пользовался минимум каждый рабочий день. Просто чтобы открыть последний проект.

oprj projectName # открыть проект
backward # обертка для rsync чтобы скачать файлы
mysqlLoad # скачать базу
dockerUp # да я сделал обертку для поднятия композера, чтобы мочь управлять докером не переходя в нужные папки
code . # запустить редактор кода

А что если вы на винде? Ну тут есть wsl. Правда с докером он дружит как кошка с собакой.
Поэтому в баше после каждой перезагрузки powershell.exe -ExecutionPolicy Bypass -f f:/winscripts/docker_wsl_11.ps1 ну а в скрипте

isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
    # Перезапуск с правами администратора
    Start-Process powershell -Verb RunAs -ArgumentList "-ExecutionPolicy Bypass -File `"$PSCommandPath`""
    Exit
}

$job1 = Start-Job { netsh interface ip add address "vEthernet (WSL)" 192.168.50.88 255.255.255.0 }
Wait-Job $job1
Receive-Job $job1

Start-Sleep -s 1

$job2 = Start-Job { wsl -d ubuntu_work -u root -- service docker start}
Wait-Job $job2
Receive-Job $job2

Start-Sleep -s 1

$job4 = Start-Job { wsl -d ubuntu_work -u root -- ip addr add 192.168.50.16/24 broadcast 192.168.50.255 dev eth0 label eth0:1}
Wait-Job $job4
Receive-Job $job4

$job10 = Start-Job { wsl -d ubuntu_work -u root -- docker ps -q `| xargs docker kill }
Wait-Job $job10
Receive-Job $job10

Start-Sleep -s 1

$job7 = Start-Job { wsl -d ubuntu_work -u root -- iptables -I DOCKER-USER -i src_if -o dst_if -j ACCEPT}
Wait-Job $job7
Receive-Job $job7

Start-Sleep -s 1

$job8 = Start-Job { route add 10.101.0.0/16 192.168.50.16 }
Wait-Job $job8
Receive-Job $job8

Любой сколько-то знающий powerShell закидает меня камнями, поэтому опишу промптом:
`Задай сетевой карточке wsl айпишник (всегда одинаковый, например для xDebug). Запусти докер внутри линукса, но прибей все контейнеры. Ну и тоже пропиши айпишник и подсеть сетевой карточке, но уже внутри wsl. Дай внешний доступ к докеру внутри wsl. И если я иду на 10.101.***.*** то значит я хочу в докер внутри моего wsl. Все делай по очереди с паузами в секунду между командами.`

После этого останется задавать проектам подсетки вида "проект 1 => 10.101.1.*", "проект 2 => 10.101.2.*"

И даже можно сделать так, чтобы какой-то проект светил в инет, но тут уже аккуратней, локалки все же, со скриптами автологина под админом.

Бонус - мощь sublime-keymap

Увы и ах, но такого я в других местах не встречал. А фишка тут в возможности выполнения макросов по кнопке, по матчам регулярок.

[ 
  //...
  // Выдели true или false и нажми ! и оно поменяется на противоположное.
  { "keys": ["!"], "command": "insert", "args": {"characters": "false"}, "context":[
      {"key": "text", "operator": "regex_match", "operand": "true", "match_all": true},
      {"key": "setting.command_mode", "operand": false}
  ]},
  { "keys": ["!"], "command": "insert", "args": {"characters": "true"}, "context":[
      {"key": "text", "operator": "regex_match", "operand": "false", "match_all": true},
      {"key": "setting.command_mode", "operand": false}
  ]},
  
  // помните <a href="javascript:void(0)">...</a>
  // так вот, можно сделать так чтобы после первой j он сам появлялся
  { "keys": ["j"], "command": "insert", "args": {"characters": "javascript:void(0)"}, "context":[
      {"key": "preceding_text", "operator": "regex_match", "operand": ".*href[\\s]*=[\\s]*[\"|']"},
      {"key": "setting.command_mode", "operand": false}
  ]},
  
  // Тут использовались макросы, а суть открывать пхп по "?"
  // Ну и быстрый <?= курсор тут ?> по "="
  // текст макросов приводить не буду, они элементарные.
  { "keys": ["="], "command": "run_macro_file", "args": {"file": "res://Packages/User/macro/php/quick_echo_php.sublime-macro"}, "context":[
      {"key": "selector", "operator": "equal", "operand": "embedding.php", "match_all":true},
      {"key": "selector", "operator": "not_equal", "operand": "meta.embedded.line.php", "match_all":true},
      {"key": "selector", "operator": "not_equal", "operand": "source.php", "match_all":true},
      {"key": "selector", "operator": "not_equal", "operand": "source.js.embedded.html", "match_all":true},
      {"key": "selector", "operator": "not_equal", "operand": "punctuation.definition.tag.end.html", "match_all":true},
      {"key": "preceding_text", "operator": "not_regex_match", "operand": ".*<"},
      {"key": "preceding_text", "operator": "not_regex_match", "operand": ".*\\?"},
      {"key": "preceding_text", "operator": "not_regex_match", "operand": ".*[a-zA-Zа-яА-Я]+"},
      {"key": "setting.command_mode", "operand": false}
  ]},
  { "keys": ["?"], "command": "run_macro_file", "args": {"file": "res://Packages/User/macro/php/quick_php.sublime-macro"}, "context":[
      {"key": "selector", "operator": "equal", "operand": "embedding.php", "match_all":true},
      {"key": "selector", "operator": "not_equal", "operand": "meta.embedded.line.php", "match_all":true},
      {"key": "selector", "operator": "not_equal", "operand": "source.php", "match_all":true},
      {"key": "selector", "operator": "not_equal", "operand": "source.js.embedded.html", "match_all":true},
      {"key": "preceding_text", "operator": "not_regex_match", "operand": ".*<"},
      {"key": "preceding_text", "operator": "not_regex_match", "operand": ".*\\?"},
      {"key": "setting.command_mode", "operand": false}
  ]},
  // ...
]

Эпилог

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

Когда ловите себя на прокрастинации перед повторением одной и той же фигни - автоматизируйте ее. Это проще чем кажется, да и весело.