{"id":323490,"date":"2021-05-20T15:01:27","date_gmt":"2021-05-20T15:01:27","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=323490"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=323490","title":{"rendered":"\u041c\u043e\u0434\u0443\u043b\u044c\u043d\u044b\u0435 frond-end \u0431\u043b\u043e\u043a\u0438 \u2014 \u043f\u0438\u0448\u0435\u043c \u0441\u0432\u043e\u0439 \u043f\u0430\u043a\u0435\u0442. \u0427\u0430\u0441\u0442\u044c 2"},"content":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/939\/ab9\/80f\/939ab980fa4e9973fb9c96a723cb379a.jpg\" width=\"2571\" height=\"750\"><figcaption><\/figcaption><\/figure>\n<p><a href=\"https:\/\/habr.com\/ru\/post\/556494\/\" rel=\"noopener noreferrer nofollow\">\u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438<\/a> \u044f \u043f\u043e\u0434\u0435\u043b\u0438\u043b\u0441\u044f \u0441\u0432\u043e\u0438\u043c \u0432\u0437\u0433\u043b\u044f\u0434\u043e\u043c \u043d\u0430 \u0442\u043e, \u043a\u0430\u043a\u0438\u043c\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 front-end \u0431\u043b\u043e\u043a\u0438, \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u0438\u0432\u043d\u0443\u044e \u043a\u0440\u0438\u0442\u0438\u043a\u0443, \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u043b \u043f\u0430\u043a\u0435\u0442 \u0438 \u0442\u0435\u043f\u0435\u0440\u044c \u0445\u043e\u0442\u0435\u043b \u0431\u044b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441 \u0432\u0430\u043c\u0438 \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439. \u041e\u043d\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043b\u0435\u0433\u043a\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u044c\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 \u0434\u043b\u044f \u043b\u044e\u0431\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441 \u0431\u0435\u043a\u0435\u043d\u0434\u043e\u043c \u043d\u0430 php.<\/p>\n<p>\u0414\u043b\u044f \u0442\u0435\u0445 \u043a\u0442\u043e \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c \u0441 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e \u044f \u0431\u0443\u0434\u0443 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043f\u043e\u0439\u043b\u0435\u0440\u044b \u0438\u0437 \u043d\u0435\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0432\u0435\u0434\u0443\u0442 \u0432 \u043a\u0443\u0440\u0441 \u0434\u0435\u043b\u0430. \u0422\u0435\u043c \u043a\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0435\u043d \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 &#8212; \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 <a href=\"#demonstration\" rel=\"noopener noreferrer nofollow\">\u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438<\/a>.<\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435<\/summary>\n<div class=\"spoiler__content\">\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044e\u0441\u044c &#8212; \u044f \u043c\u043e\u043b\u043e\u0434\u043e\u0439 \u0432\u0435\u0431 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441 \u043e\u043f\u044b\u0442\u043e\u043c \u0440\u0430\u0431\u043e\u0442\u044b 5 \u043b\u0435\u0442. \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0433\u043e\u0434 \u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e \u043d\u0430 \u0444\u0440\u0438\u043b\u0430\u043d\u0441\u0435 \u0438 \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432  \u0441\u0432\u044f\u0437\u0430\u043d\u0430 \u0441 WordPress. \u041d\u0435\u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0440\u0430\u0437\u043b\u0438\u0447\u0443\u044e \u043a\u0440\u0438\u0442\u0438\u043a\u0443 CMS \u0432 \u043e\u0431\u0449\u0435\u043c \u0438 WordPress \u0432 \u0447\u0430\u0441\u043d\u043e\u0441\u0442\u0438, \u044f \u0441\u0447\u0438\u0442\u0430\u044e \u0441\u0430\u043c\u0430 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 WordPress \u044d\u0442\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0443\u0434\u0430\u0447\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0445\u043e\u0442\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u043d\u0435 \u0431\u0435\u0437 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u043e\u0432. \u0418 \u043e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445 \u043d\u0430 \u043c\u043e\u0439 \u0432\u0437\u0433\u043b\u044f\u0434 \u044d\u0442\u043e \u0448\u0430\u0431\u043b\u043e\u043d\u044b. \u0412 \u043a\u0440\u0430\u0439\u043d\u0438\u0445 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f\u0445 \u0441\u0434\u0435\u043b\u0430\u043d\u044b \u0431\u043e\u043b\u044c\u0448\u0438\u0435  \u0448\u0430\u0433\u0438 \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u043e \u0438\u0441\u043f\u0440\u0430\u0432\u0438\u0442\u044c, \u0438 Gutenberg \u0432 \u0446\u0435\u043b\u043e\u043c \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043c\u043e\u0449\u043d\u044b\u043c  \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u043c, \u043e\u0434\u043d\u0430\u043a\u043e \u043a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e \u0432 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0435 \u0442\u0435\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u0448\u0430 \u0432 \u0448\u0430\u0431\u043b\u043e\u043d\u0430\u0445, \u0441\u0442\u0438\u043b\u044f\u0445 \u0438 \u0441\u043a\u0440\u0438\u043f\u0442\u0430\u0445, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0435\u043b\u0430\u0435\u0442 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0447\u0435\u0433\u043e-\u043b\u0438\u0431\u043e \u043a\u0440\u0430\u0439\u043d\u0435 \u0431\u043e\u043b\u0435\u0437\u043d\u0435\u043d\u043d\u044b\u043c, \u0430 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u0434\u0430 \u0437\u0430\u0447\u0430\u0441\u0442\u0443\u044e \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u043c.  \u0418\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0438 \u043f\u043e\u0434\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u043e \u043c\u0435\u043d\u044f \u043a \u0438\u0434\u0435\u0435 \u0441\u0432\u043e\u0435\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430,  \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044b \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u043b \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u043b \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u043b\u043e\u043a\u0438.  <\/p>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0432 \u0432\u0438\u0434\u0435 composer \u043f\u0430\u043a\u0435\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445  \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445, \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a WordPress.  <\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0434\u0430\u0447\u0438<\/summary>\n<div class=\"spoiler__content\">\n<p>\u041f\u043e\u043d\u044f\u0442\u0438\u0435 \u0431\u043b\u043e\u043a \u043d\u0438\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e \u0441\u0443\u0442\u0438 \u0442\u0435\u043c \u0436\u0435 \u043f\u043e\u043d\u044f\u0442\u0438\u0435\u043c \u0447\u0442\u043e \u0438 \u0431\u043b\u043e\u043a \u0432 <a href=\"https:\/\/ru.bem.info\/methodology\/\" rel=\"noopener noreferrer nofollow\">BEM \u043c\u0435\u0442\u043e\u0434\u043e\u043b\u043e\u0433\u0438\u0438<\/a>,  \u0442.\u0435. \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0433\u0440\u0443\u043f\u043f\u0430 html\/js\/css \u043a\u043e\u0434\u0430 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043e\u0434\u043d\u0443 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c.<\/p>\n<p>\u0413\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c html \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c\u0438 \u0431\u043b\u043e\u043a\u043e\u0432 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0447\u0435\u0440\u0435\u0437 php, \u0447\u0442\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043d\u0430\u0448 \u043f\u0430\u043a\u0435\u0442 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442\u044c \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0441 \u0431\u0435\u043a\u0435\u043d\u0434\u043e\u043c \u043d\u0430 php. \u0422\u0430\u043a\u0436\u0435 \u0443\u0441\u043b\u043e\u0432\u0438\u043c\u0441\u044f \u043d\u0430 \u0431\u0435\u0440\u0435\u0433\u0443 \u0447\u0442\u043e, \u043d\u0435 \u0432\u0434\u0430\u0432\u0430\u044f\u0441\u044c \u0432 \u0441\u043f\u043e\u0440\u044b, \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u0434\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u043b\u0438\u044f\u043d\u0438\u044e \u043d\u043e\u0432\u043e\u043c\u043e\u0434\u043d\u044b\u0445 \u0432\u0435\u0449\u0435\u0439, \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a css-in-js \u0438\u043b\u0438 bem-json \u0438 \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u0438\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c\u0441\u044f <s>\u044d\u043b\u044c-\u043a\u043b\u0430\u0441\u0441\u0438\u043a\u043e<\/s> \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430, \u0442.\u0435. \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c \u0447\u0442\u043e html, css \u0438 js \u044d\u0442\u043e \u0440\u0430\u0437\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b.  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0448\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u043a \u043f\u0430\u043a\u0435\u0442\u0443:<\/p>\n<ul>\n<li>\n<p>\u041e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0431\u043b\u043e\u043a\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f (\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f) \u0431\u043b\u043e\u043a\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u043b\u043e\u043a \u0432 \u0431\u043b\u043e\u043a\u0435 \u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 \u043e\u0434\u043d\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430 \u043e\u0442 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 <\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/details>\n<h2>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0430\u043a\u0435\u0442\u0430<\/h2>\n<details class=\"spoiler\">\n<summary>\u041e \u0440\u0435\u0441\u0443\u0440\u0430\u0445 \u0431\u043b\u043e\u043a\u0430 \u0438 twig \u0448\u0430\u0431\u043b\u043e\u043d\u0430\u0445<\/summary>\n<div class=\"spoiler__content\">\n<p>\u041a\u0430\u043a \u0443\u0441\u043b\u043e\u0432\u0438\u043b\u0438\u0441\u044c \u0432\u044b\u0448\u0435, \u0442\u0430\u043a\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u043a\u0430\u043a css \u0438 js \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u0443\u0434\u0443\u0442 \u0432 \u0432\u0438\u0434\u0435 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432, \u0442.\u0435. \u044d\u0442\u043e \u0431\u0443\u0434\u0443\u0442 .js \u0438 .css \u0438\u043b\u0438 .min.css \u0438 .min.js \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u0435\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0441\u043e\u0440\u043e\u0432 \u0438 \u0441\u0431\u043e\u0440\u0449\u0438\u043a\u043e\u0432 (\u043a\u0430\u043a webpack \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440). \u0414\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 html \u043a\u043e\u0434 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0442\u043e\u0440 Twig (\u0434\u043b\u044f \u0442\u0435\u0445 \u043a\u0442\u043e \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c <a href=\"https:\/\/twig.symfony.com\/\" rel=\"noopener noreferrer nofollow\">\u0441\u0441\u044b\u043b\u043a\u0430<\/a>). \u041a\u0442\u043e-\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e php \u0438 \u0441\u0430\u043c \u043f\u043e \u0441\u0435\u0431\u0435 \u0445\u043e\u0440\u043e\u0448\u0438\u0439 \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0442\u043e\u0440, \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0432\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u043f\u043e\u0440\u044b, \u043a\u0440\u043e\u043c\u0435 \u0434\u043e\u0432\u043e\u0434\u043e\u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 Twig, \u043e\u0442\u043c\u0435\u0447\u0443 \u0432\u0430\u0436\u043d\u044b\u0439 \u0434\u043b\u044f \u043c\u0435\u043d\u044f \u043f\u0443\u043d\u043a\u0442, \u0442\u043e \u0447\u0442\u043e \u043e\u043d \u0434\u0438\u0441\u0446\u0438\u043f\u043b\u0438\u043d\u0438\u0440\u0443\u0435\u0442, \u0442.\u0435. \u0437\u0430\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044f\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043e\u0442 \u0432\u044b\u0432\u043e\u0434\u0430 \u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0437\u0430\u0440\u0430\u043d\u0435\u0435, \u0438 \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e.<\/p>\n<\/div>\n<\/details>\n<ol>\n<li>\n<p>\u0411\u043b\u043e\u043a<\/p>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0431\u043b\u043e\u043a \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437:<\/p>\n<ol>\n<li>\n<p> \u0421\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 (css\/js\/twig)<\/p>\n<\/li>\n<li>\n<p>\u041a\u043b\u0430\u0441\u0441\u0430 \u0431\u043b\u043e\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f twig \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c\u0438.<\/p>\n<\/li>\n<\/ol>\n<\/li>\n<li>\n<p>\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b: Settings (\u043f\u0443\u0442\u0438 \u043a \u0431\u043b\u043e\u043a\u0430\u043c \u0438 \u0438\u0445 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430 \u0438\u043c\u0435\u043d), TwigWrapper (\u043e\u0431\u0435\u0440\u0442\u043a\u0430 \u0434\u043b\u044f Twig \u043f\u0430\u043a\u0435\u0442\u0430), BlocksLoader (\u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0432\u0441\u0435\u0445 \u0431\u043b\u043e\u043a\u043e\u0432, \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e), Helper (\u043d\u0430\u0431\u043e\u0440 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0434\u043e\u043f. \u0444\u0443\u043d\u043a\u0446\u0438\u0439)<\/p>\n<\/li>\n<li>\n<p>Renderer \u043a\u043b\u0430\u0441\u0441 &#8212; \u0441\u0432\u044f\u0437\u0443\u044e\u0449\u0438\u0439 \u043a\u043b\u0430\u0441\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0442\u044c \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0440\u0435\u043d\u0434\u0435\u0440\u0430 \u0431\u043b\u043e\u043a\u0430, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 \u0438 \u0438\u0445 \u0440\u0435\u0441\u0443\u0440\u0441\u044b (css, js)<\/p>\n<\/li>\n<\/ol>\n<h2>\u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u043a \u0431\u043b\u043e\u043a\u0430\u043c<\/h2>\n<p>\u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0438 \u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439 \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u043b\u043e\u0441\u044c, \u0442\u0435\u043f\u0435\u0440\u044c \u044d\u0442\u043e:<\/p>\n<ul>\n<li>\n<p>php 7.4<\/p>\n<\/li>\n<li>\n<p>\u041a\u043b\u0430\u0441\u0441\u044b \u0431\u043b\u043e\u043a\u043e\u0432 \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u043c\u0435\u0442\u044c PSR-4 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0435 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u043e \u0438\u043c\u0435\u043d \u0441 \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a\u043e\u043c (<a href=\"https:\/\/www.php-fig.org\/psr\/psr-4\/\" rel=\"noopener noreferrer nofollow\">PSR-4<\/a> \u0434\u0435 \u0444\u0430\u043a\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442, \u0435\u0441\u043b\u0438 \u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a \u043e\u0442 composer, \u0442.\u0435. \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0435 autoload\/psr4 \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0443 \u0432 \u0432\u0430\u0448\u0435\u043c composer.json \u0442\u043e \u0432\u0430\u0448 \u043f\u0440\u043e\u0435\u043a\u0442 \u0443\u0436\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u044d\u0442\u043e\u043c\u0443 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044e)  <\/p>\n<\/li>\n<li>\n<p>\u0418\u043c\u0435\u043d\u0430 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c \u0441 \u0438\u043c\u0435\u043d\u0435\u043c \u0431\u043b\u043e\u043a\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0434\u043b\u044f Button.php \u0431\u0443\u0434\u0443\u0442 Button.css \u0438 Button.twig)<\/p>\n<\/li>\n<\/ul>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h2>\n<p>\u041d\u0438\u0436\u0435 \u0447\u0430\u0441\u0442\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 (\u043a\u043b\u0430\u0441\u0441\u044b) \u0431\u0443\u0434\u0443\u0442 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 : \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435, \u043a\u043e\u0434 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043a\u043e\u0434 \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<p><strong>Block<\/strong><\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043a\u043b\u0430\u0441\u0441, \u0435\u0433\u043e \u043f\u043e\u0442\u043e\u043c\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f twig \u0448\u0430\u0431\u043b\u043e\u043d\u0430 (\u0432 protected \u043f\u043e\u043b\u044f\u0445) \u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043c\u044b \u0441\u043c\u043e\u0436\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u0443\u0442\u044c \u043a \u0440\u0435\u0441\u0443\u0440\u0441\u0430\u043c (\u0448\u0430\u0431\u043b\u043e\u043d\u0443, \u0441\u0442\u0438\u043b\u044f\u043c). \u0412\u0441\u0435 \u043d\u0430\u0448\u0430 \u043c\u0430\u0433\u0438\u044f \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u043f\u043e\u043b\u044f\u043c\u0438 \u0431\u0443\u0434\u0435\u0442 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u2018get_class_vars\u2019 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442 \u0438\u043c\u0435\u043d\u0430 \u043f\u043e\u043b\u0435\u0439 \u043a\u043b\u0430\u0441\u0441\u0430 \u0438 \u043d\u0430 \u2018ReflectionProperty\u2019 \u043a\u043b\u0430\u0441\u0441\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u044d\u0442\u0438\u0445 \u043f\u043e\u043b\u044f\u0445, \u0442\u0430\u043a\u0443\u044e \u043a\u0430\u043a \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u044f (protected\/public) \u0438 \u0435\u0433\u043e \u0442\u0438\u043f. \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0442\u043e\u043b\u044c\u043a\u043e \u043e protected \u043f\u043e\u043b\u044f\u0445.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u043c \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0443\u044e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0430\u0432\u0442\u043e\u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u0435\u0439 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, \u044d\u0442\u043e \u0438\u0437\u0431\u0430\u0432\u0438\u0442 \u043d\u0430\u0441 \u043e\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u0447\u0442\u043e \u043f\u0440\u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0435 \u0431\u043b\u043e\u043a\u043e\u0432 \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 \u043d\u0430\u0448\u0435 \u0432\u0440\u0435\u043c\u044f.<\/p>\n<details class=\"spoiler\">\n<summary>Block.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks;  use Exception; use ReflectionProperty;  abstract class Block {      public const TEMPLATE_KEY_NAMESPACE = '_namespace';     public const TEMPLATE_KEY_TEMPLATE = '_template';     public const TEMPLATE_KEY_IS_LOADED = '_isLoaded';     public const RESOURCE_KEY_NAMESPACE = 'namespace';     public const RESOURCE_KEY_FOLDER = 'folder';     public const RESOURCE_KEY_RELATIVE_RESOURCE_PATH = 'relativeResourcePath';     public const RESOURCE_KEY_RELATIVE_BLOCK_PATH = 'relativeBlockPath';     public const RESOURCE_KEY_RESOURCE_NAME = 'resourceName';      private array $fieldsInfo;     private bool $isLoaded;      public function __construct()     {         $this-&gt;fieldsInfo = [];         $this-&gt;isLoaded   = false;          $this-&gt;readFieldsInfo();         $this-&gt;autoInitFields();     }      public static function onLoad()     {     }      public static function getResourceInfo(Settings $settings, string $blockClass = ''): ?array     {         \/\/ using static for child support         $blockClass = ! $blockClass ?             static::class :             $blockClass;          \/\/ e.g. $blockClass = Namespace\/Example\/Theme\/Main\/ExampleThemeMain         $resourceInfo = [             self::RESOURCE_KEY_NAMESPACE              =&gt; '',             self::RESOURCE_KEY_FOLDER                 =&gt; '',             self::RESOURCE_KEY_RELATIVE_RESOURCE_PATH =&gt; '',\/\/ e.g. Example\/Theme\/Main\/ExampleThemeMain             self::RESOURCE_KEY_RELATIVE_BLOCK_PATH    =&gt; '',\/\/ e.g. Example\/Theme\/Main             self::RESOURCE_KEY_RESOURCE_NAME          =&gt; '',\/\/ e.g. ExampleThemeMain         ];          $blockFolderInfo = $settings-&gt;getBlockFolderInfoByBlockClass($blockClass);          if (! $blockFolderInfo) {             $settings-&gt;callErrorCallback(                 [                     'error'      =&gt; 'Block has the non registered namespace',                     'blockClass' =&gt; $blockClass,                 ]             );              return null;         }          $resourceInfo[self::RESOURCE_KEY_NAMESPACE] = $blockFolderInfo['namespace'];         $resourceInfo[self::RESOURCE_KEY_FOLDER]    = $blockFolderInfo['folder'];          \/\/  e.g. Example\/Theme\/Main\/ExampleThemeMain         $relativeBlockNamespace = str_replace($resourceInfo[self::RESOURCE_KEY_NAMESPACE] . '\\\\', '', $blockClass);          \/\/ e.g. ExampleThemeMain         $blockName = explode('\\\\', $relativeBlockNamespace);         $blockName = $blockName[count($blockName) - 1];          \/\/ e.g. Example\/Theme\/Main         $relativePath = explode('\\\\', $relativeBlockNamespace);         $relativePath = array_slice($relativePath, 0, count($relativePath) - 1);         $relativePath = implode(DIRECTORY_SEPARATOR, $relativePath);          $resourceInfo[self::RESOURCE_KEY_RELATIVE_RESOURCE_PATH] = $relativePath . DIRECTORY_SEPARATOR . $blockName;         $resourceInfo[self::RESOURCE_KEY_RELATIVE_BLOCK_PATH]    = $relativePath;         $resourceInfo[self::RESOURCE_KEY_RESOURCE_NAME]          = $blockName;          return $resourceInfo;     }      private static function getResourceInfoForTwigTemplate(Settings $settings, string $blockClass): ?array     {         $resourceInfo = self::getResourceInfo($settings, $blockClass);          if (! $resourceInfo) {             return null;         }          $absTwigPath = implode(             '',             [                 $resourceInfo['folder'],                 DIRECTORY_SEPARATOR,                 $resourceInfo['relativeResourcePath'],                 $settings-&gt;getTwigExtension(),             ]         );          if (! is_file($absTwigPath)) {             $parentClass = get_parent_class($blockClass);              if ($parentClass &amp;&amp;                 is_subclass_of($parentClass, self::class) &amp;&amp;                 self::class !== $parentClass) {                 return self::getResourceInfoForTwigTemplate($settings, $parentClass);             } else {                 return null;             }         }          return $resourceInfo;     }      final public function getFieldsInfo(): array     {         return $this-&gt;fieldsInfo;     }      final public function isLoaded(): bool     {         return $this-&gt;isLoaded;     }      private function getBlockField(string $fieldName): ?Block     {         $block      = null;         $fieldsInfo = $this-&gt;fieldsInfo;          if (key_exists($fieldName, $fieldsInfo)) {             $block = $this-&gt;{$fieldName};              \/\/ prevent possible recursion by a mistake (if someone will create a field with self)             \/\/ using static for children support             $block = ($block &amp;&amp;                       $block instanceof Block &amp;&amp;                       get_class($block) !== static::class) ?                 $block :                 null;         }          return $block;     }      public function getDependencies(string $sourceClass = ''): array     {         $dependencyClasses = [];         $fieldsInfo        = $this-&gt;fieldsInfo;          foreach ($fieldsInfo as $fieldName =&gt; $fieldType) {             $dependencyBlock = $this-&gt;getBlockField($fieldName);              if (! $dependencyBlock) {                 continue;             }              $dependencyClass = get_class($dependencyBlock);              \/\/ 1. prevent the possible permanent recursion             \/\/ 2. add only unique elements, because several fields can have the same type             if (                 ($sourceClass &amp;&amp; $dependencyClass === $sourceClass) ||                 in_array($dependencyClass, $dependencyClasses, true)             ) {                 continue;             }              \/\/ used static for child support             $subDependencies = $dependencyBlock-&gt;getDependencies(static::class);             \/\/ only unique elements             $subDependencies = array_diff($subDependencies, $dependencyClasses);              \/\/ sub dependencies are before the main dependency             $dependencyClasses = array_merge($dependencyClasses, $subDependencies, [$dependencyClass,]);         }          return $dependencyClasses;     }      \/\/ can be overridden for add external arguments     public function getTemplateArgs(Settings $settings): array     {         \/\/ using static for child support         $resourceInfo = self::getResourceInfoForTwigTemplate($settings, static::class);          $pathToTemplate = $resourceInfo ?             $resourceInfo[self::RESOURCE_KEY_RELATIVE_RESOURCE_PATH] . $settings-&gt;getTwigExtension() :             '';         $namespace      = $resourceInfo[self::RESOURCE_KEY_NAMESPACE] ?? '';          $templateArgs = [             self::TEMPLATE_KEY_NAMESPACE =&gt; $namespace,             self::TEMPLATE_KEY_TEMPLATE  =&gt; $pathToTemplate,             self::TEMPLATE_KEY_IS_LOADED =&gt; $this-&gt;isLoaded,         ];          if (! $pathToTemplate) {             $settings-&gt;callErrorCallback(                 [                     'error' =&gt; 'Twig template is missing for the block',                     \/\/ using static for child support                     'class' =&gt; static::class,                 ]             );         }          foreach ($this-&gt;fieldsInfo as $fieldName =&gt; $fieldType) {             $value = $this-&gt;{$fieldName};              if ($value instanceof self) {                 $value = $value-&gt;getTemplateArgs($settings);             }              $templateArgs[$fieldName] = $value;         }          return $templateArgs;     }      protected function getFieldType(string $fieldName): ?string     {         $fieldType = null;          try {             \/\/ used static for child support             $property = new ReflectionProperty(static::class, $fieldName);         } catch (Exception $ex) {             return $fieldType;         }          if (! $property-&gt;isProtected()) {             return $fieldType;         }          return $property-&gt;getType() ?             $property-&gt;getType()-&gt;getName() :             '';     }      private function readFieldsInfo(): void     {         $fieldNames = array_keys(get_class_vars(static::class));          foreach ($fieldNames as $fieldName) {             $fieldType = $this-&gt;getFieldType($fieldName);              \/\/ only protected fields             if (is_null($fieldType)) {                 continue;             }              $this-&gt;fieldsInfo[$fieldName] = $fieldType;         }     }      private function autoInitFields(): void     {         foreach ($this-&gt;fieldsInfo as $fieldName =&gt; $fieldType) {             \/\/ ignore fields without a type             if (! $fieldType) {                 continue;             }              $defaultValue = null;              switch ($fieldType) {                 case 'int':                 case 'float':                     $defaultValue = 0;                     break;                 case 'bool':                     $defaultValue = false;                     break;                 case 'string':                     $defaultValue = '';                     break;                 case 'array':                     $defaultValue = [];                     break;             }              try {                 if (is_subclass_of($fieldType, Block::class)) {                     $defaultValue = new $fieldType();                 }             } catch (Exception $ex) {                 $defaultValue = null;             }              \/\/ ignore fields with a custom type (null by default)             if (is_null($defaultValue)) {                 continue;             }              $this-&gt;{$fieldName} = $defaultValue;         }     }      final protected function load(): void     {         $this-&gt;isLoaded = true;     }  } <\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>BlockTest.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks\\Tests\\unit;  use Codeception\\Test\\Unit; use LightSource\\FrontBlocks\\Block; use LightSource\\FrontBlocks\\Settings; use org\\bovigo\\vfs\\vfsStream; use UnitTester;  class BlockTest extends Unit {      protected UnitTester $tester;      public function testReadProtectedFields()     {         $block = new class extends Block {             protected $loadedField;         };          $this-&gt;assertEquals(             ['loadedField',],             array_keys($block-&gt;getFieldsInfo())         );     }      public function testIgnoreReadPublicFields()     {         $block = new class extends Block {             public $ignoredField;         };          $this-&gt;assertEquals(             [],             array_keys($block-&gt;getFieldsInfo())         );     }      public function testReadFieldWithType()     {         $block = new class extends Block {             protected string $loadedField;         };          $this-&gt;assertEquals(             [                 'loadedField' =&gt; 'string',             ],             $block-&gt;getFieldsInfo()         );     }      public function testReadFieldWithoutType()     {         $block = new class extends Block {             protected $loadedField;         };          $this-&gt;assertEquals(             [                 'loadedField' =&gt; '',             ],             $block-&gt;getFieldsInfo()         );     }      public function testAutoInitIntField()     {         $block = new class extends Block {              protected int $int;              public function getInt()             {                 return $this-&gt;int;             }         };          $this-&gt;assertTrue(0 === $block-&gt;getInt());     }      public function testAutoInitFloatField()     {         $block = new class extends Block {              protected float $float;              public function getFloat()             {                 return $this-&gt;float;             }         };          $this-&gt;assertTrue(0.0 === $block-&gt;getFloat());     }      public function testAutoInitStringField()     {         $block = new class extends Block {              protected string $string;              public function getString()             {                 return $this-&gt;string;             }         };          $this-&gt;assertTrue('' === $block-&gt;getString());     }      public function testAutoInitBoolField()     {         $block = new class extends Block {              protected bool $bool;              public function getBool()             {                 return $this-&gt;bool;             }         };          $this-&gt;assertTrue(false === $block-&gt;getBool());     }      public function testAutoInitArrayField()     {         $block = new class extends Block {              protected array $array;              public function getArray()             {                 return $this-&gt;array;             }         };          $this-&gt;assertTrue([] === $block-&gt;getArray());     }      public function testAutoInitBlockField()     {         $testBlock        = new class extends Block {         };         $testBlockClass   = get_class($testBlock);         $block            = new class ($testBlockClass) extends Block {              protected $block;             private $testClass;              public function __construct($testClass)             {                 $this-&gt;testClass = $testClass;                 parent::__construct();             }              public function getFieldType(string $fieldName): ?string             {                 return ('block' === $fieldName ?                     $this-&gt;testClass :                     parent::getFieldType($fieldName));             }              public function getBlock()             {                 return $this-&gt;block;             }         };         $actualBlockClass = $block-&gt;getBlock() ?             get_class($block-&gt;getBlock()) :             '';          $this-&gt;assertEquals($actualBlockClass, $testBlockClass);     }      public function testIgnoreAutoInitFieldWithoutType()     {         $block = new class extends Block {              protected $default;              public function getDefault()             {                 return $this-&gt;default;             }         };          $this-&gt;assertTrue(null === $block-&gt;getDefault());     }      public function testGetResourceInfo()     {         $settings = new Settings();         $settings-&gt;addBlocksFolder('TestNamespace', 'test-folder');         $this-&gt;assertEquals(             [                 Block::RESOURCE_KEY_NAMESPACE              =&gt; 'TestNamespace',                 Block::RESOURCE_KEY_FOLDER                 =&gt; 'test-folder',                 Block::RESOURCE_KEY_RELATIVE_RESOURCE_PATH =&gt; 'Button\/Theme\/Red\/ButtonThemeRed',                 Block::RESOURCE_KEY_RELATIVE_BLOCK_PATH    =&gt; 'Button\/Theme\/Red',                 Block::RESOURCE_KEY_RESOURCE_NAME          =&gt; 'ButtonThemeRed',             ],             Block::getResourceInfo($settings, 'TestNamespace\\\\Button\\\\Theme\\\\Red\\\\ButtonThemeRed')         );     }      public function testGetDependenciesWithSubDependenciesRecursively()     {         $spanBlock   = new class extends Block {         };         $buttonBlock = new class ($spanBlock) extends Block {              protected $spanBlock;              public function __construct($spanBlock)             {                 parent::__construct();                  $this-&gt;spanBlock = $spanBlock;             }         };         $formBlock   = new class ($buttonBlock) extends Block {              protected $buttonBlock;              public function __construct($buttonBlock)             {                 parent::__construct();                  $this-&gt;buttonBlock = $buttonBlock;             }         };          $this-&gt;assertEquals(             [                 get_class($spanBlock),                 get_class($buttonBlock),             ],             $formBlock-&gt;getDependencies()         );     }      public function testGetDependenciesInRightOrder()     {         $spanBlock   = new class extends Block {         };         $buttonBlock = new class ($spanBlock) extends Block {              protected $spanBlock;              public function __construct($spanBlock)             {                 parent::__construct();                  $this-&gt;spanBlock = $spanBlock;             }         };         $formBlock   = new class ($buttonBlock) extends Block {              protected $buttonBlock;              public function __construct($buttonBlock)             {                 parent::__construct();                  $this-&gt;buttonBlock = $buttonBlock;             }         };          $this-&gt;assertEquals(             [                 get_class($spanBlock),                 get_class($buttonBlock),             ],             $formBlock-&gt;getDependencies()         );     }      public function testGetDependenciesWhenBlocksAreDependentFromEachOther()     {         $buttonBlock = new class extends Block {              protected $formBlock;              public function __construct()             {                 parent::__construct();             }              public function setFormBlock($formBlock)             {                 $this-&gt;formBlock = $formBlock;             }          };         $formBlock   = new class ($buttonBlock) extends Block {              protected $buttonBlock;              public function __construct($buttonBlock)             {                 parent::__construct();                  $this-&gt;buttonBlock = $buttonBlock;             }         };         $buttonBlock-&gt;setFormBlock($formBlock);          $this-&gt;assertEquals(             [                 get_class($buttonBlock),             ],             $formBlock-&gt;getDependencies()         );     }      public function testGetDependenciesWithoutDuplicatesWhenSeveralWithOneType()     {         function getButtonBlock()         {             return new class extends Block {             };         }          $inputBlock = new class (getButtonBlock()) extends Block {              protected $buttonBlock;              public function __construct($buttonBlock)             {                 parent::__construct();                 $this-&gt;buttonBlock = $buttonBlock;             }         };          $formBlock = new class ($inputBlock) extends Block {              protected $inputBlock;             protected $firstButtonBlock;             protected $secondButtonBlock;              public function __construct($inputBlock)             {                 parent::__construct();                  $this-&gt;inputBlock        = $inputBlock;                 $this-&gt;firstButtonBlock  = getButtonBlock();                 $this-&gt;secondButtonBlock = getButtonBlock();             }         };          $this-&gt;assertEquals(             [                 get_class(getButtonBlock()),                 get_class($inputBlock),             ],             $formBlock-&gt;getDependencies()         );     }      public function testGetTemplateArgsWhenBlockContainsBuiltInTypes()     {         $settings    = new Settings();         $buttonBlock = new class extends Block {              protected string $name;              public function __construct()             {                 parent::__construct();                 $this-&gt;name = 'button';             }         };          $this-&gt;assertEquals(             [                 Block::TEMPLATE_KEY_NAMESPACE =&gt; '',                 Block::TEMPLATE_KEY_TEMPLATE  =&gt; '',                 Block::TEMPLATE_KEY_IS_LOADED =&gt; false,                 'name'                        =&gt; 'button',             ],             $buttonBlock-&gt;getTemplateArgs($settings)         );     }      public function testGetTemplateArgsWhenBlockContainsAnotherBlockRecursively()     {         $settings    = new Settings();         $spanBlock   = new class extends Block {              protected string $name;              public function __construct()             {                 parent::__construct();                 $this-&gt;name = 'span';             }         };         $buttonBlock = new class ($spanBlock) extends Block {              protected $spanBlock;              public function __construct($spanBlock)             {                 parent::__construct();                 $this-&gt;spanBlock = $spanBlock;             }         };         $formBlock   = new class ($buttonBlock) extends Block {              protected $buttonBlock;              public function __construct($buttonBlock)             {                 parent::__construct();                 $this-&gt;buttonBlock = $buttonBlock;             }          };          $this-&gt;assertEquals(             [                 Block::TEMPLATE_KEY_NAMESPACE =&gt; '',                 Block::TEMPLATE_KEY_TEMPLATE  =&gt; '',                 Block::TEMPLATE_KEY_IS_LOADED =&gt; false,                 'buttonBlock'                 =&gt; [                     Block::TEMPLATE_KEY_NAMESPACE =&gt; '',                     Block::TEMPLATE_KEY_TEMPLATE  =&gt; '',                     Block::TEMPLATE_KEY_IS_LOADED =&gt; false,                     'spanBlock'                   =&gt; [                         Block::TEMPLATE_KEY_NAMESPACE =&gt; '',                         Block::TEMPLATE_KEY_TEMPLATE  =&gt; '',                         Block::TEMPLATE_KEY_IS_LOADED =&gt; false,                         'name'                        =&gt; 'span',                     ],                 ],             ],             $formBlock-&gt;getTemplateArgs($settings)         );     }      public function testGetTemplateArgsWhenTemplateIsInParent()     {         $rootDirectory = $this-&gt;tester-&gt;getUniqueDirectory(__METHOD__);         $namespace     = $this-&gt;tester-&gt;getUniqueControllerNamespaceWithAutoloader(__METHOD__, $rootDirectory-&gt;url());         $blocksFolder  = vfsStream::create(             [                 'ButtonBase'  =&gt; [                     'ButtonBase.php'  =&gt; $this-&gt;tester-&gt;getBlockClassFile(                         $namespace . '\\ButtonBase',                         'ButtonBase',                         '\\\\' . Block::class                     ),                     'ButtonBase.twig' =&gt; '',                 ],                 'ButtonChild' =&gt; [                     'ButtonChild.php' =&gt; $this-&gt;tester-&gt;getBlockClassFile(                         $namespace . '\\ButtonChild',                         'ButtonChild',                         '\\\\' . $namespace . '\\ButtonBase\\ButtonBase'                     ),                 ],             ],             $rootDirectory         );           $settings = new Settings();         $settings-&gt;addBlocksFolder($namespace, $blocksFolder-&gt;url());          $buttonChildClass = $namespace . '\\ButtonChild\\ButtonChild';         $buttonChild      = new $buttonChildClass();          if (! $buttonChild instanceof Block) {             $this-&gt;fail(\"Class doesn't child to Block\");         }          $this-&gt;assertEquals(             [                 Block::TEMPLATE_KEY_NAMESPACE =&gt; $namespace,                 Block::TEMPLATE_KEY_TEMPLATE  =&gt; 'ButtonBase\/ButtonBase.twig',                 Block::TEMPLATE_KEY_IS_LOADED =&gt; false,             ],             $buttonChild-&gt;getTemplateArgs($settings)         );     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<p><strong>BlocksLoader<\/strong><\/p>\n<p>\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u0443\u044e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0432\u0441\u0435\u0445 \u0431\u043b\u043e\u043a\u043e\u0432, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u043d \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043c\u0435\u0442\u043e\u0434 ::onLoad, \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043e \u0434\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438 ajax \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438 \u0442.\u0434.<\/p>\n<details class=\"spoiler\">\n<summary>BlocksLoader.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks;  class BlocksLoader {      private array $loadedBlockClasses;     private Settings $settings;      public function __construct(Settings $settings)     {         $this-&gt;loadedBlockClasses = [];         $this-&gt;settings           = $settings;     }      final public function getLoadedBlockClasses(): array     {         return $this-&gt;loadedBlockClasses;     }      private function tryToLoadBlock(string $phpClass): bool     {         $isLoaded = false;          if (             ! class_exists($phpClass, true) ||             ! is_subclass_of($phpClass, Block::class)         ) {             \/\/ without any error, because php files can contain other things             return $isLoaded;         }          call_user_func([$phpClass, 'onLoad']);          return true;     }      private function loadBlocks(string $namespace, array $phpFileNames): void     {         foreach ($phpFileNames as $phpFileName) {             $phpClass = implode('\\\\', [$namespace, str_replace('.php', '', $phpFileName),]);              if (! $this-&gt;tryToLoadBlock($phpClass)) {                 continue;             }              $this-&gt;loadedBlockClasses[] = $phpClass;         }     }      private function loadDirectory(string $directory, string $namespace): void     {         \/\/ exclude ., ..         $fs = array_diff(scandir($directory), ['.', '..']);          $phpFilePreg = '\/.php$\/';          $phpFileNames      = Helper::arrayFilter(             $fs,             function ($f) use ($phpFilePreg) {                 return (1 === preg_match($phpFilePreg, $f));             },             false         );         $subDirectoryNames = Helper::arrayFilter(             $fs,             function ($f) {                 return false === strpos($f, '.');             },             false         );          foreach ($subDirectoryNames as $subDirectoryName) {             $subDirectory = implode(DIRECTORY_SEPARATOR, [$directory, $subDirectoryName]);             $subNamespace = implode('\\\\', [$namespace, $subDirectoryName]);              $this-&gt;loadDirectory($subDirectory, $subNamespace);         }          $this-&gt;loadBlocks($namespace, $phpFileNames);     }      final public function loadAllBlocks(): void     {         $blockFoldersInfo = $this-&gt;settings-&gt;getBlockFoldersInfo();          foreach ($blockFoldersInfo as $namespace =&gt; $folder) {             $this-&gt;loadDirectory($folder, $namespace);         }     }  }<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>BlocksLoaderTest.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks\\Tests\\unit;  use Codeception\\Test\\Unit; use LightSource\\FrontBlocks\\Block; use LightSource\\FrontBlocks\\BlocksLoader; use LightSource\\FrontBlocks\\Settings; use org\\bovigo\\vfs\\vfsStream; use UnitTester;  class BlocksLoaderTest extends Unit {      protected UnitTester $tester;      public function testLoadAllBlocksWhichChildToBlock()     {         $rootDirectory = $this-&gt;tester-&gt;getUniqueDirectory(__METHOD__);         $namespace     = $this-&gt;tester-&gt;getUniqueControllerNamespaceWithAutoloader(__METHOD__, $rootDirectory-&gt;url());         $blocksFolder  = vfsStream::create(             [                 'ButtonBase'  =&gt; [                     'ButtonBase.php' =&gt; $this-&gt;tester-&gt;getBlockClassFile(                         $namespace . '\\ButtonBase',                         'ButtonBase',                         '\\\\' . Block::class                     ),                 ],                 'ButtonChild' =&gt; [                     'ButtonChild.php' =&gt; $this-&gt;tester-&gt;getBlockClassFile(                         $namespace . '\\ButtonChild',                         'ButtonChild',                         '\\\\' . $namespace . '\\ButtonBase\\ButtonBase'                     ),                 ],             ],             $rootDirectory         );          $settings = new Settings();         $settings-&gt;addBlocksFolder($namespace, $blocksFolder-&gt;url());          $blocksLoader = new BlocksLoader($settings);         $blocksLoader-&gt;loadAllBlocks();          $this-&gt;assertEquals(             [                 $namespace . '\\ButtonBase\\ButtonBase',                 $namespace . '\\ButtonChild\\ButtonChild',             ],             $blocksLoader-&gt;getLoadedBlockClasses()         );     }      public function testLoadAllBlocksIgnoreNonChild()     {         $rootDirectory = $this-&gt;tester-&gt;getUniqueDirectory(__METHOD__);         $namespace     = $this-&gt;tester-&gt;getUniqueControllerNamespaceWithAutoloader(__METHOD__, $rootDirectory-&gt;url());         $blocksFolder  = vfsStream::create(             [                 'ButtonBase' =&gt; [                     'ButtonBase.php' =&gt; '&lt;?php use ' . $namespace . '; class ButtonBase{}',                 ],             ],             $rootDirectory         );          $settings = new Settings();         $settings-&gt;addBlocksFolder($namespace, $blocksFolder-&gt;url());          $blocksLoader = new BlocksLoader($settings);         $blocksLoader-&gt;loadAllBlocks();          $this-&gt;assertEmpty($blocksLoader-&gt;getLoadedBlockClasses());     }      public function testLoadAllBlocksInSeveralFolders()     {         $rootDirectory   = $this-&gt;tester-&gt;getUniqueDirectory(__METHOD__);         $firstFolderUrl  = $rootDirectory-&gt;url() . '\/First';         $secondFolderUrl = $rootDirectory-&gt;url() . '\/Second';         $firstNamespace  = $this-&gt;tester-&gt;getUniqueControllerNamespaceWithAutoloader(             __METHOD__ . '_first',             $firstFolderUrl,         );         $secondNamespace = $this-&gt;tester-&gt;getUniqueControllerNamespaceWithAutoloader(             __METHOD__ . '_second',             $secondFolderUrl,         );         vfsStream::create(             [                 'First'  =&gt; [                     'ButtonBase' =&gt; [                         'ButtonBase.php' =&gt; $this-&gt;tester-&gt;getBlockClassFile(                             $firstNamespace . '\\ButtonBase',                             'ButtonBase',                             '\\\\' . Block::class                         ),                     ],                 ],                 'Second' =&gt; [                     'ButtonBase' =&gt; [                         'ButtonBase.php' =&gt; $this-&gt;tester-&gt;getBlockClassFile(                             $secondNamespace . '\\ButtonBase',                             'ButtonBase',                             '\\\\' . Block::class                         ),                     ],                 ],             ],             $rootDirectory         );          $settings = new Settings();         $settings-&gt;addBlocksFolder($firstNamespace, $firstFolderUrl);         $settings-&gt;addBlocksFolder($secondNamespace, $secondFolderUrl);          $blocksLoader = new BlocksLoader($settings);         $blocksLoader-&gt;loadAllBlocks();          $this-&gt;assertEquals(             [                 $firstNamespace . '\\ButtonBase\\ButtonBase',                 $secondNamespace . '\\ButtonBase\\ButtonBase',             ],             $blocksLoader-&gt;getLoadedBlockClasses()         );     } }<\/code><\/pre>\n<\/div>\n<\/details>\n<p><strong>Renderer<\/strong><\/p>\n<p>\u0421\u0432\u044f\u0437\u0443\u044e\u0449\u0438\u0439 \u043a\u043b\u0430\u0441\u0441, \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u0442 \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0440\u0435\u043d\u0434\u0435\u0440\u0430 \u0431\u043b\u043e\u043a\u0430, \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 \u0438 \u0438\u0445 \u0440\u0435\u0441\u0443\u0440\u0441\u044b (css, js)<\/p>\n<details class=\"spoiler\">\n<summary>Renderer.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks;  class Renderer {      private Settings $settings;     private TwigWrapper $twigWrapper;     private BlocksLoader $blocksLoader;     private array $usedBlockClasses;      public function __construct(Settings $settings)     {         $this-&gt;settings         = $settings;         $this-&gt;twigWrapper             = new TwigWrapper($settings);         $this-&gt;blocksLoader     = new BlocksLoader($settings);         $this-&gt;usedBlockClasses = [];     }      final public function getSettings(): Settings     {         return $this-&gt;settings;     }      final public function getTwigWrapper(): TwigWrapper     {         return $this-&gt;twigWrapper;     }      final public function getBlocksLoader(): BlocksLoader     {         return $this-&gt;blocksLoader;     }      final public function getUsedBlockClasses(): array     {         return $this-&gt;usedBlockClasses;     }      final public function getUsedResources(string $extension, bool $isIncludeSource = false): string     {         $resourcesContent = '';          foreach ($this-&gt;usedBlockClasses as $usedBlockClass) {             $getResourcesInfoCallback = [$usedBlockClass, 'getResourceInfo'];              if (! is_callable($getResourcesInfoCallback)) {                 $this-&gt;settings-&gt;callErrorCallback(                     [                         'message' =&gt; \"Block class doesn't exist\",                         'class'   =&gt; $usedBlockClass,                     ]                 );                  continue;             }              $resourceInfo = call_user_func_array(                 $getResourcesInfoCallback,                 [                     $this-&gt;settings,                 ]             );              $pathToResourceFile = $resourceInfo['folder'] .                                   DIRECTORY_SEPARATOR . $resourceInfo['relativeResourcePath'] . $extension;              if (! is_file($pathToResourceFile)) {                 continue;             }              $resourcesContent .= $isIncludeSource ?                 \"\\n\/* \" . $resourceInfo['resourceName'] . \" *\/\\n\" :                 '';              $resourcesContent .= file_get_contents($pathToResourceFile);         }          return $resourcesContent;     }      final public function render(Block $block, array $args = [], bool $isPrint = false): string     {         $dependencies           = array_merge($block-&gt;getDependencies(), [get_class($block),]);         $newDependencies        = array_diff($dependencies, $this-&gt;usedBlockClasses);         $this-&gt;usedBlockClasses = array_merge($this-&gt;usedBlockClasses, $newDependencies);          $templateArgs           = $block-&gt;getTemplateArgs($this-&gt;settings);         $templateArgs           = Helper::arrayMergeRecursive($templateArgs, $args);          $namespace              = $templateArgs[Block::TEMPLATE_KEY_NAMESPACE];         $relativePathToTemplate = $templateArgs[Block::TEMPLATE_KEY_TEMPLATE];          \/\/ log already exists         if (! $relativePathToTemplate) {             return '';         }          return $this-&gt;twigWrapper-&gt;render($namespace, $relativePathToTemplate, $templateArgs, $isPrint);     }  } <\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>RendererTest.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks\\Tests\\unit;  use Codeception\\Test\\Unit; use LightSource\\FrontBlocks\\Block; use LightSource\\FrontBlocks\\Renderer; use LightSource\\FrontBlocks\\Settings; use org\\bovigo\\vfs\\vfsStream; use UnitTester;  class RendererTest extends Unit {      protected UnitTester $tester;      public function testRenderAddsBlockToUsedList()     {         $settings = new Settings();         $renderer = new Renderer($settings);          $button = new class extends Block {         };          $renderer-&gt;render($button);          $this-&gt;assertEquals(             [                 get_class($button),             ],             $renderer-&gt;getUsedBlockClasses()         );     }      public function testRenderAddsBlockDependenciesToUsedList()     {         $settings = new Settings();         $renderer = new Renderer($settings);          $button = new class extends Block {         };         $form   = new class ($button) extends Block {              protected $button;              public function __construct($button)             {                 parent::__construct();                 $this-&gt;button = $button;             }         };          $renderer-&gt;render($form);          $this-&gt;assertEquals(             [                 get_class($button),                 get_class($form),             ],             $renderer-&gt;getUsedBlockClasses()         );     }      public function testRenderAddsDependenciesBeforeBlockToUsedList()     {         $settings = new Settings();         $renderer = new Renderer($settings);          $button = new class extends Block {         };         $form   = new class ($button) extends Block {              protected $button;              public function __construct($button)             {                 parent::__construct();                 $this-&gt;button = $button;             }         };          $renderer-&gt;render($form);          $this-&gt;assertEquals(             [                 get_class($button),                 get_class($form),             ],             $renderer-&gt;getUsedBlockClasses()         );     }      public function testRenderAddsBlockToUsedListOnce()     {         $settings = new Settings();         $renderer = new Renderer($settings);          $button = new class extends Block {         };          $renderer-&gt;render($button);         $renderer-&gt;render($button);          $this-&gt;assertEquals(             [                 get_class($button),             ],             $renderer-&gt;getUsedBlockClasses()         );     }      public function testRenderAddsBlockDependenciesToUsedListOnce()     {         $settings = new Settings();         $renderer = new Renderer($settings);          $button = new class extends Block {         };         $form   = new class ($button) extends Block {              protected $button;              public function __construct($button)             {                 parent::__construct();                 $this-&gt;button = $button;             }         };         $footer = new class ($button) extends Block {              protected $button;              public function __construct($button)             {                 parent::__construct();                 $this-&gt;button = $button;             }         };          $renderer-&gt;render($form);         $renderer-&gt;render($footer);          $this-&gt;assertEquals(             [                 get_class($button),                 get_class($form),                 get_class($footer),             ],             $renderer-&gt;getUsedBlockClasses()         );     }      public function testGetUsedResources()     {         $rootDirectory = $this-&gt;tester-&gt;getUniqueDirectory(__METHOD__);         $namespace     = $this-&gt;tester-&gt;getUniqueControllerNamespaceWithAutoloader(__METHOD__, $rootDirectory-&gt;url());         $blocksFolder  = vfsStream::create(             [                 'Button' =&gt; [                     'Button.php' =&gt; $this-&gt;tester-&gt;getBlockClassFile(                         $namespace . '\\Button',                         'Button',                         '\\\\' . Block::class                     ),                     'Button.css' =&gt; '.button{}',                 ],                 'Form'   =&gt; [                     'Form.php' =&gt; $this-&gt;tester-&gt;getBlockClassFile(                         $namespace . '\\Form',                         'Form',                         '\\\\' . Block::class                     ),                     'Form.css' =&gt; '.form{}',                 ],             ],             $rootDirectory         );          $formClass   = $namespace . '\\Form\\Form';         $form        = new $formClass();         $buttonClass = $namespace . '\\Button\\Button';         $button      = new $buttonClass();          $settings = new Settings();         $settings-&gt;addBlocksFolder($namespace, $blocksFolder-&gt;url());         $renderer = new Renderer($settings);          $renderer-&gt;render($button);         $renderer-&gt;render($form);          $this-&gt;assertEquals('.button{}.form{}', $renderer-&gt;getUsedResources('.css'));     }      public function testGetUsedResourcesWithIncludedSource()     {         $rootDirectory = $this-&gt;tester-&gt;getUniqueDirectory(__METHOD__);         $namespace     = $this-&gt;tester-&gt;getUniqueControllerNamespaceWithAutoloader(__METHOD__, $rootDirectory-&gt;url());         $blocksFolder  = vfsStream::create(             [                 'Button' =&gt; [                     'Button.php' =&gt; $this-&gt;tester-&gt;getBlockClassFile(                         $namespace . '\\Button',                         'Button',                         '\\\\' . Block::class                     ),                     'Button.css' =&gt; '.button{}',                 ],                 'Form'   =&gt; [                     'Form.php' =&gt; $this-&gt;tester-&gt;getBlockClassFile(                         $namespace . '\\Form',                         'Form',                         '\\\\' . Block::class                     ),                     'Form.css' =&gt; '.form{}',                 ],             ],             $rootDirectory         );          $formClass   = $namespace . '\\Form\\Form';         $form        = new $formClass();         $buttonClass = $namespace . '\\Button\\Button';         $button      = new $buttonClass();          $settings = new Settings();         $settings-&gt;addBlocksFolder($namespace, $blocksFolder-&gt;url());         $renderer = new Renderer($settings);          $renderer-&gt;render($button);         $renderer-&gt;render($form);          $this-&gt;assertEquals(             \"\\n\/* Button *\/\\n.button{}\\n\/* Form *\/\\n.form{}\",             $renderer-&gt;getUsedResources('.css', true)         );     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<p><strong>Settings<\/strong><\/p>\n<p>\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441, \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u044d\u0442\u043e \u043f\u0443\u0442\u0438 \u043a \u0431\u043b\u043e\u043a\u0430\u043c \u0438 \u0438\u0445 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430 \u0438\u043c\u0435\u043d<\/p>\n<details class=\"spoiler\">\n<summary>Settings.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks;  class Settings {      private array $blockFoldersInfo;     private array $twigArgs;     private string $twigExtension;     private $errorCallback;      public function __construct()     {         $this-&gt;blockFoldersInfo = [];         $this-&gt;twigArgs         = [             \/\/ will generate exception if a var doesn't exist instead of replace to NULL             'strict_variables' =&gt; true,             \/\/ disable autoescape to prevent break data             'autoescape'       =&gt; false,         ];         $this-&gt;twigExtension    = '.twig';         $this-&gt;errorCallback    = null;     }      public function addBlocksFolder(string $namespace, string $folder): void     {         $this-&gt;blockFoldersInfo[$namespace] = $folder;     }      public function setTwigArgs(array $twigArgs): void     {         $this-&gt;twigArgs = array_merge($this-&gt;twigArgs, $twigArgs);     }      public function setErrorCallback(?callable $errorCallback): void     {         $this-&gt;errorCallback = $errorCallback;     }      public function setTwigExtension(string $twigExtension): void     {         $this-&gt;twigExtension = $twigExtension;     }      public function getBlockFoldersInfo(): array     {         return $this-&gt;blockFoldersInfo;     }      public function getBlockFolderInfoByBlockClass(string $blockClass): ?array     {         foreach ($this-&gt;blockFoldersInfo as $blockNamespace =&gt; $blockFolder) {             if (0 !== strpos($blockClass, $blockNamespace)) {                 continue;             }              return [                 'namespace' =&gt; $blockNamespace,                 'folder'    =&gt; $blockFolder,             ];         }          return null;     }      public function getTwigArgs(): array     {         return $this-&gt;twigArgs;     }      public function getTwigExtension(): string     {         return $this-&gt;twigExtension;     }      public function callErrorCallback(array $errors): void     {         if (! is_callable($this-&gt;errorCallback)) {             return;         }          call_user_func_array($this-&gt;errorCallback, [$errors,]);     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>SettingsTest.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks\\Tests\\unit;  use Codeception\\Test\\Unit; use LightSource\\FrontBlocks\\Settings;  class SettingsTest extends Unit {     public function testGetBlockFolderInfoByBlockClass()     {         $settings = new Settings();         $settings-&gt;addBlocksFolder('TestNamespace', 'test-folder');         $this-&gt;assertEquals(             [                 'namespace' =&gt; 'TestNamespace',                 'folder'    =&gt; 'test-folder',             ],             $settings-&gt;getBlockFolderInfoByBlockClass('TestNamespace\\Class')         );     }      public function testGetBlockFolderInfoByBlockClassWhenSeveral()     {         $settings = new Settings();         $settings-&gt;addBlocksFolder('FirstNamespace', 'first-namespace');         $settings-&gt;addBlocksFolder('SecondNamespace', 'second-namespace');         $this-&gt;assertEquals(             [                 'namespace' =&gt; 'FirstNamespace',                 'folder'    =&gt; 'first-namespace',             ],             $settings-&gt;getBlockFolderInfoByBlockClass('FirstNamespace\\Class')         );     }      public function testGetBlockFolderInfoByBlockClassIgnoreWrong()     {         $settings = new Settings();         $settings-&gt;addBlocksFolder('TestNamespace', 'test-folder');         $this-&gt;assertEquals(             null,             $settings-&gt;getBlockFolderInfoByBlockClass('WrongNamespace\\Class')         );     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<p><strong>TwigWrapper<\/strong><\/p>\n<p>\u041a\u043b\u0430\u0441\u0441 \u043e\u0431\u0435\u0440\u0442\u043a\u0430 \u0434\u043b\u044f Twig \u043f\u0430\u043a\u0435\u0442\u0430, \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u0441 \u0448\u0430\u0431\u043b\u043e\u043d\u0430\u043c\u0438. \u0422\u0430\u043a\u0436\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0438\u043b\u0438 twig \u0441\u0432\u043e\u0435\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439 _include (\u043a\u043e\u0442\u043e\u0440\u0430\u044f \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u0431\u0435\u0440\u0442\u043a\u043e\u0439 \u0434\u043b\u044f \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u0433\u043e include \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043d\u0430\u0448\u0438 \u043f\u043e\u043b\u044f _isLoaded \u0438 _template \u0438\u0437 \u043c\u0435\u0442\u043e\u0434\u0430 Block-&gt;getTemplateArgs \u0432\u044b\u0448\u0435) \u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u043c _merge (\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u0442\u0435\u043c, \u0447\u0442\u043e \u0440\u0435\u043a\u0443\u0440\u0441\u0438\u0432\u043d\u043e \u0441\u043b\u0438\u0432\u0430\u0435\u0442 \u043c\u0430\u0441\u0441\u0438\u0432\u044b).<\/p>\n<details class=\"spoiler\">\n<summary>TwigWrapper.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks;  use Exception; use Twig\\Environment; use Twig\\Loader\\FilesystemLoader; use Twig\\Loader\\LoaderInterface; use Twig\\TwigFilter; use Twig\\TwigFunction;  class TwigWrapper {      private ?LoaderInterface $twigLoader;     private ?Environment $twigEnvironment;     private Settings $settings;      public function __construct(Settings $settings, ?LoaderInterface $twigLoader = null)     {         $this-&gt;twigEnvironment = null;         $this-&gt;settings        = $settings;         $this-&gt;twigLoader      = $twigLoader;          $this-&gt;init();     }      private static function GetTwigNamespace(string $namespace)     {         return str_replace('\\\\', '_', $namespace);     }      \/\/ e.g for extend a twig with adding a new filter     public function getEnvironment(): ?Environment     {         return $this-&gt;twigEnvironment;     }      private function extendTwig(): void     {         $this-&gt;twigEnvironment-&gt;addFilter(             new TwigFilter(                 '_merge',                 function ($source, $additional) {                     return Helper::arrayMergeRecursive($source, $additional);                 }             )         );         $this-&gt;twigEnvironment-&gt;addFunction(             new TwigFunction(                 '_include',                 function ($block, $args = []) {                     $block = Helper::arrayMergeRecursive($block, $args);                      return $block[Block::TEMPLATE_KEY_IS_LOADED] ?                         $this-&gt;render(                             $block[Block::TEMPLATE_KEY_NAMESPACE],                             $block[Block::TEMPLATE_KEY_TEMPLATE],                             $block                         ) :                         '';                 }             )         );     }      private function init(): void     {         $blockFoldersInfo = $this-&gt;settings-&gt;getBlockFoldersInfo();          try {             \/\/ can be already init (in tests)             if (! $this-&gt;twigLoader) {                 $this-&gt;twigLoader = new FilesystemLoader();                 foreach ($blockFoldersInfo as $namespace =&gt; $folder) {                     $this-&gt;twigLoader-&gt;addPath($folder, self::GetTwigNamespace($namespace));                 }             }              $this-&gt;twigEnvironment = new Environment($this-&gt;twigLoader, $this-&gt;settings-&gt;getTwigArgs());         } catch (Exception $ex) {             $this-&gt;twigEnvironment = null;              $this-&gt;settings-&gt;callErrorCallback(                 [                     'message' =&gt; $ex-&gt;getMessage(),                     'file'    =&gt; $ex-&gt;getFile(),                     'line'    =&gt; $ex-&gt;getLine(),                     'trace'   =&gt; $ex-&gt;getTraceAsString(),                 ]             );              return;         }          $this-&gt;extendTwig();     }      public function render(string $namespace, string $template, array $args = [], bool $isPrint = false): string     {         $html = '';          \/\/ twig isn't loaded         if (is_null($this-&gt;twigEnvironment)) {             return $html;         }          \/\/ can be empty, e.g. for tests         $twigNamespace = $namespace ?             '@' . self::GetTwigNamespace($namespace) . '\/' :             '';          try {             \/\/ will generate ean exception if a template doesn't exist OR broken             \/\/ also if a var doesn't exist (if using a 'strict_variables' flag, see Twig_Environment-&gt;__construct)             $html .= $this-&gt;twigEnvironment-&gt;render($twigNamespace . $template, $args);         } catch (Exception $ex) {             $html = '';              $this-&gt;settings-&gt;callErrorCallback(                 [                     'message'  =&gt; $ex-&gt;getMessage(),                     'file'     =&gt; $ex-&gt;getFile(),                     'line'     =&gt; $ex-&gt;getLine(),                     'trace'    =&gt; $ex-&gt;getTraceAsString(),                     'template' =&gt; $template,                 ]             );         }          if ($isPrint) {             echo $html;         }          return $html;     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>TwigWrapperTest.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks\\Tests\\unit;  use Codeception\\Test\\Unit; use LightSource\\FrontBlocks\\Block; use LightSource\\FrontBlocks\\Settings; use LightSource\\FrontBlocks\\TwigWrapper; use Twig\\Loader\\ArrayLoader;  class TwigWrapperTest extends Unit {      private function renderBlock(array $blocks, string $template, array $renderArgs = []): string     {         $twigLoader = new ArrayLoader($blocks);         $settings   = new Settings();         $twig       = new TwigWrapper($settings, $twigLoader);          return $twig-&gt;render('', $template, $renderArgs);     }      public function testExtendTwigIncludeFunctionWhenBlockIsLoaded()     {         $blocks     = [             'form.twig'   =&gt; '{{ _include(button) }}',             'button.twig' =&gt; 'button content',         ];         $template   = 'form.twig';         $renderArgs = [             'button' =&gt; [                 Block::TEMPLATE_KEY_NAMESPACE =&gt; '',                 Block::TEMPLATE_KEY_TEMPLATE  =&gt; 'button.twig',                 Block::TEMPLATE_KEY_IS_LOADED =&gt; true,             ],         ];          $this-&gt;assertEquals('button content', $this-&gt;renderBlock($blocks, $template, $renderArgs));     }      public function testExtendTwigIncludeFunctionWhenBlockNotLoaded()     {         $blocks     = [             'form.twig'   =&gt; '{{ _include(button) }}',             'button.twig' =&gt; 'button content',         ];         $template   = 'form.twig';         $renderArgs = [             'button' =&gt; [                 Block::TEMPLATE_KEY_NAMESPACE =&gt; '',                 Block::TEMPLATE_KEY_TEMPLATE  =&gt; 'button.twig',                 Block::TEMPLATE_KEY_IS_LOADED =&gt; false,             ],         ];          $this-&gt;assertEquals('', $this-&gt;renderBlock($blocks, $template, $renderArgs));     }      public function testExtendTwigIncludeFunctionWhenArgsPassed()     {         $blocks     = [             'form.twig'   =&gt; '{{ _include(button,{classes:[\"test-class\",],}) }}',             'button.twig' =&gt; '{{ classes|join(\" \") }}',         ];         $template   = 'form.twig';         $renderArgs = [             'button' =&gt; [                 Block::TEMPLATE_KEY_NAMESPACE =&gt; '',                 Block::TEMPLATE_KEY_TEMPLATE  =&gt; 'button.twig',                 Block::TEMPLATE_KEY_IS_LOADED =&gt; true,                 'classes'                     =&gt; ['own-class',],             ],         ];          $this-&gt;assertEquals('own-class test-class', $this-&gt;renderBlock($blocks, $template, $renderArgs));     }      public function testExtendTwigMergeFilter()     {         $blocks     = [             'button.twig' =&gt; '{{ {\"array\":[\"first\",],}|_merge({\"array\":[\"second\",],}).array|join(\" \") }}',         ];         $template   = 'button.twig';         $renderArgs = [];          $this-&gt;assertEquals('first second', $this-&gt;renderBlock($blocks, $template, $renderArgs));     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<p><strong>Helper<\/strong><\/p>\n<p>\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441, \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u0438\u0448\u044c \u043f\u0430\u0440\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u043e\u0441\u0442\u0430\u0432\u043b\u044e \u0431\u0435\u0437 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0435\u0432.<\/p>\n<details class=\"spoiler\">\n<summary>Helper.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks;  abstract class Helper {      final public static function arrayFilter(array $array, callable $callback, bool $isSaveKeys): array     {         $arrayResult = array_filter($array, $callback);          return $isSaveKeys ?             $arrayResult :             array_values($arrayResult);     }      final public static function arrayMergeRecursive(array $args1, array $args2): array     {         foreach ($args2 as $key =&gt; $value) {             if (intval($key) === $key) {                 $args1[] = $value;                  continue;             }              \/\/ recursive sub-merge for internal arrays             if (                 is_array($value) &amp;&amp;                 key_exists($key, $args1) &amp;&amp;                 is_array($args1[$key])             ) {                 $value = self::arrayMergeRecursive($args1[$key], $value);             }              $args1[$key] = $value;         }          return $args1;     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>HelperTest.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks\\Tests\\unit;  use Codeception\\Test\\Unit; use LightSource\\FrontBlocks\\Helper;  class HelperTest extends Unit {      public function testArrayFilterWithoutSaveKeys()     {         $this-&gt;assertEquals(             [                 0 =&gt; '2',             ],             Helper::ArrayFilter(                 ['1', '2'],                 function ($value) {                     return '1' !== $value;                 },                 false             )         );     }      public function testArrayFilterWithSaveKeys()     {         $this-&gt;assertEquals(             [                 1 =&gt; '2',             ],             Helper::ArrayFilter(                 ['1', '2'],                 function ($value) {                     return '1' !== $value;                 },                 true             )         );     }      public function testArrayMergeRecursive()     {         $this-&gt;assertEquals(             [                 'classes' =&gt; [                     'first',                     'second',                 ],                 'value'   =&gt; 2,             ],             Helper::arrayMergeRecursive(                 [                     'classes' =&gt; [                         'first',                     ],                     'value'   =&gt; 1,                 ],                 [                     'classes' =&gt; [                         'second',                     ],                     'value'   =&gt; 2,                 ]             )         );     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u042d\u0442\u043e \u0431\u044b\u043b \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043a\u043b\u0430\u0441\u0441, \u0442\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u044c \u043a \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u043c\u0443 \u043f\u0440\u0438\u043c\u0435\u0440\u0443.<\/p>\n<h2>\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440<\/h2>\n<p><a class=\"anchor\" name=\"demonstration\" id=\"demonstration\"><\/a><\/p>\n<p>\u041d\u0438\u0436\u0435 \u043f\u0440\u0438\u0432\u0435\u0434\u0443 \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0430\u043a\u0435\u0442\u0430, \u0441 \u043e\u0434\u043d\u0438\u043c \u0447\u0438\u0441\u0442\u044b\u043c css \u0434\u043b\u044f \u043d\u0430\u0433\u043b\u044f\u0434\u043d\u043e\u0441\u0442\u0438, \u0435\u0441\u043b\u0438 \u043a\u043e\u043c\u0443-\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0435\u043d \u043f\u0440\u0438\u043c\u0435\u0440 \u0441 scss\/webpack \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438.<\/p>\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0431\u043b\u043e\u043a\u0438 \u0434\u043b\u044f \u0442\u0435\u0441\u0442\u0430, \u043f\u0443\u0441\u0442\u044c \u044d\u0442\u043e \u0431\u0443\u0434\u0443\u0442 Header, Article \u0438 Button. Header \u0438 Button \u0431\u0443\u0434\u0443\u0442 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u043c\u0438 \u0431\u043b\u043e\u043a\u0430\u043c\u0438, Article \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u043a\u0430\u0442\u044c Button.<\/p>\n<p><strong>Header<\/strong><\/p>\n<details class=\"spoiler\">\n<summary>Header.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  namespace LightSource\\FrontBlocksSample\\Header;  use LightSource\\FrontBlocks\\Block;  class Header extends Block {      protected string $name;      public function loadByTest()     {         parent::load();         $this-&gt;name = 'I\\'m Header';     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>Header.twig<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"django\">&lt;div class=\"header\"&gt;     {{ name }} &lt;\/div&gt;<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>Header.css<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"css\">.header {     color: green;     border:1px solid green;     padding: 10px; } <\/code><\/pre>\n<\/div>\n<\/details>\n<p><strong>Button<\/strong><\/p>\n<details class=\"spoiler\">\n<summary>Button.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  namespace LightSource\\FrontBlocksSample\\Button;  use LightSource\\FrontBlocks\\Block;  class Button extends Block {      protected string $name;      public function loadByTest()     {         parent::load();         $this-&gt;name = 'I\\'m Button';     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>Button.twig<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"django\">&lt;div class=\"button\"&gt;     {{ name }} &lt;\/div&gt;<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>Button.css<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"css\">.button {     color: black;     border: 1px solid black;     padding: 10px; } <\/code><\/pre>\n<\/div>\n<\/details>\n<p><strong>Article<\/strong><\/p>\n<details class=\"spoiler\">\n<summary>Article.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  namespace LightSource\\FrontBlocksSample\\Article;  use LightSource\\FrontBlocks\\Block; use LightSource\\FrontBlocksSample\\Button\\Button;  class Article extends Block {      protected string $name;     protected Button $button;      public function loadByTest()     {         parent::load();         $this-&gt;name = 'I\\'m Article, I contain another block';         $this-&gt;button-&gt;loadByTest();     } } <\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>Article.twig<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"django\">&lt;div class=\"article\"&gt;      &lt;p class=\"article__name\"&gt;{{ name }}&lt;\/p&gt;      {{ _include(button) }}  &lt;\/div&gt;<\/code><\/pre>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>Article.css<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"css\">.article {     color: orange;     border: 1px solid orange;     padding: 10px; }  .article__name {     margin: 0 0 10px;     line-height: 1.5; } <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043f\u0430\u043a\u0435\u0442 \u0438 \u0440\u0435\u043d\u0434\u0435\u0440\u0438\u043c \u0431\u043b\u043e\u043a\u0438, \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0432 html \u043a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0432 \u0448\u0430\u043f\u043a\u0435 \u0432\u044b\u0432\u043e\u0434\u0438\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0439 css \u043a\u043e\u0434<\/p>\n<details class=\"spoiler\">\n<summary>example.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  use LightSource\\FrontBlocks\\{     Renderer,     Settings }; use LightSource\\FrontBlocksSample\\{     Article\\Article,     Header\\Header };  require_once __DIR__ . '\/vendors\/vendor\/autoload.php';  \/\/\/\/ settings  ini_set('display_errors', 1);  $settings = new Settings(); $settings-&gt;addBlocksFolder('LightSource\\FrontBlocksSample', __DIR__ . '\/Blocks'); $settings-&gt;setErrorCallback(     function (array $errors) {         \/\/ todo log or any other actions         echo '&lt;pre&gt;' . print_r($errors, true) . '&lt;\/pre&gt;';     } ); $renderer = new Renderer($settings);  \/\/\/\/ usage  $header = new Header(); $header-&gt;loadByTest();  $article = new Article(); $article-&gt;loadByTest();  $content = $renderer-&gt;render($header); $content .= $renderer-&gt;render($article); $css     = $renderer-&gt;getUsedResources('.css', true);  \/\/\/\/ html  ?&gt; &lt;html&gt;  &lt;head&gt;      &lt;title&gt;Example&lt;\/title&gt;     &lt;style&gt;         &lt;?= $css ?&gt;     &lt;\/style&gt;     &lt;style&gt;         .article {             margin-top: 10px;         }     &lt;\/style&gt;  &lt;\/head&gt;  &lt;body&gt;  &lt;?= $content ?&gt;  &lt;\/body&gt;  &lt;\/html&gt; <\/code><\/pre>\n<\/div>\n<\/details>\n<p>\u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0432\u044b\u0432\u043e\u0434 \u0431\u0443\u0434\u0435\u0442 \u0442\u0430\u043a\u0438\u043c<\/p>\n<details class=\"spoiler\">\n<summary>example.png<\/summary>\n<div class=\"spoiler__content\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/18e\/f3a\/bcb\/18ef3abcb94c76f79b8cfc2a1970f17d.png\" width=\"1920\" height=\"164\"><figcaption><\/figcaption><\/figure>\n<\/div>\n<\/details>\n<h2>\u041f\u043e\u0441\u043b\u0435\u0441\u043b\u043e\u0432\u0438\u0435<\/h2>\n<p>\u0414\u0430\u043d\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442 \u0431\u044b\u043b \u0441\u043e\u0437\u0434\u0430\u043d \u0434\u043b\u044f \u043b\u0438\u0447\u043d\u044b\u0445 \u0446\u0435\u043b\u0435\u0439, \u043e\u043d \u043e\u0431\u043b\u0435\u0433\u0447\u0430\u0435\u0442 \u043c\u043d\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443, \u0438 \u044f \u0431\u0443\u0434\u0443 \u0440\u0430\u0434 \u0435\u0441\u043b\u0438 \u043e\u043d \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f \u043a\u043e\u043c\u0443-\u0442\u043e \u0435\u0449\u0435. \u041d\u0435 \u0441\u0442\u0435\u0441\u043d\u044f\u0439\u0442\u0435\u0441\u044c \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u0438 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u2013 \u044f \u0431\u0443\u0434\u0443 \u0440\u0430\u0434 \u043e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u043d\u0430 \u0432\u0430\u0448\u0438 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u0438 \u0443\u0441\u043b\u044b\u0448\u0430\u0442\u044c \u0432\u0430\u0448\u0435 \u043c\u043d\u0435\u043d\u0438\u0435. \u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435.<\/p>\n<p>\u041f\u043e\u043d\u0440\u0430\u0432\u0438\u043b\u0430\u0441\u044c \u0441\u0442\u0430\u0442\u044c\u044f? \u041d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c \u043f\u0440\u043e\u0433\u043e\u043b\u043e\u0441\u043e\u0432\u0430\u0442\u044c.<\/p>\n<p>\u0421\u0441\u044b\u043b\u043a\u0438:<\/p>\n<p><a href=\"https:\/\/github.com\/light-source\/front-blocks\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c \u043f\u0430\u043a\u0435\u0442\u043e\u043c<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/light-source\/front-blocks-sample\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u043c<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/light-source\/front-blocks-webpack-sample\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f scss \u0438 js \u0432 \u0431\u043b\u043e\u043a\u0430\u0445 (webpack \u0441\u0431\u043e\u0440\u0449\u0438\u043a)<\/a><\/p>\n<p><a href=\"https:\/\/github.com\/light-source\/wp-theme-bones\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0441 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432 WordPress \u0442\u0435\u043c\u0435<\/a> (\u0437\u0434\u0435\u0441\u044c \u0432\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c <a href=\"https:\/\/github.com\/light-source\/wp-theme-bones\/blob\/master\/Classes\/Block.php\" rel=\"noopener noreferrer nofollow\">\u043f\u0440\u0438\u043c\u0435\u0440 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u043a\u043b\u0430\u0441\u0441\u0430<\/a><a href=\"https:\/\/github.com\/light-source\/wp-theme-bones\/blob\/master\/Classes\/CONTROLLER.php\" rel=\"noopener noreferrer nofollow\"> <\/a>\u0431\u043b\u043e\u043a\u0430 \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438, \u0447\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 ajax \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0434\u043b\u044f \u0431\u043b\u043e\u043a\u043e\u0432)<\/p>\n<p>P.S. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044e <a class=\"mention\" href=\"\/users\/alexmixaylov\">@alexmixaylov<\/a>, <a class=\"mention\" href=\"\/users\/bombe\">@bombe<\/a> \u0438 <a class=\"mention\" href=\"\/users\/rpsv\">@rpsv<\/a> \u0437\u0430 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0438 \u043a \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438.<\/p>\n<\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/558422\/\"> https:\/\/habr.com\/ru\/post\/558422\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<p><a href=\"https:\/\/habr.com\/ru\/post\/556494\/\" rel=\"noopener noreferrer nofollow\">\u0412 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u0438<\/a> \u044f \u043f\u043e\u0434\u0435\u043b\u0438\u043b\u0441\u044f \u0441\u0432\u043e\u0438\u043c \u0432\u0437\u0433\u043b\u044f\u0434\u043e\u043c \u043d\u0430 \u0442\u043e, \u043a\u0430\u043a\u0438\u043c\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 front-end \u0431\u043b\u043e\u043a\u0438, \u043f\u043e\u043b\u0443\u0447\u0438\u043b \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u0438\u0432\u043d\u0443\u044e \u043a\u0440\u0438\u0442\u0438\u043a\u0443, \u0434\u043e\u0440\u0430\u0431\u043e\u0442\u0430\u043b \u043f\u0430\u043a\u0435\u0442 \u0438 \u0442\u0435\u043f\u0435\u0440\u044c \u0445\u043e\u0442\u0435\u043b \u0431\u044b \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441 \u0432\u0430\u043c\u0438 \u043d\u043e\u0432\u043e\u0439 \u0432\u0435\u0440\u0441\u0438\u0435\u0439. \u041e\u043d\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043b\u0435\u0433\u043a\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043c\u043e\u0434\u0443\u043b\u044c\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 \u0434\u043b\u044f \u043b\u044e\u0431\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441 \u0431\u0435\u043a\u0435\u043d\u0434\u043e\u043c \u043d\u0430 php.<\/p>\n<p>\u0414\u043b\u044f \u0442\u0435\u0445 \u043a\u0442\u043e \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c \u0441 \u043f\u0435\u0440\u0432\u043e\u0439 \u0447\u0430\u0441\u0442\u044c\u044e \u044f \u0431\u0443\u0434\u0443 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043f\u043e\u0439\u043b\u0435\u0440\u044b \u0438\u0437 \u043d\u0435\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0432\u0435\u0434\u0443\u0442 \u0432 \u043a\u0443\u0440\u0441 \u0434\u0435\u043b\u0430. \u0422\u0435\u043c \u043a\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0435\u043d \u043a\u043e\u043d\u0435\u0447\u043d\u044b\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 &#8212; \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 <a href=\"#demonstration\" rel=\"noopener noreferrer nofollow\">\u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438<\/a>.<\/p>\n<details class=\"spoiler\">\n<summary>\u041f\u0440\u0435\u0434\u0438\u0441\u043b\u043e\u0432\u0438\u0435<\/summary>\n<div class=\"spoiler__content\">\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044e\u0441\u044c &#8212; \u044f \u043c\u043e\u043b\u043e\u0434\u043e\u0439 \u0432\u0435\u0431 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0441 \u043e\u043f\u044b\u0442\u043e\u043c \u0440\u0430\u0431\u043e\u0442\u044b 5 \u043b\u0435\u0442. \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0433\u043e\u0434 \u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e \u043d\u0430 \u0444\u0440\u0438\u043b\u0430\u043d\u0441\u0435 \u0438 \u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0442\u0435\u043a\u0443\u0449\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432  \u0441\u0432\u044f\u0437\u0430\u043d\u0430 \u0441 WordPress. \u041d\u0435\u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0440\u0430\u0437\u043b\u0438\u0447\u0443\u044e \u043a\u0440\u0438\u0442\u0438\u043a\u0443 CMS \u0432 \u043e\u0431\u0449\u0435\u043c \u0438 WordPress \u0432 \u0447\u0430\u0441\u043d\u043e\u0441\u0442\u0438, \u044f \u0441\u0447\u0438\u0442\u0430\u044e \u0441\u0430\u043c\u0430 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 WordPress \u044d\u0442\u043e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0443\u0434\u0430\u0447\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u0445\u043e\u0442\u044f \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u043d\u0435 \u0431\u0435\u0437 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445 \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u043e\u0432. \u0418 \u043e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445 \u043d\u0430 \u043c\u043e\u0439 \u0432\u0437\u0433\u043b\u044f\u0434 \u044d\u0442\u043e \u0448\u0430\u0431\u043b\u043e\u043d\u044b. \u0412 \u043a\u0440\u0430\u0439\u043d\u0438\u0445 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f\u0445 \u0441\u0434\u0435\u043b\u0430\u043d\u044b \u0431\u043e\u043b\u044c\u0448\u0438\u0435  \u0448\u0430\u0433\u0438 \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u043e \u0438\u0441\u043f\u0440\u0430\u0432\u0438\u0442\u044c, \u0438 Gutenberg \u0432 \u0446\u0435\u043b\u043e\u043c \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043c\u043e\u0449\u043d\u044b\u043c  \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u043c, \u043e\u0434\u043d\u0430\u043a\u043e \u043a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e \u0432 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0435 \u0442\u0435\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u0448\u0430 \u0432 \u0448\u0430\u0431\u043b\u043e\u043d\u0430\u0445, \u0441\u0442\u0438\u043b\u044f\u0445 \u0438 \u0441\u043a\u0440\u0438\u043f\u0442\u0430\u0445, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0434\u0435\u043b\u0430\u0435\u0442 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0447\u0435\u0433\u043e-\u043b\u0438\u0431\u043e \u043a\u0440\u0430\u0439\u043d\u0435 \u0431\u043e\u043b\u0435\u0437\u043d\u0435\u043d\u043d\u044b\u043c, \u0430 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u043e\u0434\u0430 \u0437\u0430\u0447\u0430\u0441\u0442\u0443\u044e \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u043c.  \u0418\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430 \u0438 \u043f\u043e\u0434\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u043e \u043c\u0435\u043d\u044f \u043a \u0438\u0434\u0435\u0435 \u0441\u0432\u043e\u0435\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430,  \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044b \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u043b \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0438 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u043b \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u043b\u043e\u043a\u0438.  <\/p>\n<p>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0432 \u0432\u0438\u0434\u0435 composer \u043f\u0430\u043a\u0435\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0432 \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445  \u043f\u0440\u043e\u0435\u043a\u0442\u0430\u0445, \u0431\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a WordPress.  <\/p>\n<\/div>\n<\/details>\n<details class=\"spoiler\">\n<summary>\u041f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0434\u0430\u0447\u0438<\/summary>\n<div class=\"spoiler__content\">\n<p>\u041f\u043e\u043d\u044f\u0442\u0438\u0435 \u0431\u043b\u043e\u043a \u043d\u0438\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e \u0441\u0443\u0442\u0438 \u0442\u0435\u043c \u0436\u0435 \u043f\u043e\u043d\u044f\u0442\u0438\u0435\u043c \u0447\u0442\u043e \u0438 \u0431\u043b\u043e\u043a \u0432 <a href=\"https:\/\/ru.bem.info\/methodology\/\" rel=\"noopener noreferrer nofollow\">BEM \u043c\u0435\u0442\u043e\u0434\u043e\u043b\u043e\u0433\u0438\u0438<\/a>,  \u0442.\u0435. \u044d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0433\u0440\u0443\u043f\u043f\u0430 html\/js\/css \u043a\u043e\u0434\u0430 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u043e\u0434\u043d\u0443 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u044c.<\/p>\n<p>\u0413\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c html \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c\u0438 \u0431\u043b\u043e\u043a\u043e\u0432 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0447\u0435\u0440\u0435\u0437 php, \u0447\u0442\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e \u0442\u043e\u043c, \u0447\u0442\u043e \u043d\u0430\u0448 \u043f\u0430\u043a\u0435\u0442 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442\u044c \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0441 \u0431\u0435\u043a\u0435\u043d\u0434\u043e\u043c \u043d\u0430 php. \u0422\u0430\u043a\u0436\u0435 \u0443\u0441\u043b\u043e\u0432\u0438\u043c\u0441\u044f \u043d\u0430 \u0431\u0435\u0440\u0435\u0433\u0443 \u0447\u0442\u043e, \u043d\u0435 \u0432\u0434\u0430\u0432\u0430\u044f\u0441\u044c \u0432 \u0441\u043f\u043e\u0440\u044b, \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u0434\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u043b\u0438\u044f\u043d\u0438\u044e \u043d\u043e\u0432\u043e\u043c\u043e\u0434\u043d\u044b\u0445 \u0432\u0435\u0449\u0435\u0439, \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a css-in-js \u0438\u043b\u0438 bem-json \u0438 \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u0438\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c\u0441\u044f <s>\u044d\u043b\u044c-\u043a\u043b\u0430\u0441\u0441\u0438\u043a\u043e<\/s> \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0430, \u0442.\u0435. \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c \u0447\u0442\u043e html, css \u0438 js \u044d\u0442\u043e \u0440\u0430\u0437\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b.  <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u0443\u0435\u043c \u043d\u0430\u0448\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u043a \u043f\u0430\u043a\u0435\u0442\u0443:<\/p>\n<ul>\n<li>\n<p>\u041e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0431\u043b\u043e\u043a\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f (\u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f) \u0431\u043b\u043e\u043a\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0431\u043b\u043e\u043a \u0432 \u0431\u043b\u043e\u043a\u0435 \u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0443 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 \u043e\u0434\u043d\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430 \u043e\u0442 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 <\/p>\n<\/li>\n<\/ul>\n<\/div>\n<\/details>\n<h2>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0430\u043a\u0435\u0442\u0430<\/h2>\n<details class=\"spoiler\">\n<summary>\u041e \u0440\u0435\u0441\u0443\u0440\u0430\u0445 \u0431\u043b\u043e\u043a\u0430 \u0438 twig \u0448\u0430\u0431\u043b\u043e\u043d\u0430\u0445<\/summary>\n<div class=\"spoiler__content\">\n<p>\u041a\u0430\u043a \u0443\u0441\u043b\u043e\u0432\u0438\u043b\u0438\u0441\u044c \u0432\u044b\u0448\u0435, \u0442\u0430\u043a\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u043a\u0430\u043a css \u0438 js \u0432\u0441\u0435\u0433\u0434\u0430 \u0431\u0443\u0434\u0443\u0442 \u0432 \u0432\u0438\u0434\u0435 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432, \u0442.\u0435. \u044d\u0442\u043e \u0431\u0443\u0434\u0443\u0442 .js \u0438 .css \u0438\u043b\u0438 .min.css \u0438 .min.js \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u0440\u0435\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0441\u043e\u0440\u043e\u0432 \u0438 \u0441\u0431\u043e\u0440\u0449\u0438\u043a\u043e\u0432 (\u043a\u0430\u043a webpack \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440). \u0414\u043b\u044f \u0432\u0441\u0442\u0430\u0432\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 html \u043a\u043e\u0434 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0442\u043e\u0440 Twig (\u0434\u043b\u044f \u0442\u0435\u0445 \u043a\u0442\u043e \u043d\u0435 \u0437\u043d\u0430\u043a\u043e\u043c <a href=\"https:\/\/twig.symfony.com\/\" rel=\"noopener noreferrer nofollow\">\u0441\u0441\u044b\u043b\u043a\u0430<\/a>). \u041a\u0442\u043e-\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e php \u0438 \u0441\u0430\u043c \u043f\u043e \u0441\u0435\u0431\u0435 \u0445\u043e\u0440\u043e\u0448\u0438\u0439 \u0448\u0430\u0431\u043b\u043e\u043d\u0438\u0437\u0430\u0442\u043e\u0440, \u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0432\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u0441\u043f\u043e\u0440\u044b, \u043a\u0440\u043e\u043c\u0435 \u0434\u043e\u0432\u043e\u0434\u043e\u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u043d\u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 Twig, \u043e\u0442\u043c\u0435\u0447\u0443 \u0432\u0430\u0436\u043d\u044b\u0439 \u0434\u043b\u044f \u043c\u0435\u043d\u044f \u043f\u0443\u043d\u043a\u0442, \u0442\u043e \u0447\u0442\u043e \u043e\u043d \u0434\u0438\u0441\u0446\u0438\u043f\u043b\u0438\u043d\u0438\u0440\u0443\u0435\u0442, \u0442.\u0435. \u0437\u0430\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043e\u0442\u0434\u0435\u043b\u044f\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043e\u0442 \u0432\u044b\u0432\u043e\u0434\u0430 \u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0437\u0430\u0440\u0430\u043d\u0435\u0435, \u0438 \u0432 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0435\u0433\u043e.<\/p>\n<\/div>\n<\/details>\n<ol>\n<li>\n<p>\u0411\u043b\u043e\u043a<\/p>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0431\u043b\u043e\u043a \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437:<\/p>\n<ol>\n<li>\n<p> \u0421\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 (css\/js\/twig)<\/p>\n<\/li>\n<li>\n<p>\u041a\u043b\u0430\u0441\u0441\u0430 \u0431\u043b\u043e\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f twig \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c\u0438.<\/p>\n<\/li>\n<\/ol>\n<\/li>\n<li>\n<p>\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b: Settings (\u043f\u0443\u0442\u0438 \u043a \u0431\u043b\u043e\u043a\u0430\u043c \u0438 \u0438\u0445 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0430 \u0438\u043c\u0435\u043d), TwigWrapper (\u043e\u0431\u0435\u0440\u0442\u043a\u0430 \u0434\u043b\u044f Twig \u043f\u0430\u043a\u0435\u0442\u0430), BlocksLoader (\u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0432\u0441\u0435\u0445 \u0431\u043b\u043e\u043a\u043e\u0432, \u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e), Helper (\u043d\u0430\u0431\u043e\u0440 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0445 \u0434\u043e\u043f. \u0444\u0443\u043d\u043a\u0446\u0438\u0439)<\/p>\n<\/li>\n<li>\n<p>Renderer \u043a\u043b\u0430\u0441\u0441 &#8212; \u0441\u0432\u044f\u0437\u0443\u044e\u0449\u0438\u0439 \u043a\u043b\u0430\u0441\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0442\u044c \u0432\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0440\u0435\u043d\u0434\u0435\u0440\u0430 \u0431\u043b\u043e\u043a\u0430, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 \u0438 \u0438\u0445 \u0440\u0435\u0441\u0443\u0440\u0441\u044b (css, js)<\/p>\n<\/li>\n<\/ol>\n<h2>\u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f \u043a \u0431\u043b\u043e\u043a\u0430\u043c<\/h2>\n<p>\u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0438 \u043e\u0442 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u0439 \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u043b\u043e\u0441\u044c, \u0442\u0435\u043f\u0435\u0440\u044c \u044d\u0442\u043e:<\/p>\n<ul>\n<li>\n<p>php 7.4<\/p>\n<\/li>\n<li>\n<p>\u041a\u043b\u0430\u0441\u0441\u044b \u0431\u043b\u043e\u043a\u043e\u0432 \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u043c\u0435\u0442\u044c PSR-4 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0435 \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u043e \u0438\u043c\u0435\u043d \u0441 \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a\u043e\u043c (<a href=\"https:\/\/www.php-fig.org\/psr\/psr-4\/\" rel=\"noopener noreferrer nofollow\">PSR-4<\/a> \u0434\u0435 \u0444\u0430\u043a\u0442\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442, \u0435\u0441\u043b\u0438 \u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u0447\u0438\u043a \u043e\u0442 composer, \u0442.\u0435. \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0435 autoload\/psr4 \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0443 \u0432 \u0432\u0430\u0448\u0435\u043c composer.json \u0442\u043e \u0432\u0430\u0448 \u043f\u0440\u043e\u0435\u043a\u0442 \u0443\u0436\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u044d\u0442\u043e\u043c\u0443 \u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044e)  <\/p>\n<\/li>\n<li>\n<p>\u0418\u043c\u0435\u043d\u0430 \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432 \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c \u0441 \u0438\u043c\u0435\u043d\u0435\u043c \u0431\u043b\u043e\u043a\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0434\u043b\u044f Button.php \u0431\u0443\u0434\u0443\u0442 Button.css \u0438 Button.twig)<\/p>\n<\/li>\n<\/ul>\n<h2>\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/h2>\n<p>\u041d\u0438\u0436\u0435 \u0447\u0430\u0441\u0442\u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 (\u043a\u043b\u0430\u0441\u0441\u044b) \u0431\u0443\u0434\u0443\u0442 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 : \u0442\u0435\u043a\u0441\u0442\u043e\u0432\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435, \u043a\u043e\u0434 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u043a\u043e\u0434 \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<p><strong>Block<\/strong><\/p>\n<p>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043a\u043b\u0430\u0441\u0441, \u0435\u0433\u043e \u043f\u043e\u0442\u043e\u043c\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f twig \u0448\u0430\u0431\u043b\u043e\u043d\u0430 (\u0432 protected \u043f\u043e\u043b\u044f\u0445) \u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043c\u044b \u0441\u043c\u043e\u0436\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043f\u0443\u0442\u044c \u043a \u0440\u0435\u0441\u0443\u0440\u0441\u0430\u043c (\u0448\u0430\u0431\u043b\u043e\u043d\u0443, \u0441\u0442\u0438\u043b\u044f\u043c). \u0412\u0441\u0435 \u043d\u0430\u0448\u0430 \u043c\u0430\u0433\u0438\u044f \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u043f\u043e\u043b\u044f\u043c\u0438 \u0431\u0443\u0434\u0435\u0442 \u0441\u0442\u0440\u043e\u0438\u0442\u0441\u044f \u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u2018get_class_vars\u2019 \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442 \u0438\u043c\u0435\u043d\u0430 \u043f\u043e\u043b\u0435\u0439 \u043a\u043b\u0430\u0441\u0441\u0430 \u0438 \u043d\u0430 \u2018ReflectionProperty\u2019 \u043a\u043b\u0430\u0441\u0441\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0438\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e\u0431 \u044d\u0442\u0438\u0445 \u043f\u043e\u043b\u044f\u0445, \u0442\u0430\u043a\u0443\u044e \u043a\u0430\u043a \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u043f\u043e\u043b\u044f (protected\/public) \u0438 \u0435\u0433\u043e \u0442\u0438\u043f. \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0441\u043e\u0431\u0438\u0440\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0442\u043e\u043b\u044c\u043a\u043e \u043e protected \u043f\u043e\u043b\u044f\u0445.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u043c \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0443\u044e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0430\u0432\u0442\u043e\u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u0435\u0439 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e, \u044d\u0442\u043e \u0438\u0437\u0431\u0430\u0432\u0438\u0442 \u043d\u0430\u0441 \u043e\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0438\u0445 \u0432 \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440\u0435 \u0432\u0440\u0443\u0447\u043d\u0443\u044e, \u0447\u0442\u043e \u043f\u0440\u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0435 \u0431\u043b\u043e\u043a\u043e\u0432 \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 \u043d\u0430\u0448\u0435 \u0432\u0440\u0435\u043c\u044f.<\/p>\n<details class=\"spoiler\">\n<summary>Block.php<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"php\">&lt;?php  declare(strict_types=1);  namespace LightSource\\FrontBlocks;  use Exception; use ReflectionProperty;  abstract class Block {      public const TEMPLATE_KEY_NAMESPACE = '_namespace';     public const TEMPLATE_KEY_TEMPLATE = '_template';     public const TEMPLATE_KEY_IS_LOADED = '_isLoaded';     public const RESOURCE_KEY_NAMESPACE = 'namespace';     public const RESOURCE_KEY_FOLDER = 'folder';     public const RESOURCE_KEY_RELATIVE_RESOURCE_PATH = 'relativeResourcePath';     public const RESOURCE_KEY_RELATIVE_BLOCK_PATH = 'relativeBlockPath';     public const RESOURCE_KEY_RESOURCE_NAME = 'resourceName';      private array $fieldsInfo;     private bool $isLoaded;      public function __construct()     {         $this-&gt;fieldsInfo = [];         $this-&gt;isLoaded   = false;          $this-&gt;readFieldsInfo();         $this-&gt;autoInitFields();     }      public static function onLoad()     {     }      public static function getResourceInfo(Settings $settings, string $blockClass = ''): ?array     {         \/\/ using static for child support         $blockClass = ! $blockClass ?             static::class :             $blockClass;          \/\/ e.g. $blockClass = Namespace\/Example\/Theme\/Main\/ExampleThemeMain         $resourceInfo = [             self::RESOURCE_KEY_NAMESPACE              =&gt; '',             self::RESOURCE_KEY_FOLDER                 =&gt; '',             self::RESOURCE_KEY_RELATIVE_RESOURCE_PATH =&gt; '',\/\/ e.g. Example\/Theme\/Main\/ExampleThemeMain             self::RESOURCE_KEY_RELATIVE_BLOCK_PATH    =&gt; '',\/\/ e.g. Example\/Theme\/Main             self::RESOURCE_KEY_RESOURCE_NAME          =&gt; '',\/\/ e.g. ExampleThemeMain         ];          $blockFolderInfo = $settings-&gt;getBlockFolderInfoByBlockClass($blockClass);          if (! $blockFolderInfo) {             $settings-&gt;callErrorCallback(                 [                     'error'      =&gt; 'Block has the non registered namespace',                     'blockClass' =&gt; $blockClass,                 ]             );              return null;         }          $resourceInfo[self::RESOURCE_KEY_NAMESPACE] = $blockFolderInfo['namespace'];         $resourceInfo[self::RESOURCE_KEY_FOLDER]    = $blockFolderInfo['folder'];          \/\/  e.g. Example\/Theme\/Main\/ExampleThemeMain         $relativeBlockNamespace = str_replace($resourceInfo[self::RESOURCE_KEY_NAMESPACE] . '\\\\', '', $blockClass);          \/\/ e.g. ExampleThemeMain         $blockName = explode('\\\\', $relativeBlockNamespace);         $blockName = $blockName[count($blockName) - 1];          \/\/ e.g. Example\/Theme\/Main         $relativePath = explode('\\\\', $relativeBlockNamespace);         $relativePath = array_slice($relativePath, 0, count($relativePath) - 1);         $relativePath = implode(DIRECTORY_SEPARATOR, $relativePath);          $resourceInfo[self::RESOURCE_KEY_RELATIVE_RESOURCE_PATH] = $relativePath . DIRECTORY_SEPARATOR . $blockName;         $resourceInfo[self::RESOURCE_KEY_RELATIVE_BLOCK_PATH]    = $relativePath;         $resourceInfo[self::RESOURCE_KEY_RESOURCE_NAME]          = $blockName;          return $resourceInfo;     }      private static function getResourceInfoForTwigTemplate(Settings $settings, string $blockClass): ?array     {         $resourceInfo = self::getResourceInfo($settings, $blockClass);          if (! $resourceInfo) {             return null;         }          $absTwigPath = implode(             '',             [                 $resourceInfo['folder'],                 DIRECTORY_SEPARATOR,                 $resourceInfo['relativeResourcePath'],                 $settings-&gt;getTwigExtension(),             ]         );          if (! is_file($absTwigPath)) {             $parentClass = get_parent_class($blockClass);              if ($parentClass &amp;&amp;                 is_subclass_of($parentClass, self::class) &amp;&amp;                 self::class !== $parentClass) {                 return self::getResourceInfoForTwigTemplate($settings, $parentClass);             } else {                 return null;             }         }          return $resourceInfo;     }      final public function getFieldsInfo(): array     {         return $this-&gt;fieldsInfo;     }      final public function isLoaded(): bool     {         return $this-&gt;isLoaded;     }      private function getBlockField(string $fieldName): ?Block     {         $block      = null;         $fieldsInfo = $this-&gt;fieldsInfo;          if (key_exists($fieldName, $fieldsInfo)) {             $block = $this-&gt;{$fieldName};              \/\/ prevent possible recursion by a mistake (if someone will create a field with self)             \/\/ using static for children support             $block = ($block &amp;&amp;                       $block instanceof Block &amp;&amp;                       get_class($block) !== static::class) ?                 $block :                 null;         }          return $block;     }      public function getDependencies(string $sourceClass = ''): array     {         $dependencyClasses = [];         $fieldsInfo        = $this-&gt;fieldsInfo;          foreach ($fieldsInfo as $fieldName =&gt; $fieldType) {             $dependencyBlock = $this-&gt;getBlockField($fie<\/code><\/pre>\n<\/div>\n<\/details>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-323490","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/323490","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=323490"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/323490\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=323490"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=323490"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=323490"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}