[{"data":1,"prerenderedAt":2364},["ShallowReactive",2],{"all-blog-posts":3},[4,43,228,821,1856,2033,2205],{"id":5,"title":6,"body":7,"date":24,"description":25,"extension":26,"image":27,"meta":28,"navigation":29,"path":30,"pinned":31,"seo":32,"stem":34,"summary":35,"tags":36,"updated":27,"__hash__":42},"blog\u002Fblog\u002F2020-12-03-my-first-blog-post.md","My first blog post here",{"type":8,"value":9,"toc":19},"minimark",[10,15],[11,12,14],"h2",{"id":13},"what-type-of-articles-can-you-except-here","What type of articles can you except here?",[16,17,18],"p",{},"This blog will be a place where I will share the topics related with PHP development and from time to time also some other topics and technologies like NuxtJS, VueJS or something else that will come to my mind :)",{"title":20,"searchDepth":21,"depth":21,"links":22},"",2,[23],{"id":13,"depth":21,"text":14},"2020-12-03","Hello from Denis Veg, a Magento 2 and Laravel developer from Slovenia. Here is what to expect on this blog: PHP, Magento 2, Laravel, Vue.js and Nuxt.js.","md",null,{},true,"\u002Fblog\u002Fmy-first-blog-post-here",false,{"title":33,"description":25},"Hello World: A Magento 2 & Laravel Developer Blog","blog\u002F2020-12-03-my-first-blog-post","Hello everyone! :) I'm Denis Veg, 29 yrs old web developer from Gornja Radgona, Slovenia. I'm PHP developer - Magento 2 as my day-to-day job and Laravel as my framework of choice for all side-projects. I've been waiting way too long with my very own blog and now I've decided to put it finally online.",[37,38,39,40,41],"me","coding","magento","development","topics","Mln1EeWaea30zySG4bJ54blIbbtrdMTmZfBmN2Xn1fU",{"id":44,"title":45,"body":46,"date":214,"description":215,"extension":26,"image":216,"meta":218,"navigation":29,"path":219,"pinned":31,"seo":220,"stem":222,"summary":223,"tags":224,"updated":27,"__hash__":227},"blog\u002Fblog\u002F2020-12-05-magento-2-block-condition-for-template.md","Magento 2 - block visibility",{"type":8,"value":47,"toc":212},[48,54,62,65,68,93,107,122,127,205,208],[16,49,50],{},[51,52,53],"strong",{},"How to accomplish this business requirement and have the simple and clean code at the same?",[16,55,56,57,61],{},"Let say we have a defined block in ",[58,59,60],"code",{},"app\\code\\Vendor\\Module\\etc\\di.xml"," where we also set a custom template for this block.",[16,63,64],{},"Everyone will probably think this is an easy requirement. You only have to create - in case that you don't have already, one helper class that will have the public method which will read the system config value set in Magento 2 backend and then pass it to the template and write one small if statement to handle this requirement, right? Yeah, that's what I thought first.",[16,66,67],{},"Above solution will work fine, but there's one issue - you will have to mess with PHP in html template when this is not needed because, as I'll show you, there's better and more clean solution.",[16,69,70,71,76],{},"Time to think and find a proper solution that will be simple and clean at one side and without the need to put business logic in a template on other side.\n",[72,73],"img",{"alt":74,"src":75},"Programing is about thinking, not writing","\u002Fimages\u002Fmagento-2-block-condition.webp",[77,78,79],"small",{},[80,81,82,83,88,89],"span",{},"Photo by ",[84,85,87],"a",{"href":86},"https:\u002F\u002Funsplash.com\u002F@perloov?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText","Per Lööv"," on ",[84,90,92],{"href":91},"https:\u002F\u002Funsplash.com\u002Fs\u002Fphotos\u002Fweb-developer?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText","Unsplash",[16,94,95,96,102,103,106],{},"The solution for this business requirement is to use Magento 2 ",[84,97,101],{"href":98,"rel":99},"https:\u002F\u002Fdevdocs.magento.com\u002Fguides\u002Fv2.4\u002Ffrontend-dev-guide\u002Flayouts\u002Fxml-manage.html#ref_config_block",[100],"nofollow","ifconfig argument in block definition"," to set the condition that either show or not the block based on ",[58,104,105],{}," Magento\u002FConfig\u002FModel\u002FConfig\u002FSource\u002FYesno"," option that we have in system config for a certain field.",[16,108,109,110,113,114,117,118,121],{},"So we need to set it like below where the ",[58,111,112],{},"my\u002Fyesno\u002Ffield"," is the field XPATH - you can find it in database table ",[58,115,116],{},"core_config_data"," or in case of custom module it must be defined in ",[58,119,120],{},"Vendor\\Module\\etc\\adminhtml\\system.xml",".",[16,123,124],{},[77,125,126],{},"Note: field XPATH is from three parts; first one is section id, second one is group id, and the third one is field id.",[128,129,133],"pre",{"className":130,"code":131,"language":132,"meta":20,"style":20},"language-xml shiki shiki-themes material-theme-palenight","\u003Cblock class=\"Namespace\\Module\\Block\\Type\" name=\"block.example\" ifconfig=\"my\u002Fyesno\u002Ffield\">\n...\n\u003C\u002Fblock>\n","xml",[58,134,135,189,195],{"__ignoreMap":20},[80,136,139,143,147,151,154,157,161,163,166,168,170,173,175,178,180,182,184,186],{"class":137,"line":138},"line",1,[80,140,142],{"class":141},"sAklC","\u003C",[80,144,146],{"class":145},"s-wAU","block",[80,148,150],{"class":149},"sJ14y"," class",[80,152,153],{"class":141},"=",[80,155,156],{"class":141},"\"",[80,158,160],{"class":159},"sfyAc","Namespace\\Module\\Block\\Type",[80,162,156],{"class":141},[80,164,165],{"class":149}," name",[80,167,153],{"class":141},[80,169,156],{"class":141},[80,171,172],{"class":159},"block.example",[80,174,156],{"class":141},[80,176,177],{"class":149}," ifconfig",[80,179,153],{"class":141},[80,181,156],{"class":141},[80,183,112],{"class":159},[80,185,156],{"class":141},[80,187,188],{"class":141},">\n",[80,190,191],{"class":137,"line":21},[80,192,194],{"class":193},"s0W1g","...\n",[80,196,198,201,203],{"class":137,"line":197},3,[80,199,200],{"class":141},"\u003C\u002F",[80,202,146],{"class":145},[80,204,188],{"class":141},[16,206,207],{},"I hope you find it useful and that it will help you make your code simple and more clean.",[209,210,211],"style",{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":20,"searchDepth":21,"depth":21,"links":213},[],"2020-12-05","Show or hide a Magento 2 block based on a system config value with the ifconfig attribute, keeping PHP conditions out of your template.",{"src":75,"alt":217},"Magento 2 block visibility with the ifconfig attribute",{},"\u002Fblog\u002Fmagento-2-block-visibility",{"title":221,"description":215},"Magento 2 Block Visibility with the ifconfig Attribute","blog\u002F2020-12-05-magento-2-block-condition-for-template","I was recently in the situation where I've needed to set a condition check to display some block template according to the system setting set in backend config. Let me show you what I did to accomplish this business requirement.",[225,146,226,40],"magento 2","visibility","_t0DCV3qGsrWgjfq_Sv6BmAUeJO9KdKNINqtS4fhI_g",{"id":229,"title":230,"body":231,"date":807,"description":808,"extension":26,"image":809,"meta":811,"navigation":29,"path":812,"pinned":31,"seo":813,"stem":815,"summary":816,"tags":817,"updated":27,"__hash__":820},"blog\u002Fblog\u002F2020-12-17-magento-2-update-single-model-value.md","Magento 2 - update single model value",{"type":8,"value":232,"toc":805},[233,238,250,253,256,259,274,796,799,802],[16,234,235],{},[51,236,237],{},"Magento 2: update single model value (field) without loading the whole model instance",[16,239,240,243],{},[72,241],{"alt":74,"src":242},"\u002Fimages\u002Fdeveloper.webp",[77,244,245],{},[84,246,249],{"href":247,"rel":248},"https:\u002F\u002Fwww.pexels.com\u002Fphoto\u002Fperson-using-a-macbook-air-4974913\u002F?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels",[100],"Photo by olia danilevich from Pexels",[16,251,252],{},"Well, let's say we're in above situation, and it's not applicable in our case to use the classic method where we can just use setter to set the value we want and then just persist these changes to the database.",[16,254,255],{},"So what shall we do in such case?",[16,257,258],{},"For me the best solution so far is to use Magento 2 App Resource instance with which you can update one or more model fields (attributes) manually - it's like you're running single SQL update statement for one table.",[16,260,261,262,265,266,269,270,273],{},"So the easiest way and most-common - also recommended, is to inject the instance of class ",[58,263,264],{},"Magento\\Framework\\App\\ResourceConnection"," into ",[58,267,268],{},"construct"," - don't use ",[58,271,272],{},"ObjectManager"," just to get the instance - it's not recommended by Magento standards, and then perform the action either in some function or wrote own function and call it on place you need it.",[128,275,279],{"className":276,"code":277,"language":278,"meta":20,"style":20},"language-php shiki shiki-themes material-theme-palenight","use Magento\\Framework\\App\\ResourceConnection;\nuse Psr\\Log\\LoggerInterface;\n\n\u002F** \n * @var ResourceConnection $connection \n *\u002F\nprotected $connection;\n\n\u002F** \n * @var LoggerInterface $logger \n *\u002F\nprotected $logger;\n\n\u002F**\n * @param ResourceConnection $connection\n * @param LoggerInterface $logger\n *\u002F\npublic function __construct(\n    ResourceConnection $connection, \n    LoggerInterface $logger\n){\n    $this->connection = $connection;\n    $this->logger = $logger;\n}\n\n\u002F**\n * @param $itemId\n * @param $status\n *\u002F\nprotected function updateItemStatus(int $itemId, $status): void\n{\n    try {\n        $this->connection->getConnection()->update('sales_order_item', [\n            'my_custom_product_status' => $status,\n        ],\n            $this->connection->getConnection()->quoteInto('item_id = ?', $itemId)\n        );\n    } catch (Exception $exception) {\n        $this->logger->error($exception->getMessage());\n    }\n}\n","php",[58,280,281,309,328,333,340,357,363,377,382,387,400,405,417,422,428,441,453,458,474,491,502,508,525,541,547,552,557,567,577,582,616,622,632,667,688,694,729,735,760,785,791],{"__ignoreMap":20},[80,282,283,287,290,293,296,298,301,303,306],{"class":137,"line":138},[80,284,286],{"class":285},"sx098","use",[80,288,289],{"class":193}," Magento",[80,291,292],{"class":141},"\\",[80,294,295],{"class":193},"Framework",[80,297,292],{"class":141},[80,299,300],{"class":193},"App",[80,302,292],{"class":141},[80,304,305],{"class":193},"ResourceConnection",[80,307,308],{"class":141},";\n",[80,310,311,313,316,318,321,323,326],{"class":137,"line":21},[80,312,286],{"class":285},[80,314,315],{"class":193}," Psr",[80,317,292],{"class":141},[80,319,320],{"class":193},"Log",[80,322,292],{"class":141},[80,324,325],{"class":193},"LoggerInterface",[80,327,308],{"class":141},[80,329,330],{"class":137,"line":197},[80,331,332],{"emptyLinePlaceholder":29},"\n",[80,334,336],{"class":137,"line":335},4,[80,337,339],{"class":338},"saEQR","\u002F** \n",[80,341,343,346,350,354],{"class":137,"line":342},5,[80,344,345],{"class":338}," * ",[80,347,349],{"class":348},"sfFnM","@var",[80,351,353],{"class":352},"stCc5"," ResourceConnection",[80,355,356],{"class":338}," $connection \n",[80,358,360],{"class":137,"line":359},6,[80,361,362],{"class":338}," *\u002F\n",[80,364,366,369,372,375],{"class":137,"line":365},7,[80,367,368],{"class":149},"protected",[80,370,371],{"class":141}," $",[80,373,374],{"class":193},"connection",[80,376,308],{"class":141},[80,378,380],{"class":137,"line":379},8,[80,381,332],{"emptyLinePlaceholder":29},[80,383,385],{"class":137,"line":384},9,[80,386,339],{"class":338},[80,388,390,392,394,397],{"class":137,"line":389},10,[80,391,345],{"class":338},[80,393,349],{"class":348},[80,395,396],{"class":352}," LoggerInterface",[80,398,399],{"class":338}," $logger \n",[80,401,403],{"class":137,"line":402},11,[80,404,362],{"class":338},[80,406,408,410,412,415],{"class":137,"line":407},12,[80,409,368],{"class":149},[80,411,371],{"class":141},[80,413,414],{"class":193},"logger",[80,416,308],{"class":141},[80,418,420],{"class":137,"line":419},13,[80,421,332],{"emptyLinePlaceholder":29},[80,423,425],{"class":137,"line":424},14,[80,426,427],{"class":338},"\u002F**\n",[80,429,431,433,436,438],{"class":137,"line":430},15,[80,432,345],{"class":338},[80,434,435],{"class":348},"@param",[80,437,353],{"class":352},[80,439,440],{"class":338}," $connection\n",[80,442,444,446,448,450],{"class":137,"line":443},16,[80,445,345],{"class":338},[80,447,435],{"class":348},[80,449,396],{"class":352},[80,451,452],{"class":338}," $logger\n",[80,454,456],{"class":137,"line":455},17,[80,457,362],{"class":338},[80,459,461,464,467,471],{"class":137,"line":460},18,[80,462,463],{"class":149},"public",[80,465,466],{"class":149}," function",[80,468,470],{"class":469},"sdLwU"," __construct",[80,472,473],{"class":141},"(\n",[80,475,477,481,483,485,488],{"class":137,"line":476},19,[80,478,480],{"class":479},"s5Dmg","    ResourceConnection",[80,482,371],{"class":141},[80,484,374],{"class":193},[80,486,487],{"class":141},",",[80,489,490],{"class":193}," \n",[80,492,494,497,499],{"class":137,"line":493},20,[80,495,496],{"class":479},"    LoggerInterface",[80,498,371],{"class":141},[80,500,501],{"class":193},"logger\n",[80,503,505],{"class":137,"line":504},21,[80,506,507],{"class":141},"){\n",[80,509,511,514,517,519,521,523],{"class":137,"line":510},22,[80,512,513],{"class":141},"    $this->",[80,515,516],{"class":193},"connection ",[80,518,153],{"class":141},[80,520,371],{"class":141},[80,522,374],{"class":193},[80,524,308],{"class":141},[80,526,528,530,533,535,537,539],{"class":137,"line":527},23,[80,529,513],{"class":141},[80,531,532],{"class":193},"logger ",[80,534,153],{"class":141},[80,536,371],{"class":141},[80,538,414],{"class":193},[80,540,308],{"class":141},[80,542,544],{"class":137,"line":543},24,[80,545,546],{"class":141},"}\n",[80,548,550],{"class":137,"line":549},25,[80,551,332],{"emptyLinePlaceholder":29},[80,553,555],{"class":137,"line":554},26,[80,556,427],{"class":338},[80,558,560,562,564],{"class":137,"line":559},27,[80,561,345],{"class":338},[80,563,435],{"class":348},[80,565,566],{"class":338}," $itemId\n",[80,568,570,572,574],{"class":137,"line":569},28,[80,571,345],{"class":338},[80,573,435],{"class":348},[80,575,576],{"class":338}," $status\n",[80,578,580],{"class":137,"line":579},29,[80,581,362],{"class":338},[80,583,585,587,589,592,595,598,600,603,605,607,610,613],{"class":137,"line":584},30,[80,586,368],{"class":149},[80,588,466],{"class":149},[80,590,591],{"class":469}," updateItemStatus",[80,593,594],{"class":141},"(",[80,596,597],{"class":285},"int",[80,599,371],{"class":141},[80,601,602],{"class":193},"itemId",[80,604,487],{"class":141},[80,606,371],{"class":141},[80,608,609],{"class":193},"status",[80,611,612],{"class":141},"):",[80,614,615],{"class":285}," void\n",[80,617,619],{"class":137,"line":618},31,[80,620,621],{"class":141},"{\n",[80,623,625,629],{"class":137,"line":624},32,[80,626,628],{"class":627},"s6cf3","    try",[80,630,631],{"class":141}," {\n",[80,633,635,638,640,643,646,649,652,654,657,660,662,664],{"class":137,"line":634},33,[80,636,637],{"class":141},"        $this->",[80,639,374],{"class":193},[80,641,642],{"class":141},"->",[80,644,645],{"class":469},"getConnection",[80,647,648],{"class":141},"()->",[80,650,651],{"class":469},"update",[80,653,594],{"class":141},[80,655,656],{"class":141},"'",[80,658,659],{"class":159},"sales_order_item",[80,661,656],{"class":141},[80,663,487],{"class":141},[80,665,666],{"class":141}," [\n",[80,668,670,673,676,678,681,683,685],{"class":137,"line":669},34,[80,671,672],{"class":141},"            '",[80,674,675],{"class":159},"my_custom_product_status",[80,677,656],{"class":141},[80,679,680],{"class":141}," =>",[80,682,371],{"class":141},[80,684,609],{"class":193},[80,686,687],{"class":141},",\n",[80,689,691],{"class":137,"line":690},35,[80,692,693],{"class":141},"        ],\n",[80,695,697,700,702,704,706,708,711,713,715,718,720,722,724,726],{"class":137,"line":696},36,[80,698,699],{"class":141},"            $this->",[80,701,374],{"class":193},[80,703,642],{"class":141},[80,705,645],{"class":469},[80,707,648],{"class":141},[80,709,710],{"class":469},"quoteInto",[80,712,594],{"class":141},[80,714,656],{"class":141},[80,716,717],{"class":159},"item_id = ?",[80,719,656],{"class":141},[80,721,487],{"class":141},[80,723,371],{"class":141},[80,725,602],{"class":193},[80,727,728],{"class":141},")\n",[80,730,732],{"class":137,"line":731},37,[80,733,734],{"class":141},"        );\n",[80,736,738,741,744,747,750,752,755,758],{"class":137,"line":737},38,[80,739,740],{"class":141},"    }",[80,742,743],{"class":627}," catch",[80,745,746],{"class":141}," (",[80,748,749],{"class":479},"Exception",[80,751,371],{"class":141},[80,753,754],{"class":193},"exception",[80,756,757],{"class":141},")",[80,759,631],{"class":141},[80,761,763,765,767,769,772,775,777,779,782],{"class":137,"line":762},39,[80,764,637],{"class":141},[80,766,414],{"class":193},[80,768,642],{"class":141},[80,770,771],{"class":469},"error",[80,773,774],{"class":141},"($",[80,776,754],{"class":193},[80,778,642],{"class":141},[80,780,781],{"class":469},"getMessage",[80,783,784],{"class":141},"());\n",[80,786,788],{"class":137,"line":787},40,[80,789,790],{"class":141},"    }\n",[80,792,794],{"class":137,"line":793},41,[80,795,546],{"class":141},[16,797,798],{},"This way we can easily update, in our case model Sales_order_item, attribute on some model without the need to use repository or factory to load the whole model with all attributes when we have to update only one single attribute.",[16,800,801],{},"I hope this will help you and you'll find it useful!",[209,803,804],{},"html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .saEQR, html code.shiki .saEQR{--shiki-default:#676E95;--shiki-default-font-style:italic}html pre.shiki code .sfFnM, html code.shiki .sfFnM{--shiki-default:#F78C6C;--shiki-default-font-style:italic}html pre.shiki code .stCc5, html code.shiki .stCc5{--shiki-default:#FFCB6B;--shiki-default-font-style:italic}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":20,"searchDepth":21,"depth":21,"links":806},[],"2020-12-17","Update a single field in Magento 2 without loading the whole model: use ResourceConnection to run a targeted SQL update, with a PHP example.",{"src":242,"alt":810},"Magento 2 update a single model value with ResourceConnection",{},"\u002Fblog\u002Fmagento-2-update-single-model-value",{"title":814,"description":808},"Magento 2 Update Single Field with ResourceConnection","blog\u002F2020-12-17-magento-2-update-single-model-value","Often you'll get in a situation when you have to update only one model field and at the same time you don't have a model object instance. Let's take a look how to achieve this.",[225,818,819,40],"model","sql update","kEyp5ViQNiASyFMxpgWHx9VCXQQX_87YlycNR1m3W-o",{"id":822,"title":823,"body":824,"date":1842,"description":1843,"extension":26,"image":1844,"meta":1846,"navigation":29,"path":1847,"pinned":31,"seo":1848,"stem":1850,"summary":1851,"tags":1852,"updated":1854,"__hash__":1855},"blog\u002Fblog\u002F2021-10-19-magento-2-view-models.md","Magento 2 view models: ArgumentInterface, layout XML, and when to use them",{"type":8,"value":825,"toc":1829},[826,837,844,866,869,876,881,884,892,896,899,915,918,925,928,1038,1048,1058,1270,1274,1280,1283,1412,1416,1419,1446,1457,1460,1607,1611,1614,1618,1698,1701,1777,1781,1796,1810,1815,1826],[16,827,828,831],{},[72,829],{"alt":74,"src":830},"\u002Fimages\u002Fmagento-2-view-models.webp",[77,832,833],{},[84,834,249],{"href":835,"rel":836},"https:\u002F\u002Fwww.pexels.com\u002Fphoto\u002Fperson-coding-on-a-macbook-pro-4974912\u002F",[100],[16,838,839,840,843],{},"A Magento 2 view model is a plain PHP class you attach to a block through layout XML. It holds the methods your ",[58,841,842],{},".phtml"," needs, without stuffing business logic into the block class or rewriting core blocks.",[16,845,846,847,850,851,854,855,858,859,862,863,121],{},"You declare it with a layout argument named ",[58,848,849],{},"view_model"," and type ",[58,852,853],{},"object",". The class must implement ",[58,856,857],{},"\\Magento\\Framework\\View\\Element\\Block\\ArgumentInterface",". In the template you pull it with ",[58,860,861],{},"$block->getViewModel()"," or ",[58,864,865],{},"$block->getData('view_model')",[16,867,868],{},"Use a view model when you want reusable, testable template helpers and you do not need Magento 1-style block backwards compatibility. Prefer a custom block (or preference) only when you must change block lifecycle behavior, cache keys, or something the template layer cannot own.",[16,870,871,872,875],{},"Below: layout examples (new blocks and ",[58,873,874],{},"referenceBlock","), a minimal ArgumentInterface class, DI compile, template usage, and a short view model vs block checklist.",[877,878,880],"h3",{"id":879},"what-is-a-magento-2-view-model","What is a Magento 2 view model?",[16,882,883],{},"A view model is an abstraction of the view: public methods (and optional constructor dependencies) that templates call. It moves features and business logic out of block classes into classes that are easier to maintain, test, and reuse.",[16,885,886,887,121],{},"Adobe documents this pattern in the ",[84,888,891],{"href":889,"rel":890},"https:\u002F\u002Fdevdocs.magento.com\u002Fguides\u002Fv2.4\u002Fextension-dev-guide\u002Fview-models.html",[100],"extension development guide on view models",[877,893,895],{"id":894},"when-to-use-view-models-vs-overriding-a-block","When to use view models (vs overriding a block)",[16,897,898],{},"Use a view model when:",[900,901,902,906,909,912],"ul",{},[903,904,905],"li",{},"You need extra data or helpers in a template",[903,907,908],{},"You want to avoid class preferences \u002F block rewrites",[903,910,911],{},"The logic is presentation-adjacent (labels, flags, small calculations), not core domain services you would put in an API layer anyway",[903,913,914],{},"Backwards compatibility with old Magento block patterns is not required",[16,916,917],{},"Skip view models (or pair them carefully) when you must change how the block itself caches, names itself, or participates in layout generation. That still belongs on the block.",[877,919,921,922],{"id":920},"layout-xml-argument-nameview_model-xsitypeobject","Layout XML: ",[58,923,924],{},"argument name=\"view_model\" xsi:type=\"object\"",[16,926,927],{},"Attach a view model to a block you own:",[128,929,931],{"className":130,"code":930,"language":132,"meta":20,"style":20},"\u003Cblock name=\"demoapp.content.viewmodel\" template=\"DemoApp_Catalog::demo.phtml\">\n    \u003Carguments>\n        \u003Cargument name=\"view_model\" xsi:type=\"object\">DemoApp\\Sales\\ViewModel\\ExampleViewModel\u003C\u002Fargument>\n    \u003C\u002Farguments>\n\u003C\u002Fblock>\n",[58,932,933,964,974,1021,1030],{"__ignoreMap":20},[80,934,935,937,939,941,943,945,948,950,953,955,957,960,962],{"class":137,"line":138},[80,936,142],{"class":141},[80,938,146],{"class":145},[80,940,165],{"class":149},[80,942,153],{"class":141},[80,944,156],{"class":141},[80,946,947],{"class":159},"demoapp.content.viewmodel",[80,949,156],{"class":141},[80,951,952],{"class":149}," template",[80,954,153],{"class":141},[80,956,156],{"class":141},[80,958,959],{"class":159},"DemoApp_Catalog::demo.phtml",[80,961,156],{"class":141},[80,963,188],{"class":141},[80,965,966,969,972],{"class":137,"line":21},[80,967,968],{"class":141},"    \u003C",[80,970,971],{"class":145},"arguments",[80,973,188],{"class":141},[80,975,976,979,982,984,986,988,990,992,995,998,1001,1003,1005,1007,1009,1012,1015,1017,1019],{"class":137,"line":197},[80,977,978],{"class":141},"        \u003C",[80,980,981],{"class":145},"argument",[80,983,165],{"class":149},[80,985,153],{"class":141},[80,987,156],{"class":141},[80,989,849],{"class":159},[80,991,156],{"class":141},[80,993,994],{"class":149}," xsi",[80,996,997],{"class":141},":",[80,999,1000],{"class":149},"type",[80,1002,153],{"class":141},[80,1004,156],{"class":141},[80,1006,853],{"class":159},[80,1008,156],{"class":141},[80,1010,1011],{"class":141},">",[80,1013,1014],{"class":193},"DemoApp\\Sales\\ViewModel\\ExampleViewModel",[80,1016,200],{"class":141},[80,1018,981],{"class":145},[80,1020,188],{"class":141},[80,1022,1023,1026,1028],{"class":137,"line":335},[80,1024,1025],{"class":141},"    \u003C\u002F",[80,1027,971],{"class":145},[80,1029,188],{"class":141},[80,1031,1032,1034,1036],{"class":137,"line":342},[80,1033,200],{"class":141},[80,1035,146],{"class":145},[80,1037,188],{"class":141},[16,1039,1040,1041,1043,1044,1047],{},"You can also attach the same pattern to existing Magento or third-party blocks with ",[58,1042,874],{},". Pass the fully qualified class name ",[51,1045,1046],{},"without"," a leading backslash.",[16,1049,1050,1051,1054,1055,997],{},"Example: add a view model to ",[58,1052,1053],{},"invoice_totals"," from ",[58,1056,1057],{},"sales_email_order_invoice_items.xml",[128,1059,1061],{"className":130,"code":1060,"language":132,"meta":20,"style":20},"\u003C?xml version=\"1.0\"?>\n\u003Cpage xmlns:xsi=\"http:\u002F\u002Fwww.w3.org\u002F2001\u002FXMLSchema-instance\"\n      xsi:noNamespaceSchemaLocation=\"urn:magento:framework:View\u002FLayout\u002Fetc\u002Fpage_configuration.xsd\"\n      label=\"Email Invoice Items List\"\n      design_abstraction=\"custom\">\n    \u003Cbody>\n        \u003CreferenceBlock name=\"invoice_totals\">\n            \u003Carguments>\n                \u003Cargument name=\"view_model\" xsi:type=\"object\">DemoApp\\Sales\\ViewModel\\ExampleViewModel\u003C\u002Fargument>\n            \u003C\u002Farguments>\n        \u003C\u002FreferenceBlock>\n    \u003C\u002Fbody>\n\u003C\u002Fpage>\n",[58,1062,1063,1085,1110,1129,1143,1159,1168,1186,1195,1236,1245,1254,1262],{"__ignoreMap":20},[80,1064,1065,1068,1070,1073,1075,1077,1080,1082],{"class":137,"line":138},[80,1066,1067],{"class":141},"\u003C?",[80,1069,132],{"class":145},[80,1071,1072],{"class":149}," version",[80,1074,153],{"class":141},[80,1076,156],{"class":141},[80,1078,1079],{"class":159},"1.0",[80,1081,156],{"class":141},[80,1083,1084],{"class":141},"?>\n",[80,1086,1087,1089,1092,1095,1097,1100,1102,1104,1107],{"class":137,"line":21},[80,1088,142],{"class":141},[80,1090,1091],{"class":145},"page",[80,1093,1094],{"class":149}," xmlns",[80,1096,997],{"class":141},[80,1098,1099],{"class":149},"xsi",[80,1101,153],{"class":141},[80,1103,156],{"class":141},[80,1105,1106],{"class":159},"http:\u002F\u002Fwww.w3.org\u002F2001\u002FXMLSchema-instance",[80,1108,1109],{"class":141},"\"\n",[80,1111,1112,1115,1117,1120,1122,1124,1127],{"class":137,"line":197},[80,1113,1114],{"class":149},"      xsi",[80,1116,997],{"class":141},[80,1118,1119],{"class":149},"noNamespaceSchemaLocation",[80,1121,153],{"class":141},[80,1123,156],{"class":141},[80,1125,1126],{"class":159},"urn:magento:framework:View\u002FLayout\u002Fetc\u002Fpage_configuration.xsd",[80,1128,1109],{"class":141},[80,1130,1131,1134,1136,1138,1141],{"class":137,"line":335},[80,1132,1133],{"class":149},"      label",[80,1135,153],{"class":141},[80,1137,156],{"class":141},[80,1139,1140],{"class":159},"Email Invoice Items List",[80,1142,1109],{"class":141},[80,1144,1145,1148,1150,1152,1155,1157],{"class":137,"line":342},[80,1146,1147],{"class":149},"      design_abstraction",[80,1149,153],{"class":141},[80,1151,156],{"class":141},[80,1153,1154],{"class":159},"custom",[80,1156,156],{"class":141},[80,1158,188],{"class":141},[80,1160,1161,1163,1166],{"class":137,"line":359},[80,1162,968],{"class":141},[80,1164,1165],{"class":145},"body",[80,1167,188],{"class":141},[80,1169,1170,1172,1174,1176,1178,1180,1182,1184],{"class":137,"line":365},[80,1171,978],{"class":141},[80,1173,874],{"class":145},[80,1175,165],{"class":149},[80,1177,153],{"class":141},[80,1179,156],{"class":141},[80,1181,1053],{"class":159},[80,1183,156],{"class":141},[80,1185,188],{"class":141},[80,1187,1188,1191,1193],{"class":137,"line":379},[80,1189,1190],{"class":141},"            \u003C",[80,1192,971],{"class":145},[80,1194,188],{"class":141},[80,1196,1197,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218,1220,1222,1224,1226,1228,1230,1232,1234],{"class":137,"line":384},[80,1198,1199],{"class":141},"                \u003C",[80,1201,981],{"class":145},[80,1203,165],{"class":149},[80,1205,153],{"class":141},[80,1207,156],{"class":141},[80,1209,849],{"class":159},[80,1211,156],{"class":141},[80,1213,994],{"class":149},[80,1215,997],{"class":141},[80,1217,1000],{"class":149},[80,1219,153],{"class":141},[80,1221,156],{"class":141},[80,1223,853],{"class":159},[80,1225,156],{"class":141},[80,1227,1011],{"class":141},[80,1229,1014],{"class":193},[80,1231,200],{"class":141},[80,1233,981],{"class":145},[80,1235,188],{"class":141},[80,1237,1238,1241,1243],{"class":137,"line":389},[80,1239,1240],{"class":141},"            \u003C\u002F",[80,1242,971],{"class":145},[80,1244,188],{"class":141},[80,1246,1247,1250,1252],{"class":137,"line":402},[80,1248,1249],{"class":141},"        \u003C\u002F",[80,1251,874],{"class":145},[80,1253,188],{"class":141},[80,1255,1256,1258,1260],{"class":137,"line":407},[80,1257,1025],{"class":141},[80,1259,1165],{"class":145},[80,1261,188],{"class":141},[80,1263,1264,1266,1268],{"class":137,"line":419},[80,1265,200],{"class":141},[80,1267,1091],{"class":145},[80,1269,188],{"class":141},[877,1271,1273],{"id":1272},"the-view-model-class-and-argumentinterface","The view model class and ArgumentInterface",[16,1275,1276,1277,1279],{},"Your view model can expose as many methods as you need. The hard requirement: implement ",[58,1278,857],{},". Constructor injection works like any other Magento class.",[16,1281,1282],{},"Minimal example:",[128,1284,1286],{"className":276,"code":1285,"language":278,"meta":20,"style":20},"namespace DemoApp\\Sales\\ViewModel;\n\nuse Magento\\Framework\\View\\Element\\Block\\ArgumentInterface;\n\nclass ExampleViewModel implements ArgumentInterface\n{\n    public function getSomeDemoText()\n    {\n        return __('My demo text');\n    }\n}\n",[58,1287,1288,1308,1312,1344,1348,1362,1366,1379,1384,1404,1408],{"__ignoreMap":20},[80,1289,1290,1293,1296,1298,1301,1303,1306],{"class":137,"line":138},[80,1291,1292],{"class":285},"namespace",[80,1294,1295],{"class":479}," DemoApp",[80,1297,292],{"class":141},[80,1299,1300],{"class":479},"Sales",[80,1302,292],{"class":141},[80,1304,1305],{"class":479},"ViewModel",[80,1307,308],{"class":141},[80,1309,1310],{"class":137,"line":21},[80,1311,332],{"emptyLinePlaceholder":29},[80,1313,1314,1316,1318,1320,1322,1324,1327,1329,1332,1334,1337,1339,1342],{"class":137,"line":197},[80,1315,286],{"class":285},[80,1317,289],{"class":193},[80,1319,292],{"class":141},[80,1321,295],{"class":193},[80,1323,292],{"class":141},[80,1325,1326],{"class":193},"View",[80,1328,292],{"class":141},[80,1330,1331],{"class":193},"Element",[80,1333,292],{"class":141},[80,1335,1336],{"class":193},"Block",[80,1338,292],{"class":141},[80,1340,1341],{"class":193},"ArgumentInterface",[80,1343,308],{"class":141},[80,1345,1346],{"class":137,"line":335},[80,1347,332],{"emptyLinePlaceholder":29},[80,1349,1350,1353,1356,1359],{"class":137,"line":342},[80,1351,1352],{"class":149},"class",[80,1354,1355],{"class":479}," ExampleViewModel",[80,1357,1358],{"class":149}," implements",[80,1360,1361],{"class":479}," ArgumentInterface\n",[80,1363,1364],{"class":137,"line":359},[80,1365,621],{"class":141},[80,1367,1368,1371,1373,1376],{"class":137,"line":365},[80,1369,1370],{"class":149},"    public",[80,1372,466],{"class":149},[80,1374,1375],{"class":469}," getSomeDemoText",[80,1377,1378],{"class":141},"()\n",[80,1380,1381],{"class":137,"line":379},[80,1382,1383],{"class":141},"    {\n",[80,1385,1386,1389,1392,1394,1396,1399,1401],{"class":137,"line":384},[80,1387,1388],{"class":627},"        return",[80,1390,1391],{"class":469}," __",[80,1393,594],{"class":141},[80,1395,656],{"class":141},[80,1397,1398],{"class":159},"My demo text",[80,1400,656],{"class":141},[80,1402,1403],{"class":141},");\n",[80,1405,1406],{"class":137,"line":389},[80,1407,790],{"class":141},[80,1409,1410],{"class":137,"line":402},[80,1411,546],{"class":141},[877,1413,1415],{"id":1414},"compile-di-before-using-it-in-templates","Compile DI before using it in templates",[16,1417,1418],{},"After you add the class and layout argument, compile DI so generated code and interceptors stay in sync:",[128,1420,1424],{"className":1421,"code":1422,"language":1423,"meta":20,"style":20},"language-bash shiki shiki-themes material-theme-palenight","bin\u002Fmagento setup:di:compile\n# short form\nbin\u002Fmagento s:d:c\n","bash",[58,1425,1426,1434,1439],{"__ignoreMap":20},[80,1427,1428,1431],{"class":137,"line":138},[80,1429,1430],{"class":479},"bin\u002Fmagento",[80,1432,1433],{"class":159}," setup:di:compile\n",[80,1435,1436],{"class":137,"line":21},[80,1437,1438],{"class":338},"# short form\n",[80,1440,1441,1443],{"class":137,"line":197},[80,1442,1430],{"class":479},[80,1444,1445],{"class":159}," s:d:c\n",[877,1447,1449,1450,1453,1454],{"id":1448},"template-usage-getviewmodel-and-getdataview_model","Template usage: ",[58,1451,1452],{},"getViewModel()"," and ",[58,1455,1456],{},"getData('view_model')",[16,1458,1459],{},"Both of these resolve the same argument:",[128,1461,1463],{"className":276,"code":1462,"language":278,"meta":20,"style":20},"\u003C?php\n\u002F** @var $viewModel \\DemoApp\\Sales\\ViewModel\\ExampleViewModel *\u002F\n$viewModel = $block->getViewModel();\n$viewModelText = $block->getData('view_model');\n?>\n\u003Cp>\u003C?= $block->escapeHtml($viewModel->getSomeDemoText()); ?>\u003C\u002Fp>\n\u003Cp>\u003C?= $block->escapeHtml($viewModelText->getSomeDemoText()); ?>\u003C\u002Fp>\n",[58,1464,1465,1472,1482,1504,1532,1536,1574],{"__ignoreMap":20},[80,1466,1467,1469],{"class":137,"line":138},[80,1468,1067],{"class":141},[80,1470,1471],{"class":193},"php\n",[80,1473,1474,1477,1479],{"class":137,"line":21},[80,1475,1476],{"class":338},"\u002F** ",[80,1478,349],{"class":348},[80,1480,1481],{"class":338}," $viewModel \\DemoApp\\Sales\\ViewModel\\ExampleViewModel *\u002F\n",[80,1483,1484,1487,1490,1492,1494,1496,1498,1501],{"class":137,"line":197},[80,1485,1486],{"class":141},"$",[80,1488,1489],{"class":193},"viewModel ",[80,1491,153],{"class":141},[80,1493,371],{"class":141},[80,1495,146],{"class":193},[80,1497,642],{"class":141},[80,1499,1500],{"class":469},"getViewModel",[80,1502,1503],{"class":141},"();\n",[80,1505,1506,1508,1511,1513,1515,1517,1519,1522,1524,1526,1528,1530],{"class":137,"line":335},[80,1507,1486],{"class":141},[80,1509,1510],{"class":193},"viewModelText ",[80,1512,153],{"class":141},[80,1514,371],{"class":141},[80,1516,146],{"class":193},[80,1518,642],{"class":141},[80,1520,1521],{"class":469},"getData",[80,1523,594],{"class":141},[80,1525,656],{"class":141},[80,1527,849],{"class":159},[80,1529,656],{"class":141},[80,1531,1403],{"class":141},[80,1533,1534],{"class":137,"line":342},[80,1535,1084],{"class":141},[80,1537,1538,1540,1542,1545,1547,1549,1551,1554,1556,1559,1561,1564,1567,1570,1572],{"class":137,"line":359},[80,1539,142],{"class":141},[80,1541,16],{"class":193},[80,1543,1544],{"class":141},">\u003C?=",[80,1546,371],{"class":141},[80,1548,146],{"class":193},[80,1550,642],{"class":141},[80,1552,1553],{"class":469},"escapeHtml",[80,1555,774],{"class":141},[80,1557,1558],{"class":193},"viewModel",[80,1560,642],{"class":141},[80,1562,1563],{"class":469},"getSomeDemoText",[80,1565,1566],{"class":141},"());",[80,1568,1569],{"class":141}," ?>\u003C\u002F",[80,1571,16],{"class":193},[80,1573,188],{"class":141},[80,1575,1576,1578,1580,1582,1584,1586,1588,1590,1592,1595,1597,1599,1601,1603,1605],{"class":137,"line":365},[80,1577,142],{"class":141},[80,1579,16],{"class":193},[80,1581,1544],{"class":141},[80,1583,371],{"class":141},[80,1585,146],{"class":193},[80,1587,642],{"class":141},[80,1589,1553],{"class":469},[80,1591,774],{"class":141},[80,1593,1594],{"class":193},"viewModelText",[80,1596,642],{"class":141},[80,1598,1563],{"class":469},[80,1600,1566],{"class":141},[80,1602,1569],{"class":141},[80,1604,16],{"class":193},[80,1606,188],{"class":141},[877,1608,1610],{"id":1609},"practical-example","Practical example",[16,1612,1613],{},"View models shine when you customize a template: conditional copy, customer-group checks, payment-method notes on an invoice, small flags that used to live in an overridden block. Keep domain-heavy work in services; keep the view model thin and template-focused.",[877,1615,1617],{"id":1616},"magento-2-view-model-vs-block-quick-difference","Magento 2 view model vs block: quick difference",[1619,1620,1621,1639],"table",{},[1622,1623,1624],"thead",{},[1625,1626,1627,1630,1635],"tr",{},[1628,1629],"th",{},[1628,1631,1632],{},[51,1633,1634],{},"View model",[1628,1636,1637],{},[51,1638,1336],{},[1640,1641,1642,1654,1675,1687],"tbody",{},[1625,1643,1644,1648,1651],{},[1645,1646,1647],"td",{},"Role",[1645,1649,1650],{},"Methods\u002Fdata for the template",[1645,1652,1653],{},"Layout participant, template host, cache\u002Fname\u002Flifecycle",[1625,1655,1656,1659,1665],{},[1645,1657,1658],{},"Wiring",[1645,1660,1661,1662,1664],{},"Layout ",[58,1663,849],{}," object argument",[1645,1666,1667,1668,1671,1672],{},"Class in layout, preferences, ",[58,1669,1670],{},"phtml"," via ",[58,1673,1674],{},"$block",[1625,1676,1677,1680,1684],{},[1645,1678,1679],{},"Interface",[1645,1681,1682],{},[58,1683,1341],{},[1645,1685,1686],{},"Extends Magento block classes",[1625,1688,1689,1692,1695],{},[1645,1690,1691],{},"Prefer when",[1645,1693,1694],{},"Add logic without rewrite",[1645,1696,1697],{},"Change block behavior itself",[16,1699,1700],{},"If the question is \"do I need a preference on this block just to print one extra string?\", start with a view model.",[1702,1703,1704,1709,1717,1725,1737,1744,1759,1763,1766,1770],"faq",{},[1705,1706,1708],"h4",{"id":1707},"what-is-argumentinterface-for-in-magento-2-view-models","What is ArgumentInterface for in Magento 2 view models?",[16,1710,1711,1713,1714,1716],{},[58,1712,1341],{}," marks the class as a valid layout argument object Magento can inject into a block. Without it, the ",[58,1715,849],{}," argument should not be treated as a proper view model.",[1705,1718,1720,1721,1724],{"id":1719},"how-do-i-set-layout-argument-nameview_model","How do I set layout argument ",[58,1722,1723],{},"name=\"view_model\"","?",[16,1726,1727,1728,1730,1731,1734,1735,121],{},"In the block's layout XML (or a ",[58,1729,874],{},"), add ",[58,1732,1733],{},"\u003Cargument name=\"view_model\" xsi:type=\"object\">Vendor\\Module\\ViewModel\\YourClass\u003C\u002Fargument>",". No leading ",[58,1736,292],{},[1705,1738,1740,1741,1743],{"id":1739},"how-do-i-call-block-getviewmodel-in-a-template","How do I call ",[58,1742,861],{}," in a template?",[16,1745,1746,1747,1749,1750,1753,1754,1756,1757,121],{},"After DI compile, in the ",[58,1748,842],{},": ",[58,1751,1752],{},"$viewModel = $block->getViewModel();"," then call your methods. ",[58,1755,865],{}," is equivalent if the argument name is ",[58,1758,849],{},[1705,1760,1762],{"id":1761},"magento-2-view-model-vs-block-which-should-i-choose","Magento 2 view model vs block: which should I choose?",[16,1764,1765],{},"Choose a view model to extend template behavior without overriding blocks. Choose (or extend) a block when you need block-level behavior: caching, identity, structural layout changes, or logic that is not \"template helper\" scope.",[1705,1767,1769],{"id":1768},"where-is-the-official-magento-2-view-models-documentation","Where is the official Magento 2 view models documentation?",[16,1771,1772,1773,121],{},"Adobe DevDocs: ",[84,1774,1776],{"href":889,"rel":1775},[100],"View models",[877,1778,1780],{"id":1779},"wrap-up","Wrap-up",[16,1782,1783,1784,1786,1787,1789,1790,1792,1793,1795],{},"View models keep templates flexible and blocks thinner: ",[58,1785,1341],{}," class, ",[58,1788,849],{}," layout argument, compile DI, call ",[58,1791,1452],{}," in the ",[58,1794,842],{},". Use them when you would otherwise rewrite a block for template-only needs.",[16,1797,1798,1799,1804,1805,121],{},"If you are stuck on a Magento 2 \u002F Adobe Commerce customization, I take on freelance Magento and Laravel work: ",[84,1800,1803],{"href":1801,"rel":1802},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdenisveg\u002F",[100],"let's work together on LinkedIn"," or start from ",[84,1806,1809],{"href":1807,"rel":1808},"https:\u002F\u002Fdenisveg.dev\u002F",[100],"denisveg.dev",[16,1811,1812],{},[51,1813,1814],{},"Related on this site:",[900,1816,1817,1822],{},[903,1818,1819],{},[84,1820,45],{"href":1821},"\u002Fblog\u002Fmagento-2-block-condition-for-template",[903,1823,1824],{},[84,1825,230],{"href":812},[209,1827,1828],{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sx098, html code.shiki .sx098{--shiki-default:#F78C6C}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .sdLwU, html code.shiki .sdLwU{--shiki-default:#82AAFF}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .saEQR, html code.shiki .saEQR{--shiki-default:#676E95;--shiki-default-font-style:italic}html pre.shiki code .sfFnM, html code.shiki .sfFnM{--shiki-default:#F78C6C;--shiki-default-font-style:italic}",{"title":20,"searchDepth":21,"depth":21,"links":1830},[1831,1832,1833,1835,1836,1837,1839,1840,1841],{"id":879,"depth":197,"text":880},{"id":894,"depth":197,"text":895},{"id":920,"depth":197,"text":1834},"Layout XML: argument name=\"view_model\" xsi:type=\"object\"",{"id":1272,"depth":197,"text":1273},{"id":1414,"depth":197,"text":1415},{"id":1448,"depth":197,"text":1838},"Template usage: getViewModel() and getData('view_model')",{"id":1609,"depth":197,"text":1610},{"id":1616,"depth":197,"text":1617},{"id":1779,"depth":197,"text":1780},"2021-10-19","Learn Magento 2 view models: ArgumentInterface, the name=\"view_model\" layout argument, getViewModel(), and view model vs block, with XML and PHP examples.",{"src":830,"alt":1845},"Magento 2 view models, ArgumentInterface and layout XML",{},"\u002Fblog\u002Fmagento-2-view-models",{"title":1849,"description":1843},"Magento 2 View Models & ArgumentInterface Guide","blog\u002F2021-10-19-magento-2-view-models","Magento 2 view models let you inject logic into templates without overriding blocks. This guide covers ArgumentInterface, layout XML (name=\"view_model\"), getViewModel(), and when a view model beats a block.",[225,1853,40],"view-models","2026-09-18","1h1T3qQ-6_xw0JoQ9MZGHwiFaWQ9VN0dAYJJX7WwMDI",{"id":1857,"title":1858,"body":1859,"date":2018,"description":2019,"extension":26,"image":2020,"meta":2022,"navigation":29,"path":2023,"pinned":31,"seo":2024,"stem":2026,"summary":2027,"tags":2028,"updated":2031,"__hash__":2032},"blog\u002Fblog\u002F2023-01-03-8-essential-developer-tools-to-make-you-more-productive.md","8 Essential Developer Tools to Make You More Productive on Mac",{"type":8,"value":1860,"toc":2008},[1861,1873,1877,1885,1888,1892,1899,1907,1911,1918,1922,1930,1956,1960,1971,1975,1982,1986,1994,1997,2001],[16,1862,1863,1866],{},[72,1864],{"alt":74,"src":1865},"\u002Fimages\u002Fdeveloper-tools-to-make-you-more-productive.webp",[77,1867,1868],{},[84,1869,1872],{"href":1870,"rel":1871},"https:\u002F\u002Fwww.pexels.com\u002Fphoto\u002Ffaceless-person-using-touchpad-on-laptop-4126724\u002F",[100],"Photo by Ketut Subiyanto",[877,1874,1876],{"id":1875},"numi-app","Numi app",[16,1878,1879,1884],{},[84,1880,1883],{"href":1881,"rel":1882},"https:\u002F\u002Fnumi.app\u002F",[100],"Numi"," is a calculator app for Mac. For me Numi is a better alternative to the default calculator app on Mac with additional features that will help developers that are working with clients in different timezones and currencies to do a quick calculation. It supports a lot of functions and operators, variables and constants.",[16,1886,1887],{},"The app is free but if you want unlimited notes and iCloud sync you need to pay $19,99 + VAT.",[877,1889,1891],{"id":1890},"grammarly","Grammarly",[16,1893,1894,1898],{},[84,1895,1891],{"href":1896,"rel":1897},"https:\u002F\u002Fgrammarly.com\u002F",[100]," is my favorite tool for writing. It is not just a great tool for developers but also for everyone who need to write a lot of documents. It is a great alternative to the default spell checker on Mac.",[16,1900,1901,1902,121],{},"The alternative for Grammarly is ",[84,1903,1906],{"href":1904,"rel":1905},"https:\u002F\u002Fhemingwayapp.com\u002F",[100],"Hemingway Editor",[877,1908,1910],{"id":1909},"rectangle","Rectangle",[16,1912,1913,1917],{},[84,1914,1910],{"href":1915,"rel":1916},"https:\u002F\u002Frectangleapp.com",[100]," is a free, open-source window manager for macOS. You move and resize windows with keyboard shortcuts or by dragging them to screen edges, which is a big win when you juggle an editor, a terminal, a browser and a database client at the same time. It is the app I use instead of Spectacle, which is no longer maintained.",[877,1919,1921],{"id":1920},"alfred","Alfred",[16,1923,1924,1929],{},[84,1925,1928],{"href":1926,"rel":1927},"https:\u002F\u002Fwww.alfredapp.com\u002F",[100],"Alfred app"," is a great tool and alternative to the default Spotlight app on Mac. It supports a lot of features:",[900,1931,1932,1935,1938,1941,1944,1947,1950,1953],{},[903,1933,1934],{},"search for files",[903,1936,1937],{},"find apps & files",[903,1939,1940],{},"calculate",[903,1942,1943],{},"search the web",[903,1945,1946],{},"system commands",[903,1948,1949],{},"clipboard history",[903,1951,1952],{},"1password integration",[903,1954,1955],{},"etc.",[877,1957,1959],{"id":1958},"laravel-herd","Laravel Herd",[16,1961,1962,1966,1967,1970],{},[84,1963,1959],{"href":1964,"rel":1965},"https:\u002F\u002Fherd.laravel.com",[100]," is a native macOS app that gives you a complete local PHP environment: PHP, a web server and Node, without Docker or manual configuration. Sites are served on ",[58,1968,1969],{},".test"," domains and you can switch PHP versions per project, which is handy when you move between Laravel and Magento \u002F Adobe Commerce projects that need different versions.",[877,1972,1974],{"id":1973},"tableplus","TablePlus",[16,1976,1977,1981],{},[84,1978,1974],{"href":1979,"rel":1980},"https:\u002F\u002Ftableplus.com",[100]," is a fast, native database GUI client. It supports MySQL, MariaDB, PostgreSQL, SQLite, Redis and more, so one app covers the databases you touch in Laravel and Magento work. It's what I open when I need to inspect data or run a quick query.",[877,1983,1985],{"id":1984},"cleanshot-x-paid-app","CleanShot X (Paid app)",[16,1987,1988,1993],{},[84,1989,1992],{"href":1990,"rel":1991},"https:\u002F\u002Fcleanshot.com\u002F",[100],"Cleanshot X"," is for me the best snipping tool on Mac. It's way better than the default snipping tool on Mac. What I like is that it can record as gif directly, showing things like clicks or even keystrokes. It has many nice features for taking screenshots too.",[16,1995,1996],{},"It’s paid, but it was one of the best apps I’ve ever purchased.",[877,1998,2000],{"id":1999},"loom","Loom",[16,2002,2003,2007],{},[84,2004,2000],{"href":2005,"rel":2006},"https:\u002F\u002Fwww.loom.com\u002F",[100]," is a great tool for recording your screen. It's a great alternative to the default QuickTime app on Mac. It's a great collaboration tool between developers and project managers or just a casual users who need to quickly record their screen and be able to simply upload it in cloud and share the URL link.",{"title":20,"searchDepth":21,"depth":21,"links":2009},[2010,2011,2012,2013,2014,2015,2016,2017],{"id":1875,"depth":197,"text":1876},{"id":1890,"depth":197,"text":1891},{"id":1909,"depth":197,"text":1910},{"id":1920,"depth":197,"text":1921},{"id":1958,"depth":197,"text":1959},{"id":1973,"depth":197,"text":1974},{"id":1984,"depth":197,"text":1985},{"id":1999,"depth":197,"text":2000},"2023-01-03","Eight Mac apps for Laravel and Magento developers: Numi, Grammarly, Rectangle, Alfred, Laravel Herd, TablePlus, CleanShot X and Loom, and what each does.",{"src":1865,"alt":2021},"Developer using a laptop, Mac developer productivity tools",{},"\u002Fblog\u002F8-essential-developer-tools-to-make-you-more-productive",{"title":2025,"description":2019},"8 Essential Mac Developer Tools for Laravel & Magento (2026)","blog\u002F2023-01-03-8-essential-developer-tools-to-make-you-more-productive","Learn about 8 essential developer tools for Mac that help Laravel and Magento developers stay productive: window management, local PHP, databases, screenshots and more.",[2029,2030,40],"dev-tools","productivity","2026-09-21","eOT3U2OT9AvlQzbVKnkedvJCU0nyOgFRfg28iWBmSck",{"id":2034,"title":2035,"body":2036,"date":2190,"description":2191,"extension":26,"image":27,"meta":2192,"navigation":29,"path":2193,"pinned":31,"seo":2194,"stem":2196,"summary":2197,"tags":2198,"updated":27,"__hash__":2204},"blog\u002Fblog\u002F2026-07-03-nativephp-flutter-react-native.md","NativePHP vs Flutter vs React Native for Stores (2026)",{"type":8,"value":2037,"toc":2183},[2038,2041,2055,2058,2062,2065,2072,2076,2079,2091,2094,2104,2108,2111,2114,2120,2124,2131,2138,2141,2148,2151,2157,2161,2164,2171,2174],[16,2039,2040],{},"If you run an online store, sooner or later somebody will tell you: \"You need a mobile app.\" And then comes the confusing part — every developer or agency you talk to recommends a different technology, and suddenly you're expected to have an opinion on things like \"cross-platform frameworks.\"",[16,2042,2043,2044,2047,2048,2051,2052,121],{},"I get this question a lot from clients, so let me break down the three options you'll hear about most in 2026: ",[51,2045,2046],{},"Flutter",", ",[51,2049,2050],{},"React Native",", and the newcomer that's especially interesting if your site already runs on PHP — ",[51,2053,2054],{},"NativePHP",[16,2056,2057],{},"No jargon, I promise. Just what each one is, what it costs you in practice, and when I'd recommend it.",[11,2059,2061],{"id":2060},"first-things-first-what-is-a-cross-platform-framework","First things first: what is a cross-platform framework?",[16,2063,2064],{},"Building an app the \"old way\" means building it twice — once for iPhone, once for Android. Two codebases, two teams, roughly double the budget.",[16,2066,2067,2068,2071],{},"Cross-platform frameworks let developers write the app ",[51,2069,2070],{},"once"," and ship it to both app stores. That's why almost every business app today is built this way. The three tools below all do this — they just get there differently.",[11,2073,2075],{"id":2074},"flutter-the-polished-one","Flutter — the polished one",[16,2077,2078],{},"Flutter is Google's framework, and right now it's the most popular of the three — it holds around 46% of the cross-platform market compared to React Native's 35%.",[16,2080,2081,2082,2085,2086,2090],{},"Its biggest strength is ",[51,2083,2084],{},"visual control",". Flutter draws every pixel itself, which means your app looks ",[2087,2088,2089],"em",{},"exactly"," the same on every device, and animations are buttery smooth. If your brand lives or dies on visual experience — fashion, cosmetics, lifestyle products — Flutter apps can look genuinely stunning.",[16,2092,2093],{},"The trade-off? Flutter uses its own programming language (Dart), which most web teams don't know. That usually means hiring a dedicated Flutter developer or agency, and your existing website code can't be reused at all.",[16,2095,2096,2099,2100,2103],{},[51,2097,2098],{},"Choose Flutter when:"," the app ",[2087,2101,2102],{},"is"," the brand experience and you have the budget for a dedicated mobile team.",[11,2105,2107],{"id":2106},"react-native-the-pragmatic-one","React Native — the pragmatic one",[16,2109,2110],{},"React Native comes from Meta (Facebook), and it's built on JavaScript — the language of the web. That's its superpower: if the team behind your website already works with React (very common), they can build your mobile app too, sharing logic between web and app.",[16,2112,2113],{},"Performance used to be React Native's weak spot, but its \"New Architecture\" closed that gap — in 2026, both Flutter and React Native are simply good enough for the vast majority of business apps. Honestly, your customers won't be able to tell the difference.",[16,2115,2116,2119],{},[51,2117,2118],{},"Choose React Native when:"," you already have a web team, you want to move fast, and you'd rather extend the team you trust than hire a new one.",[11,2121,2123],{"id":2122},"nativephp-the-interesting-newcomer","NativePHP — the interesting newcomer",[16,2125,2126,2127,2130],{},"Here's where it gets personal for me. Most e-commerce sites and business applications I work on — Magento 2 \u002F Adobe Commerce stores, Laravel applications — run on ",[51,2128,2129],{},"PHP",". Until recently, PHP developers had no real path to mobile apps.",[16,2132,2133,2134,2137],{},"NativePHP changed that. It lets developers build native iOS and Android apps using Laravel — the same framework, the same skills, often reusing big parts of the existing backend code. And as of February 2026, NativePHP for Mobile v3 is completely ",[51,2135,2136],{},"free and open source"," (MIT licensed), which removed the last real barrier to trying it.",[16,2139,2140],{},"It's matured quickly: the latest versions run PHP persistently on the device, handle background jobs, and there's even a companion app called Jump that lets you preview your app on a real phone without compiling anything. Extra native features like biometrics or geolocation are paid plugins — but one-time purchases, not subscriptions.",[16,2142,2143,2144,2147],{},"Why does this matter to you as a store owner? ",[51,2145,2146],{},"Cost and continuity."," If your store already runs on PHP, the developer who knows your codebase can build your mobile app too. No new agency, no rebuilding your business logic in another language, no coordination overhead between two teams.",[16,2149,2150],{},"The honest caveat: NativePHP is young. Flutter and React Native have a decade of production apps behind them; NativePHP's ecosystem is still growing. For a highly complex, animation-heavy app, I'd still point you elsewhere. For a solid, functional companion app to an existing PHP store — catalog, account, orders, push notifications — it's become a very sensible option.",[16,2152,2153,2156],{},[51,2154,2155],{},"Choose NativePHP when:"," your store runs on Laravel or PHP, you want one team owning everything, and your app is a companion to the store rather than the product itself.",[11,2158,2160],{"id":2159},"the-short-version","The short version",[16,2162,2163],{},"There's no universal winner here — anyone who tells you otherwise is selling something.",[16,2165,2166,2167,2170],{},"My rule of thumb: ",[51,2168,2169],{},"start from the team and stack you already have."," The framework matters less than the people building with it, and the most expensive app is the one built by a second team that doesn't know your business.",[16,2172,2173],{},"If you're picking from scratch with no existing constraints, Flutter for design-first apps, React Native for web-first teams. But if you're on Magento or Laravel and you've been putting off \"the app question\" because it felt like a whole separate world — that's exactly the gap NativePHP just closed.",[16,2175,2176,2177,2182],{},"Whatever direction you take, having someone who understands your stack makes these decisions much easier. I work with Laravel and Magento 2 \u002F Adobe Commerce daily — whether it's your store, a custom web application, an integration, or just a second opinion on a technical decision, ",[84,2178,2181],{"href":2179,"rel":2180},"https:\u002F\u002Fdenisveg.dev",[100],"get in touch"," and I'll be happy to help.",{"title":20,"searchDepth":21,"depth":21,"links":2184},[2185,2186,2187,2188,2189],{"id":2060,"depth":21,"text":2061},{"id":2074,"depth":21,"text":2075},{"id":2106,"depth":21,"text":2107},{"id":2122,"depth":21,"text":2123},{"id":2159,"depth":21,"text":2160},"2026-07-03","NativePHP vs Flutter vs React Native in 2026: cross-platform performance, cost and team fit compared, and when a PHP or Laravel store should pick NativePHP.",{},"\u002Fblog\u002Fmobile-app-nativephp-flutter-react-native",{"title":2195,"description":2191},"NativePHP vs Flutter vs React Native: 2026 Comparison","blog\u002F2026-07-03-nativephp-flutter-react-native","NativePHP vs Flutter vs React Native in 2026 — an honest, jargon-free comparison for store owners deciding how to build their mobile app.",[2199,40,2200,2201,2202,2203],"mobile-development","laravel","nativephp","flutter","react-native","gHuierzy60NfnPkvMWW-IV-_xcSukhGhP0QNz7mIoMU",{"id":2206,"title":2207,"body":2208,"date":2348,"description":2349,"extension":26,"image":2350,"meta":2352,"navigation":29,"path":2353,"pinned":31,"seo":2354,"stem":2356,"summary":2357,"tags":2358,"updated":27,"__hash__":2363},"blog\u002Fblog\u002F2026-07-05-cloudflare-slovenian-location.md","Cloudflare's 1.1.1.1 Now Has a Slovenian PoP",{"type":8,"value":2209,"toc":2341},[2210,2215,2220,2223,2226,2230,2233,2236,2239,2253,2257,2260,2263,2267,2270,2280,2283,2290,2294,2315,2321,2331,2334,2338],[16,2211,2212],{},[72,2213],{"alt":2207,"src":2214},"\u002Fimages\u002Fcloudflare-1111-slovenia-hero.webp",[2216,2217,2219],"h1",{"id":2218},"cloudflares-1111-dns-is-now-served-locally-in-slovenia-heres-why-that-matters","Cloudflare's 1.1.1.1 DNS Is Now Served Locally in Slovenia — Here's Why That Matters",[16,2221,2222],{},"Cloudflare, the world's largest CDN provider, has quietly deployed servers in Slovenia that answer DNS queries for its public resolver, 1.1.1.1. Until now, queries from Slovenian networks were typically routed to points of presence in Vienna, Zagreb, or Milan. With a local node, response times drop to just a few milliseconds — effectively as fast as your ISP's own resolver, and often faster.",[16,2224,2225],{},"If you've been hesitant to switch to a public DNS resolver because of the latency penalty, that argument just evaporated for anyone browsing from Slovenia.",[11,2227,2229],{"id":2228},"a-quick-refresher-what-1111-actually-is","A quick refresher: what 1.1.1.1 actually is",[16,2231,2232],{},"DNS is the phone book of the internet — every time you open a website, your device first asks a DNS resolver to translate the domain name into an IP address. By default, that resolver belongs to your ISP.",[16,2234,2235],{},"Cloudflare's 1.1.1.1 is a free, privacy-focused alternative. It's consistently among the fastest public resolvers globally, and Cloudflare commits to not selling query data or using it for ad targeting.",[16,2237,2238],{},"Beyond the plain resolver, Cloudflare offers two filtered variants worth knowing about:",[900,2240,2241,2247],{},[903,2242,2243,2246],{},[51,2244,2245],{},"1.1.1.2"," — blocks known malicious domains (phishing, malware distribution, and similar threats) at the DNS level",[903,2248,2249,2252],{},[51,2250,2251],{},"1.1.1.3"," — everything 1.1.1.2 does, plus filtering of adult content, making it a simple network-wide family filter you can set on a home router in about a minute",[11,2254,2256],{"id":2255},"why-local-presence-changes-the-math","Why local presence changes the math",[16,2258,2259],{},"DNS latency compounds. A single page load can trigger dozens of DNS lookups — the page itself, its CDN assets, analytics scripts, fonts, embedded widgets. Shaving even 10–15 ms off each uncached lookup adds up to noticeably snappier browsing, especially on first visits to a site.",[16,2261,2262],{},"With Cloudflare now answering from within Slovenia, traceroutes from local ISPs show responses in the 1–3 ms range on fiber connections. That's the kind of number you previously only got from your ISP's own resolver sitting a few hops away.",[11,2264,2266],{"id":2265},"privacy-what-switching-your-dns-does-and-doesnt-do","Privacy: what switching your DNS does — and doesn't — do",[16,2268,2269],{},"This is where a lot of people overestimate the benefit, so let's be precise.",[16,2271,2272,2273,862,2276,2279],{},"If you use 1.1.1.1 over ",[51,2274,2275],{},"DNS over HTTPS (DoH)",[51,2277,2278],{},"DNS over TLS (DoT)",", your DNS queries are encrypted. Your ISP can no longer see, log, or tamper with your lookups, which neutralizes DNS hijacking, spoofing, and DNS-level traffic inspection.",[16,2281,2282],{},"But your browsing isn't invisible. When your browser opens an HTTPS connection, it still sends the hostname in plaintext inside the TLS ClientHello — the SNI field. Your ISP can't see your DNS queries anymore, but it can still see which domains you're connecting to.",[16,2284,2285,2286,2289],{},"The missing piece is ",[51,2287,2288],{},"ECH (Encrypted Client Hello)",", the successor to ESNI, which encrypts the entire ClientHello including the SNI. Cloudflare supports it on their edge, and recent Firefox versions can use it, but adoption across the wider web is still ramping up. Once ECH becomes commonplace, DoH + ECH together will genuinely hide your browsing destinations from network-level observers. Until then, if you need full traffic privacy, that's still VPN or Tor territory — each with its own trust trade-offs.",[11,2291,2293],{"id":2292},"how-to-switch-60-seconds","How to switch (60 seconds)",[16,2295,2296,2299,2300,2303,2304,2307,2308,2310,2311,2314],{},[51,2297,2298],{},"On your router"," (applies to every device on the network): set primary DNS to ",[58,2301,2302],{},"1.1.1.1"," and secondary to ",[58,2305,2306],{},"1.0.0.1"," — or ",[58,2309,2251],{}," \u002F ",[58,2312,2313],{},"1.0.0.3"," if you want the family filter.",[16,2316,2317,2320],{},[51,2318,2319],{},"In your browser"," (with encryption): enable DoH in settings — Firefox, Chrome, and Edge all support it natively with Cloudflare as a built-in provider option.",[16,2322,2323,2326,2327,2330],{},[51,2324,2325],{},"Verify it works",": visit ",[58,2328,2329],{},"1.1.1.1\u002Fhelp",", which tells you whether you're actually using Cloudflare and whether DoH is active.",[16,2332,2333],{},"Want to compare resolvers yourself? Run a quick benchmark at a DNS speed-testing site and check latency to Cloudflare, Quad9, Google DNS, and your ISP from your own connection. With the new Slovenian node, don't be surprised if 1.1.1.1 tops the list.",[11,2335,2337],{"id":2336},"the-takeaway","The takeaway",[16,2339,2340],{},"Local infrastructure keeps arriving in smaller markets, and that's good news all around. For Slovenian users, there's now essentially no performance trade-off in choosing a resolver based on privacy policy and filtering features rather than proximity. Fast, encrypted, optionally filtered DNS — for free, with single-digit-millisecond responses. Hard to argue with that.",{"title":20,"searchDepth":21,"depth":21,"links":2342},[2343,2344,2345,2346,2347],{"id":2228,"depth":21,"text":2229},{"id":2255,"depth":21,"text":2256},{"id":2265,"depth":21,"text":2266},{"id":2292,"depth":21,"text":2293},{"id":2336,"depth":21,"text":2337},"2026-07-05","Cloudflare now answers 1.1.1.1 DNS queries from a Slovenian PoP. What it means for latency, privacy (DoH, DoT, ECH) and how to switch in a minute.",{"src":2214,"alt":2351},"Cloudflare 1.1.1.1 DNS now served from a Slovenian point of presence",{},"\u002Fblog\u002Fcloudflare-slovenian-location",{"title":2355,"description":2349},"Cloudflare 1.1.1.1 DNS Now Served from Slovenia","blog\u002F2026-07-05-cloudflare-slovenian-location","Cloudflare's 1.1.1.1 is now served from Slovenia — fast, private, optionally filtered DNS with zero latency trade-off.",[2359,2360,2361,2362],"cloudflare","dns","privacy","slovenia","iR1ug0wZXzp8D0LkHV98bRQxx-1XM_F5yvLiczePDY4",1790016360857]