[{"data":1,"prerenderedAt":2042},["ShallowReactive",2],{"all-blog-posts":3},[4,39,220,809,1533,1717,1886],{"id":5,"title":6,"body":7,"date":24,"description":20,"extension":25,"meta":26,"navigation":27,"path":28,"seo":29,"stem":30,"summary":31,"tags":32,"__hash__":38},"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","md",{},true,"\u002Fblog\u002Fmy-first-blog-post-here",{"title":6,"description":20},"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.",[33,34,35,36,37],"me","coding","magento","development","topics","TN_9MA02Hle3NvqYkBuUsgdnR7TxgkAv9sAdRvutvCY",{"id":40,"title":41,"body":42,"date":210,"description":49,"extension":25,"meta":211,"navigation":27,"path":212,"seo":213,"stem":214,"summary":215,"tags":216,"__hash__":219},"blog\u002Fblog\u002F2020-12-05-magento-2-block-condition-for-template.md","Magento 2 - block visibility",{"type":8,"value":43,"toc":208},[44,50,58,61,64,89,103,118,123,201,204],[16,45,46],{},[47,48,49],"strong",{},"How to accomplish this business requirement and have the simple and clean code at the same?",[16,51,52,53,57],{},"Let say we have a defined block in ",[54,55,56],"code",{},"app\\code\\Vendor\\Module\\etc\\di.xml"," where we also set a custom template for this block.",[16,59,60],{},"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,62,63],{},"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,65,66,67,72],{},"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",[68,69],"img",{"alt":70,"src":71},"Programing is about thinking, not writing","\u002Fimages\u002Fmagento-2-block-condition.webp",[73,74,75],"small",{},[76,77,78,79,84,85],"span",{},"Photo by ",[80,81,83],"a",{"href":82},"https:\u002F\u002Funsplash.com\u002F@perloov?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText","Per Lööv"," on ",[80,86,88],{"href":87},"https:\u002F\u002Funsplash.com\u002Fs\u002Fphotos\u002Fweb-developer?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText","Unsplash",[16,90,91,92,98,99,102],{},"The solution for this business requirement is to use Magento 2 ",[80,93,97],{"href":94,"rel":95},"https:\u002F\u002Fdevdocs.magento.com\u002Fguides\u002Fv2.4\u002Ffrontend-dev-guide\u002Flayouts\u002Fxml-manage.html#ref_config_block",[96],"nofollow","ifconfig argument in block definition"," to set the condition that either show or not the block based on ",[54,100,101],{}," Magento\u002FConfig\u002FModel\u002FConfig\u002FSource\u002FYesno"," option that we have in system config for a certain field.",[16,104,105,106,109,110,113,114,117],{},"So we need to set it like below where the ",[54,107,108],{},"my\u002Fyesno\u002Ffield"," is the field XPATH - you can find it in database table ",[54,111,112],{},"core_config_data"," or in case of custom module it must be defined in ",[54,115,116],{},"Vendor\\Module\\etc\\adminhtml\\system.xml",".",[16,119,120],{},[73,121,122],{},"Note: field XPATH is from three parts; first one is section id, second one is group id, and the third one is field id.",[124,125,129],"pre",{"className":126,"code":127,"language":128,"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",[54,130,131,185,191],{"__ignoreMap":20},[76,132,135,139,143,147,150,153,157,159,162,164,166,169,171,174,176,178,180,182],{"class":133,"line":134},"line",1,[76,136,138],{"class":137},"sAklC","\u003C",[76,140,142],{"class":141},"s-wAU","block",[76,144,146],{"class":145},"sJ14y"," class",[76,148,149],{"class":137},"=",[76,151,152],{"class":137},"\"",[76,154,156],{"class":155},"sfyAc","Namespace\\Module\\Block\\Type",[76,158,152],{"class":137},[76,160,161],{"class":145}," name",[76,163,149],{"class":137},[76,165,152],{"class":137},[76,167,168],{"class":155},"block.example",[76,170,152],{"class":137},[76,172,173],{"class":145}," ifconfig",[76,175,149],{"class":137},[76,177,152],{"class":137},[76,179,108],{"class":155},[76,181,152],{"class":137},[76,183,184],{"class":137},">\n",[76,186,187],{"class":133,"line":21},[76,188,190],{"class":189},"s0W1g","...\n",[76,192,194,197,199],{"class":133,"line":193},3,[76,195,196],{"class":137},"\u003C\u002F",[76,198,142],{"class":141},[76,200,184],{"class":137},[16,202,203],{},"I hope you find it useful and that it will help you make your code simple and more clean.",[205,206,207],"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":209},[],"2020-12-05",{},"\u002Fblog\u002Fmagento-2-block-visibility",{"title":41,"description":49},"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.",[217,142,218,36],"magento 2","visibility","zgrjKKPXElvNOM7CpZ-dcJEEVl2aZDo9JX4gViKHGAA",{"id":221,"title":222,"body":223,"date":799,"description":229,"extension":25,"meta":800,"navigation":27,"path":801,"seo":802,"stem":803,"summary":804,"tags":805,"__hash__":808},"blog\u002Fblog\u002F2020-12-17-magento-2-update-single-model-value.md","Magento 2 - update single model value",{"type":8,"value":224,"toc":797},[225,230,242,245,248,251,266,788,791,794],[16,226,227],{},[47,228,229],{},"Magento 2: update single model value (field) without loading the whole model instance",[16,231,232,235],{},[68,233],{"alt":70,"src":234},"\u002Fimages\u002Fdeveloper.webp",[73,236,237],{},[80,238,241],{"href":239,"rel":240},"https:\u002F\u002Fwww.pexels.com\u002Fphoto\u002Fperson-using-a-macbook-air-4974913\u002F?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels",[96],"Photo by olia danilevich from Pexels",[16,243,244],{},"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,246,247],{},"So what shall we do in such case?",[16,249,250],{},"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,252,253,254,257,258,261,262,265],{},"So the easiest way and most-common - also recommended, is to inject the instance of class ",[54,255,256],{},"Magento\\Framework\\App\\ResourceConnection"," into ",[54,259,260],{},"construct"," - don't use ",[54,263,264],{},"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.",[124,267,271],{"className":268,"code":269,"language":270,"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",[54,272,273,301,320,325,332,349,355,369,374,379,392,397,409,414,420,433,445,450,466,483,494,500,517,533,539,544,549,559,569,574,608,614,624,659,680,686,721,727,752,777,783],{"__ignoreMap":20},[76,274,275,279,282,285,288,290,293,295,298],{"class":133,"line":134},[76,276,278],{"class":277},"sx098","use",[76,280,281],{"class":189}," Magento",[76,283,284],{"class":137},"\\",[76,286,287],{"class":189},"Framework",[76,289,284],{"class":137},[76,291,292],{"class":189},"App",[76,294,284],{"class":137},[76,296,297],{"class":189},"ResourceConnection",[76,299,300],{"class":137},";\n",[76,302,303,305,308,310,313,315,318],{"class":133,"line":21},[76,304,278],{"class":277},[76,306,307],{"class":189}," Psr",[76,309,284],{"class":137},[76,311,312],{"class":189},"Log",[76,314,284],{"class":137},[76,316,317],{"class":189},"LoggerInterface",[76,319,300],{"class":137},[76,321,322],{"class":133,"line":193},[76,323,324],{"emptyLinePlaceholder":27},"\n",[76,326,328],{"class":133,"line":327},4,[76,329,331],{"class":330},"saEQR","\u002F** \n",[76,333,335,338,342,346],{"class":133,"line":334},5,[76,336,337],{"class":330}," * ",[76,339,341],{"class":340},"sfFnM","@var",[76,343,345],{"class":344},"stCc5"," ResourceConnection",[76,347,348],{"class":330}," $connection \n",[76,350,352],{"class":133,"line":351},6,[76,353,354],{"class":330}," *\u002F\n",[76,356,358,361,364,367],{"class":133,"line":357},7,[76,359,360],{"class":145},"protected",[76,362,363],{"class":137}," $",[76,365,366],{"class":189},"connection",[76,368,300],{"class":137},[76,370,372],{"class":133,"line":371},8,[76,373,324],{"emptyLinePlaceholder":27},[76,375,377],{"class":133,"line":376},9,[76,378,331],{"class":330},[76,380,382,384,386,389],{"class":133,"line":381},10,[76,383,337],{"class":330},[76,385,341],{"class":340},[76,387,388],{"class":344}," LoggerInterface",[76,390,391],{"class":330}," $logger \n",[76,393,395],{"class":133,"line":394},11,[76,396,354],{"class":330},[76,398,400,402,404,407],{"class":133,"line":399},12,[76,401,360],{"class":145},[76,403,363],{"class":137},[76,405,406],{"class":189},"logger",[76,408,300],{"class":137},[76,410,412],{"class":133,"line":411},13,[76,413,324],{"emptyLinePlaceholder":27},[76,415,417],{"class":133,"line":416},14,[76,418,419],{"class":330},"\u002F**\n",[76,421,423,425,428,430],{"class":133,"line":422},15,[76,424,337],{"class":330},[76,426,427],{"class":340},"@param",[76,429,345],{"class":344},[76,431,432],{"class":330}," $connection\n",[76,434,436,438,440,442],{"class":133,"line":435},16,[76,437,337],{"class":330},[76,439,427],{"class":340},[76,441,388],{"class":344},[76,443,444],{"class":330}," $logger\n",[76,446,448],{"class":133,"line":447},17,[76,449,354],{"class":330},[76,451,453,456,459,463],{"class":133,"line":452},18,[76,454,455],{"class":145},"public",[76,457,458],{"class":145}," function",[76,460,462],{"class":461},"sdLwU"," __construct",[76,464,465],{"class":137},"(\n",[76,467,469,473,475,477,480],{"class":133,"line":468},19,[76,470,472],{"class":471},"s5Dmg","    ResourceConnection",[76,474,363],{"class":137},[76,476,366],{"class":189},[76,478,479],{"class":137},",",[76,481,482],{"class":189}," \n",[76,484,486,489,491],{"class":133,"line":485},20,[76,487,488],{"class":471},"    LoggerInterface",[76,490,363],{"class":137},[76,492,493],{"class":189},"logger\n",[76,495,497],{"class":133,"line":496},21,[76,498,499],{"class":137},"){\n",[76,501,503,506,509,511,513,515],{"class":133,"line":502},22,[76,504,505],{"class":137},"    $this->",[76,507,508],{"class":189},"connection ",[76,510,149],{"class":137},[76,512,363],{"class":137},[76,514,366],{"class":189},[76,516,300],{"class":137},[76,518,520,522,525,527,529,531],{"class":133,"line":519},23,[76,521,505],{"class":137},[76,523,524],{"class":189},"logger ",[76,526,149],{"class":137},[76,528,363],{"class":137},[76,530,406],{"class":189},[76,532,300],{"class":137},[76,534,536],{"class":133,"line":535},24,[76,537,538],{"class":137},"}\n",[76,540,542],{"class":133,"line":541},25,[76,543,324],{"emptyLinePlaceholder":27},[76,545,547],{"class":133,"line":546},26,[76,548,419],{"class":330},[76,550,552,554,556],{"class":133,"line":551},27,[76,553,337],{"class":330},[76,555,427],{"class":340},[76,557,558],{"class":330}," $itemId\n",[76,560,562,564,566],{"class":133,"line":561},28,[76,563,337],{"class":330},[76,565,427],{"class":340},[76,567,568],{"class":330}," $status\n",[76,570,572],{"class":133,"line":571},29,[76,573,354],{"class":330},[76,575,577,579,581,584,587,590,592,595,597,599,602,605],{"class":133,"line":576},30,[76,578,360],{"class":145},[76,580,458],{"class":145},[76,582,583],{"class":461}," updateItemStatus",[76,585,586],{"class":137},"(",[76,588,589],{"class":277},"int",[76,591,363],{"class":137},[76,593,594],{"class":189},"itemId",[76,596,479],{"class":137},[76,598,363],{"class":137},[76,600,601],{"class":189},"status",[76,603,604],{"class":137},"):",[76,606,607],{"class":277}," void\n",[76,609,611],{"class":133,"line":610},31,[76,612,613],{"class":137},"{\n",[76,615,617,621],{"class":133,"line":616},32,[76,618,620],{"class":619},"s6cf3","    try",[76,622,623],{"class":137}," {\n",[76,625,627,630,632,635,638,641,644,646,649,652,654,656],{"class":133,"line":626},33,[76,628,629],{"class":137},"        $this->",[76,631,366],{"class":189},[76,633,634],{"class":137},"->",[76,636,637],{"class":461},"getConnection",[76,639,640],{"class":137},"()->",[76,642,643],{"class":461},"update",[76,645,586],{"class":137},[76,647,648],{"class":137},"'",[76,650,651],{"class":155},"sales_order_item",[76,653,648],{"class":137},[76,655,479],{"class":137},[76,657,658],{"class":137}," [\n",[76,660,662,665,668,670,673,675,677],{"class":133,"line":661},34,[76,663,664],{"class":137},"            '",[76,666,667],{"class":155},"my_custom_product_status",[76,669,648],{"class":137},[76,671,672],{"class":137}," =>",[76,674,363],{"class":137},[76,676,601],{"class":189},[76,678,679],{"class":137},",\n",[76,681,683],{"class":133,"line":682},35,[76,684,685],{"class":137},"        ],\n",[76,687,689,692,694,696,698,700,703,705,707,710,712,714,716,718],{"class":133,"line":688},36,[76,690,691],{"class":137},"            $this->",[76,693,366],{"class":189},[76,695,634],{"class":137},[76,697,637],{"class":461},[76,699,640],{"class":137},[76,701,702],{"class":461},"quoteInto",[76,704,586],{"class":137},[76,706,648],{"class":137},[76,708,709],{"class":155},"item_id = ?",[76,711,648],{"class":137},[76,713,479],{"class":137},[76,715,363],{"class":137},[76,717,594],{"class":189},[76,719,720],{"class":137},")\n",[76,722,724],{"class":133,"line":723},37,[76,725,726],{"class":137},"        );\n",[76,728,730,733,736,739,742,744,747,750],{"class":133,"line":729},38,[76,731,732],{"class":137},"    }",[76,734,735],{"class":619}," catch",[76,737,738],{"class":137}," (",[76,740,741],{"class":471},"Exception",[76,743,363],{"class":137},[76,745,746],{"class":189},"exception",[76,748,749],{"class":137},")",[76,751,623],{"class":137},[76,753,755,757,759,761,764,767,769,771,774],{"class":133,"line":754},39,[76,756,629],{"class":137},[76,758,406],{"class":189},[76,760,634],{"class":137},[76,762,763],{"class":461},"error",[76,765,766],{"class":137},"($",[76,768,746],{"class":189},[76,770,634],{"class":137},[76,772,773],{"class":461},"getMessage",[76,775,776],{"class":137},"());\n",[76,778,780],{"class":133,"line":779},40,[76,781,782],{"class":137},"    }\n",[76,784,786],{"class":133,"line":785},41,[76,787,538],{"class":137},[16,789,790],{},"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,792,793],{},"I hope this will help you and you'll find it useful!",[205,795,796],{},"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":798},[],"2020-12-17",{},"\u002Fblog\u002Fmagento-2-update-single-model-value",{"title":222,"description":229},"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.",[217,806,807,36],"model","sql update","t4iAhSnLW8-_erVEcpwU7vAKuKtILPmygWHRDt_9NjE",{"id":810,"title":811,"body":812,"date":1524,"description":818,"extension":25,"meta":1525,"navigation":27,"path":1526,"seo":1527,"stem":1528,"summary":1529,"tags":1530,"__hash__":1532},"blog\u002Fblog\u002F2021-10-19-magento-2-view-models-and-when-to-use-them.md","Magento 2 - View models",{"type":8,"value":813,"toc":1519},[814,819,830,835,838,842,845,957,960,978,1183,1187,1194,1197,1330,1333,1344,1347,1502,1505,1508,1516],[16,815,816],{},[47,817,818],{},"Magento 2: When and how to use view models",[16,820,821,824],{},[68,822],{"alt":70,"src":823},"\u002Fimages\u002Fmagento-2-view-models.webp",[73,825,826],{},[80,827,241],{"href":828,"rel":829},"https:\u002F\u002Fwww.pexels.com\u002Fphoto\u002Fperson-coding-on-a-macbook-pro-4974912\u002F",[96],[831,832,834],"h3",{"id":833},"what-is-a-view-model","What is a view model",[16,836,837],{},"A view model is an abstraction of the view exposing public properties and commands. It allows you to offload features and business logic from block classes into separate classes that are easier to maintain, test, and reuse.",[831,839,841],{"id":840},"when-to-use-view-models","When to use view models",[16,843,844],{},"Use this approach anytime you need to inject functionality into template files and your code does not need to be backwards compatible with Magento.",[124,846,848],{"className":126,"code":847,"language":128,"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",[54,849,850,881,891,940,949],{"__ignoreMap":20},[76,851,852,854,856,858,860,862,865,867,870,872,874,877,879],{"class":133,"line":134},[76,853,138],{"class":137},[76,855,142],{"class":141},[76,857,161],{"class":145},[76,859,149],{"class":137},[76,861,152],{"class":137},[76,863,864],{"class":155},"demoapp.content.viewmodel",[76,866,152],{"class":137},[76,868,869],{"class":145}," template",[76,871,149],{"class":137},[76,873,152],{"class":137},[76,875,876],{"class":155},"DemoApp_Catalog::demo.phtml",[76,878,152],{"class":137},[76,880,184],{"class":137},[76,882,883,886,889],{"class":133,"line":21},[76,884,885],{"class":137},"    \u003C",[76,887,888],{"class":141},"arguments",[76,890,184],{"class":137},[76,892,893,896,899,901,903,905,908,910,913,916,919,921,923,926,928,931,934,936,938],{"class":133,"line":193},[76,894,895],{"class":137},"        \u003C",[76,897,898],{"class":141},"argument",[76,900,161],{"class":145},[76,902,149],{"class":137},[76,904,152],{"class":137},[76,906,907],{"class":155},"view_model",[76,909,152],{"class":137},[76,911,912],{"class":145}," xsi",[76,914,915],{"class":137},":",[76,917,918],{"class":145},"type",[76,920,149],{"class":137},[76,922,152],{"class":137},[76,924,925],{"class":155},"object",[76,927,152],{"class":137},[76,929,930],{"class":137},">",[76,932,933],{"class":189},"DemoApp\\Sales\\ViewModel\\ExampleViewModel",[76,935,196],{"class":137},[76,937,898],{"class":141},[76,939,184],{"class":137},[76,941,942,945,947],{"class":133,"line":327},[76,943,944],{"class":137},"    \u003C\u002F",[76,946,888],{"class":141},[76,948,184],{"class":137},[76,950,951,953,955],{"class":133,"line":334},[76,952,196],{"class":137},[76,954,142],{"class":141},[76,956,184],{"class":137},[16,958,959],{},"You can also use this view model on existing blocks used by Magento 2 or by 3rd-party vendors in your modules. All you have to do is to pass view model class as an argument in page layout xml configuration file.",[16,961,962,963,969,970,973,974,977],{},"Let's say for example that we want to add our view model to ",[964,965,966],"em",{},[47,967,968],{},"invoice_totals"," block by referencing block in ",[54,971,972],{},"sales_email_order_invoice_items.xml"," page layout. Just use ",[54,975,976],{},"\u003CreferenceBlock>"," with name of block and pass argument with namespace of view model class and object as type - be aware that you pass namespace without first slash.",[124,979,981],{"className":126,"code":980,"language":128,"meta":20,"style":20},"\u003C?xml version=\"1.0\"?>\n\u003Cpage xmlns:xsi=\"http:\u002F\u002Fwww.w3.org\u002F2001\u002FXMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:View\u002FLayout\u002Fetc\u002Fpage_configuration.xsd\" label=\"Email Invoice Items List\" 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",[54,982,983,1005,1071,1080,1099,1108,1149,1158,1167,1175],{"__ignoreMap":20},[76,984,985,988,990,993,995,997,1000,1002],{"class":133,"line":134},[76,986,987],{"class":137},"\u003C?",[76,989,128],{"class":141},[76,991,992],{"class":145}," version",[76,994,149],{"class":137},[76,996,152],{"class":137},[76,998,999],{"class":155},"1.0",[76,1001,152],{"class":137},[76,1003,1004],{"class":137},"?>\n",[76,1006,1007,1009,1012,1015,1017,1020,1022,1024,1027,1029,1031,1033,1036,1038,1040,1043,1045,1048,1050,1052,1055,1057,1060,1062,1064,1067,1069],{"class":133,"line":21},[76,1008,138],{"class":137},[76,1010,1011],{"class":141},"page",[76,1013,1014],{"class":145}," xmlns",[76,1016,915],{"class":137},[76,1018,1019],{"class":145},"xsi",[76,1021,149],{"class":137},[76,1023,152],{"class":137},[76,1025,1026],{"class":155},"http:\u002F\u002Fwww.w3.org\u002F2001\u002FXMLSchema-instance",[76,1028,152],{"class":137},[76,1030,912],{"class":145},[76,1032,915],{"class":137},[76,1034,1035],{"class":145},"noNamespaceSchemaLocation",[76,1037,149],{"class":137},[76,1039,152],{"class":137},[76,1041,1042],{"class":155},"urn:magento:framework:View\u002FLayout\u002Fetc\u002Fpage_configuration.xsd",[76,1044,152],{"class":137},[76,1046,1047],{"class":145}," label",[76,1049,149],{"class":137},[76,1051,152],{"class":137},[76,1053,1054],{"class":155},"Email Invoice Items List",[76,1056,152],{"class":137},[76,1058,1059],{"class":145}," design_abstraction",[76,1061,149],{"class":137},[76,1063,152],{"class":137},[76,1065,1066],{"class":155},"custom",[76,1068,152],{"class":137},[76,1070,184],{"class":137},[76,1072,1073,1075,1078],{"class":133,"line":193},[76,1074,885],{"class":137},[76,1076,1077],{"class":141},"body",[76,1079,184],{"class":137},[76,1081,1082,1084,1087,1089,1091,1093,1095,1097],{"class":133,"line":327},[76,1083,895],{"class":137},[76,1085,1086],{"class":141},"referenceBlock",[76,1088,161],{"class":145},[76,1090,149],{"class":137},[76,1092,152],{"class":137},[76,1094,968],{"class":155},[76,1096,152],{"class":137},[76,1098,184],{"class":137},[76,1100,1101,1104,1106],{"class":133,"line":334},[76,1102,1103],{"class":137},"            \u003C",[76,1105,888],{"class":141},[76,1107,184],{"class":137},[76,1109,1110,1113,1115,1117,1119,1121,1123,1125,1127,1129,1131,1133,1135,1137,1139,1141,1143,1145,1147],{"class":133,"line":351},[76,1111,1112],{"class":137},"                \u003C",[76,1114,898],{"class":141},[76,1116,161],{"class":145},[76,1118,149],{"class":137},[76,1120,152],{"class":137},[76,1122,907],{"class":155},[76,1124,152],{"class":137},[76,1126,912],{"class":145},[76,1128,915],{"class":137},[76,1130,918],{"class":145},[76,1132,149],{"class":137},[76,1134,152],{"class":137},[76,1136,925],{"class":155},[76,1138,152],{"class":137},[76,1140,930],{"class":137},[76,1142,933],{"class":189},[76,1144,196],{"class":137},[76,1146,898],{"class":141},[76,1148,184],{"class":137},[76,1150,1151,1154,1156],{"class":133,"line":357},[76,1152,1153],{"class":137},"            \u003C\u002F",[76,1155,888],{"class":141},[76,1157,184],{"class":137},[76,1159,1160,1163,1165],{"class":133,"line":371},[76,1161,1162],{"class":137},"        \u003C\u002F",[76,1164,1086],{"class":141},[76,1166,184],{"class":137},[76,1168,1169,1171,1173],{"class":133,"line":376},[76,1170,944],{"class":137},[76,1172,1077],{"class":141},[76,1174,184],{"class":137},[76,1176,1177,1179,1181],{"class":133,"line":381},[76,1178,196],{"class":137},[76,1180,1011],{"class":141},[76,1182,184],{"class":137},[831,1184,1186],{"id":1185},"the-view-model-class","The view model class",[16,1188,1189,1190,1193],{},"In your view model class you can have as many methods as you want. The one and only difference is that if you want to use your class as view model you must always implement ",[54,1191,1192],{},"\\Magento\\Framework\\View\\Element\\Block\\ArgumentInterface"," interface. Everything else is in your domain - e.g. how much dependencies you will need\u002Fadd in constructor if any and what methods will you implement.",[16,1195,1196],{},"The most simple example of view model class would be something like this:",[124,1198,1200],{"className":268,"code":1199,"language":270,"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",[54,1201,1202,1222,1226,1262,1266,1280,1284,1297,1302,1322,1326],{"__ignoreMap":20},[76,1203,1204,1207,1210,1212,1215,1217,1220],{"class":133,"line":134},[76,1205,1206],{"class":277},"namespace",[76,1208,1209],{"class":471}," DemoApp",[76,1211,284],{"class":137},[76,1213,1214],{"class":471},"Sales",[76,1216,284],{"class":137},[76,1218,1219],{"class":471},"ViewModel",[76,1221,300],{"class":137},[76,1223,1224],{"class":133,"line":21},[76,1225,324],{"emptyLinePlaceholder":27},[76,1227,1228,1230,1233,1236,1238,1240,1242,1245,1247,1250,1252,1255,1257,1260],{"class":133,"line":193},[76,1229,278],{"class":277},[76,1231,1232],{"class":137}," \\",[76,1234,1235],{"class":189},"Magento",[76,1237,284],{"class":137},[76,1239,287],{"class":189},[76,1241,284],{"class":137},[76,1243,1244],{"class":189},"View",[76,1246,284],{"class":137},[76,1248,1249],{"class":189},"Element",[76,1251,284],{"class":137},[76,1253,1254],{"class":189},"Block",[76,1256,284],{"class":137},[76,1258,1259],{"class":189},"ArgumentInterface",[76,1261,300],{"class":137},[76,1263,1264],{"class":133,"line":327},[76,1265,324],{"emptyLinePlaceholder":27},[76,1267,1268,1271,1274,1277],{"class":133,"line":334},[76,1269,1270],{"class":145},"class",[76,1272,1273],{"class":471}," ExampleViewModel",[76,1275,1276],{"class":145}," implements",[76,1278,1279],{"class":471}," ArgumentInterface\n",[76,1281,1282],{"class":133,"line":351},[76,1283,613],{"class":137},[76,1285,1286,1289,1291,1294],{"class":133,"line":357},[76,1287,1288],{"class":145},"    public",[76,1290,458],{"class":145},[76,1292,1293],{"class":461}," getSomeDemoText",[76,1295,1296],{"class":137},"()\n",[76,1298,1299],{"class":133,"line":371},[76,1300,1301],{"class":137},"    {\n",[76,1303,1304,1307,1310,1312,1314,1317,1319],{"class":133,"line":376},[76,1305,1306],{"class":619},"      return",[76,1308,1309],{"class":461}," __",[76,1311,586],{"class":137},[76,1313,648],{"class":137},[76,1315,1316],{"class":155},"My demo text",[76,1318,648],{"class":137},[76,1320,1321],{"class":137},");\n",[76,1323,1324],{"class":133,"line":381},[76,1325,782],{"class":137},[76,1327,1328],{"class":133,"line":394},[76,1329,538],{"class":137},[16,1331,1332],{},"The last step before moving to the template part and finally using your view model function is to compile your DI configuration (it generates DI configuration and all missing classes that can be auto-generated - you'll find them in generated\u002Fcode folder as interceptor classes)",[16,1334,1335,1336,1339,1340,1343],{},"To compile DI just wrote in your console ",[54,1337,1338],{},"bin\u002Fmagento setup:di:compile"," or if you want short version ",[54,1341,1342],{},"bin\u002Fmagento s:d:c"," command.",[16,1345,1346],{},"When this it's done you can use getSomeDemoText() function in block's template by calling it by viewModel argument name or using getData() function on block - both will work.",[124,1348,1350],{"className":268,"code":1349,"language":270,"meta":20,"style":20},"\u003C?php\n\n\u002F** @var $viewModel \\DemoApp\\Sales\\ViewModel\\ExampleViewModel *\u002F\n$viewModel = $block->getViewModel();\n$viewModelText = $block->getData('view_model')\n\n?>\n\u003Cp>\u003C?= $block->escapeHtml($viewModel->getSomeDemoText()); ?>\u003C\u002Fp>\n\u003Cp>\u003C?= $block->escapeHtml($viewModelText->getSomeDemoText()); ?>\u003C\u002Fp>\n",[54,1351,1352,1359,1363,1373,1395,1423,1427,1431,1469],{"__ignoreMap":20},[76,1353,1354,1356],{"class":133,"line":134},[76,1355,987],{"class":137},[76,1357,1358],{"class":189},"php\n",[76,1360,1361],{"class":133,"line":21},[76,1362,324],{"emptyLinePlaceholder":27},[76,1364,1365,1368,1370],{"class":133,"line":193},[76,1366,1367],{"class":330},"\u002F** ",[76,1369,341],{"class":340},[76,1371,1372],{"class":330}," $viewModel \\DemoApp\\Sales\\ViewModel\\ExampleViewModel *\u002F\n",[76,1374,1375,1378,1381,1383,1385,1387,1389,1392],{"class":133,"line":327},[76,1376,1377],{"class":137},"$",[76,1379,1380],{"class":189},"viewModel ",[76,1382,149],{"class":137},[76,1384,363],{"class":137},[76,1386,142],{"class":189},[76,1388,634],{"class":137},[76,1390,1391],{"class":461},"getViewModel",[76,1393,1394],{"class":137},"();\n",[76,1396,1397,1399,1402,1404,1406,1408,1410,1413,1415,1417,1419,1421],{"class":133,"line":334},[76,1398,1377],{"class":137},[76,1400,1401],{"class":189},"viewModelText ",[76,1403,149],{"class":137},[76,1405,363],{"class":137},[76,1407,142],{"class":189},[76,1409,634],{"class":137},[76,1411,1412],{"class":461},"getData",[76,1414,586],{"class":137},[76,1416,648],{"class":137},[76,1418,907],{"class":155},[76,1420,648],{"class":137},[76,1422,720],{"class":137},[76,1424,1425],{"class":133,"line":351},[76,1426,324],{"emptyLinePlaceholder":27},[76,1428,1429],{"class":133,"line":357},[76,1430,1004],{"class":137},[76,1432,1433,1435,1437,1440,1442,1444,1446,1449,1451,1454,1456,1459,1462,1465,1467],{"class":133,"line":371},[76,1434,138],{"class":137},[76,1436,16],{"class":189},[76,1438,1439],{"class":137},">\u003C?=",[76,1441,363],{"class":137},[76,1443,142],{"class":189},[76,1445,634],{"class":137},[76,1447,1448],{"class":461},"escapeHtml",[76,1450,766],{"class":137},[76,1452,1453],{"class":189},"viewModel",[76,1455,634],{"class":137},[76,1457,1458],{"class":461},"getSomeDemoText",[76,1460,1461],{"class":137},"());",[76,1463,1464],{"class":137}," ?>\u003C\u002F",[76,1466,16],{"class":189},[76,1468,184],{"class":137},[76,1470,1471,1473,1475,1477,1479,1481,1483,1485,1487,1490,1492,1494,1496,1498,1500],{"class":133,"line":376},[76,1472,138],{"class":137},[76,1474,16],{"class":189},[76,1476,1439],{"class":137},[76,1478,363],{"class":137},[76,1480,142],{"class":189},[76,1482,634],{"class":137},[76,1484,1448],{"class":461},[76,1486,766],{"class":137},[76,1488,1489],{"class":189},"viewModelText",[76,1491,634],{"class":137},[76,1493,1458],{"class":461},[76,1495,1461],{"class":137},[76,1497,1464],{"class":137},[76,1499,16],{"class":189},[76,1501,184],{"class":137},[16,1503,1504],{},"View models are helpful when you need to customize some templates and add some text, adjust business logic and call function to check something and then display some text paragraph or anything else related to your business requirements and you don't want to override blocks if it's not necessary.",[16,1506,1507],{},"One good example of using view model is if you want to display custom text on invoice for specific customer groups or if customer paid with specific payment method. It's very handy and easy to use view model in that case.",[16,1509,1510,1511,117],{},"I hope this helped you understand how to add a view model in Magento 2 blocks using arguments and in which case use view models. If you want to get more information, check official docs at ",[80,1512,1515],{"href":1513,"rel":1514},"https:\u002F\u002Fdevdocs.magento.com\u002Fguides\u002Fv2.4\u002Fextension-dev-guide\u002Fview-models.html",[96],"magento devdocs",[205,1517,1518],{},"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":1520},[1521,1522,1523],{"id":833,"depth":193,"text":834},{"id":840,"depth":193,"text":841},{"id":1185,"depth":193,"text":1186},"2021-10-19",{},"\u002Fblog\u002Fmagento-2-view-models",{"title":811,"description":818},"blog\u002F2021-10-19-magento-2-view-models-and-when-to-use-them","In this article I'll go through view models in Magento 2. We'll cover the basics and talk about how and when to use them and how view models could help us.",[217,1531,36],"view-models","5_tCdKhaTFCjYcfGexH5MzE2nPMcAq_0Vdg5Ncn52Ho",{"id":1534,"title":1535,"body":1536,"date":1707,"description":1549,"extension":25,"meta":1708,"navigation":27,"path":1709,"seo":1710,"stem":1711,"summary":1712,"tags":1713,"__hash__":1716},"blog\u002Fblog\u002F2023-01-03-7-awesome-developer-tools-to-make-you-more-productive.md","7 Awesome Developer Tools to Make You More Productive on Mac",{"type":8,"value":1537,"toc":1698},[1538,1550,1554,1565,1568,1572,1582,1590,1594,1604,1608,1619,1647,1651,1662,1665,1669,1681,1684,1688],[16,1539,1540,1543],{},[68,1541],{"alt":70,"src":1542},"\u002Fimages\u002Fdeveloper-tools-to-make-you-more-productive.webp",[73,1544,1545],{},[80,1546,1549],{"href":1547,"rel":1548},"https:\u002F\u002Fwww.pexels.com\u002Fphoto\u002Ffaceless-person-using-touchpad-on-laptop-4126724\u002F",[96],"Photo by Ketut Subiyanto",[831,1551,1553],{"id":1552},"numi-app","Numi app",[16,1555,1556,1559,1564],{},[68,1557],{"alt":1553,"src":1558},"\u002Fimages\u002Fnumi.webp",[80,1560,1563],{"href":1561,"rel":1562},"https:\u002F\u002Fnumi.app\u002F",[96],"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,1566,1567],{},"The app is free but if you want unlimited notes and iCloud sync you need to pay $19,99 + VAT.",[831,1569,1571],{"id":1570},"grammarly","Grammarly",[16,1573,1574,1577,1581],{},[68,1575],{"alt":1571,"src":1576},"\u002Fimages\u002Fgrammarly.webp",[80,1578,1571],{"href":1579,"rel":1580},"https:\u002F\u002Fgrammarly.com\u002F",[96]," 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,1583,1584,1585,117],{},"The alternative for Grammarly is ",[80,1586,1589],{"href":1587,"rel":1588},"https:\u002F\u002Fhemingwayapp.com\u002F",[96],"Hemingway Editor",[831,1591,1593],{"id":1592},"spectacle","Spectacle",[16,1595,1596,1599,1603],{},[68,1597],{"alt":1593,"src":1598},"\u002Fimages\u002Fspectacle.webp",[80,1600,1593],{"href":1601,"rel":1602},"https:\u002F\u002Fwww.spectacleapp.com\u002F",[96]," helps you organize your windows without using a mouse. You can use keyboard shortcuts to move and resize windows in your preferred layout. It's a great tool for developers who spend a lot of time working with multiple windows. It's no longer being actively maintained but I'do still recommend it.",[831,1605,1607],{"id":1606},"alfred","Alfred",[16,1609,1610,1613,1618],{},[68,1611],{"alt":1607,"src":1612},"\u002Fimages\u002Falfred.webp",[80,1614,1617],{"href":1615,"rel":1616},"https:\u002F\u002Fwww.alfredapp.com\u002F",[96],"Alfred app"," is a great tool and alternative to the default Spotlight app on Mac. It supports a lot of features:",[1620,1621,1622,1626,1629,1632,1635,1638,1641,1644],"ul",{},[1623,1624,1625],"li",{},"search for files",[1623,1627,1628],{},"find apps & files",[1623,1630,1631],{},"calculate",[1623,1633,1634],{},"search the web",[1623,1636,1637],{},"system commands",[1623,1639,1640],{},"clipboard history",[1623,1642,1643],{},"1password integration",[1623,1645,1646],{},"etc.",[831,1648,1650],{"id":1649},"figio","Fig.io",[16,1652,1653,1656,1661],{},[68,1654],{"alt":1650,"src":1655},"\u002Fimages\u002Ffig.webp",[80,1657,1660],{"href":1658,"rel":1659},"https:\u002F\u002Ffig.io\u002F",[96],"Fig"," is a next generation terminal that makes you more productive. It supports autocomplete in terminal for git commands, docker commands, file paths, npm, yarn and more.",[16,1663,1664],{},"For me it's a must-have addon for my ohmyzsh terminal.",[831,1666,1668],{"id":1667},"cleanshot-x-paid-app","CleanShot X (Paid app)",[16,1670,1671,1675,1680],{},[68,1672],{"alt":1673,"src":1674},"CleanShot X","\u002Fimages\u002Fcleanshot.webp",[80,1676,1679],{"href":1677,"rel":1678},"https:\u002F\u002Fcleanshot.com\u002F",[96],"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,1682,1683],{},"It’s paid, but it was one of the best apps I’ve ever purchased.",[831,1685,1687],{"id":1686},"loom","Loom",[16,1689,1690,1693,1697],{},[68,1691],{"alt":1687,"src":1692},"\u002Fimages\u002Floom.webp",[80,1694,1687],{"href":1695,"rel":1696},"https:\u002F\u002Fwww.loom.com\u002F",[96]," 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":1699},[1700,1701,1702,1703,1704,1705,1706],{"id":1552,"depth":193,"text":1553},{"id":1570,"depth":193,"text":1571},{"id":1592,"depth":193,"text":1593},{"id":1606,"depth":193,"text":1607},{"id":1649,"depth":193,"text":1650},{"id":1667,"depth":193,"text":1668},{"id":1686,"depth":193,"text":1687},"2023-01-03",{},"\u002Fblog\u002F7-awesome-developer-tools-to-make-you-more-productive",{"title":1535,"description":1549},"blog\u002F2023-01-03-7-awesome-developer-tools-to-make-you-more-productive","Learn about 7 great developer tools that can help you improve productivity on Mac OS.",[1714,1715,36],"dev-tools","productivity","6DpmEY2Uoeg7hmpwpWE9NRSaDUn1W2bVMmCPQEnFg0E",{"id":1718,"title":1719,"body":1720,"date":1873,"description":1724,"extension":25,"meta":1874,"navigation":27,"path":1875,"seo":1876,"stem":1877,"summary":1878,"tags":1879,"__hash__":1885},"blog\u002Fblog\u002F2026-07-03-nativephp-flutter-react-native.md","Thinking about a mobile app for your store? Here's how I'd choose between NativePHP, Flutter and React Native",{"type":8,"value":1721,"toc":1866},[1722,1725,1739,1742,1746,1749,1756,1760,1763,1774,1777,1787,1791,1794,1797,1803,1807,1814,1821,1824,1831,1834,1840,1844,1847,1854,1857],[16,1723,1724],{},"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,1726,1727,1728,1731,1732,1735,1736,117],{},"I get this question a lot from clients, so let me break down the three options you'll hear about most in 2026: ",[47,1729,1730],{},"Flutter",", ",[47,1733,1734],{},"React Native",", and the newcomer that's especially interesting if your site already runs on PHP — ",[47,1737,1738],{},"NativePHP",[16,1740,1741],{},"No jargon, I promise. Just what each one is, what it costs you in practice, and when I'd recommend it.",[11,1743,1745],{"id":1744},"first-things-first-what-is-a-cross-platform-framework","First things first: what is a cross-platform framework?",[16,1747,1748],{},"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,1750,1751,1752,1755],{},"Cross-platform frameworks let developers write the app ",[47,1753,1754],{},"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,1757,1759],{"id":1758},"flutter-the-polished-one","Flutter — the polished one",[16,1761,1762],{},"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,1764,1765,1766,1769,1770,1773],{},"Its biggest strength is ",[47,1767,1768],{},"visual control",". Flutter draws every pixel itself, which means your app looks ",[964,1771,1772],{},"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,1775,1776],{},"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,1778,1779,1782,1783,1786],{},[47,1780,1781],{},"Choose Flutter when:"," the app ",[964,1784,1785],{},"is"," the brand experience and you have the budget for a dedicated mobile team.",[11,1788,1790],{"id":1789},"react-native-the-pragmatic-one","React Native — the pragmatic one",[16,1792,1793],{},"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,1795,1796],{},"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,1798,1799,1802],{},[47,1800,1801],{},"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,1804,1806],{"id":1805},"nativephp-the-interesting-newcomer","NativePHP — the interesting newcomer",[16,1808,1809,1810,1813],{},"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 ",[47,1811,1812],{},"PHP",". Until recently, PHP developers had no real path to mobile apps.",[16,1815,1816,1817,1820],{},"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 ",[47,1818,1819],{},"free and open source"," (MIT licensed), which removed the last real barrier to trying it.",[16,1822,1823],{},"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,1825,1826,1827,1830],{},"Why does this matter to you as a store owner? ",[47,1828,1829],{},"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,1832,1833],{},"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,1835,1836,1839],{},[47,1837,1838],{},"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,1841,1843],{"id":1842},"the-short-version","The short version",[16,1845,1846],{},"There's no universal winner here — anyone who tells you otherwise is selling something.",[16,1848,1849,1850,1853],{},"My rule of thumb: ",[47,1851,1852],{},"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,1855,1856],{},"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,1858,1859,1860,1865],{},"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, ",[80,1861,1864],{"href":1862,"rel":1863},"https:\u002F\u002Fdenisveg.dev",[96],"get in touch"," and I'll be happy to help.",{"title":20,"searchDepth":21,"depth":21,"links":1867},[1868,1869,1870,1871,1872],{"id":1744,"depth":21,"text":1745},{"id":1758,"depth":21,"text":1759},{"id":1789,"depth":21,"text":1790},{"id":1805,"depth":21,"text":1806},{"id":1842,"depth":21,"text":1843},"2026-07-03",{},"\u002Fblog\u002Fmobile-app-nativephp-flutter-react-native",{"title":1719,"description":1724},"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.",[1880,36,1881,1882,1883,1884],"mobile-development","laravel","nativephp","flutter","react-native","dhmt9raAgZhHfIO4Jo_2X6RWpfAHckUT_y9Bgg3aqeM",{"id":1887,"title":1888,"body":1889,"date":2030,"description":20,"extension":25,"meta":2031,"navigation":27,"path":2032,"seo":2033,"stem":2034,"summary":2035,"tags":2036,"__hash__":2041},"blog\u002Fblog\u002F2026-07-05-cloudflare-slovenian-location.md","Cloudflare's 1.1.1.1 Now Has a Slovenian PoP",{"type":8,"value":1890,"toc":2023},[1891,1896,1901,1904,1907,1911,1914,1917,1920,1934,1938,1941,1944,1948,1951,1962,1965,1972,1976,1997,2003,2013,2016,2020],[16,1892,1893],{},[68,1894],{"alt":1888,"src":1895},"\u002Fimages\u002Fcloudflare-1111-slovenia-hero.webp",[1897,1898,1900],"h1",{"id":1899},"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,1902,1903],{},"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,1905,1906],{},"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,1908,1910],{"id":1909},"a-quick-refresher-what-1111-actually-is","A quick refresher: what 1.1.1.1 actually is",[16,1912,1913],{},"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,1915,1916],{},"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,1918,1919],{},"Beyond the plain resolver, Cloudflare offers two filtered variants worth knowing about:",[1620,1921,1922,1928],{},[1623,1923,1924,1927],{},[47,1925,1926],{},"1.1.1.2"," — blocks known malicious domains (phishing, malware distribution, and similar threats) at the DNS level",[1623,1929,1930,1933],{},[47,1931,1932],{},"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,1935,1937],{"id":1936},"why-local-presence-changes-the-math","Why local presence changes the math",[16,1939,1940],{},"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,1942,1943],{},"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,1945,1947],{"id":1946},"privacy-what-switching-your-dns-does-and-doesnt-do","Privacy: what switching your DNS does — and doesn't — do",[16,1949,1950],{},"This is where a lot of people overestimate the benefit, so let's be precise.",[16,1952,1953,1954,1957,1958,1961],{},"If you use 1.1.1.1 over ",[47,1955,1956],{},"DNS over HTTPS (DoH)"," or ",[47,1959,1960],{},"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,1963,1964],{},"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,1966,1967,1968,1971],{},"The missing piece is ",[47,1969,1970],{},"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,1973,1975],{"id":1974},"how-to-switch-60-seconds","How to switch (60 seconds)",[16,1977,1978,1981,1982,1985,1986,1989,1990,1992,1993,1996],{},[47,1979,1980],{},"On your router"," (applies to every device on the network): set primary DNS to ",[54,1983,1984],{},"1.1.1.1"," and secondary to ",[54,1987,1988],{},"1.0.0.1"," — or ",[54,1991,1932],{}," \u002F ",[54,1994,1995],{},"1.0.0.3"," if you want the family filter.",[16,1998,1999,2002],{},[47,2000,2001],{},"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,2004,2005,2008,2009,2012],{},[47,2006,2007],{},"Verify it works",": visit ",[54,2010,2011],{},"1.1.1.1\u002Fhelp",", which tells you whether you're actually using Cloudflare and whether DoH is active.",[16,2014,2015],{},"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,2017,2019],{"id":2018},"the-takeaway","The takeaway",[16,2021,2022],{},"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":2024},[2025,2026,2027,2028,2029],{"id":1909,"depth":21,"text":1910},{"id":1936,"depth":21,"text":1937},{"id":1946,"depth":21,"text":1947},{"id":1974,"depth":21,"text":1975},{"id":2018,"depth":21,"text":2019},"2026-07-05",{},"\u002Fblog\u002Fcloudflare-slovenian-location",{"title":1888,"description":20},"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.",[2037,2038,2039,2040],"cloudflare","dns","privacy","slovenia","zJKbMw-M7d3XubYQjElC3D6MqOQm64DXM_bpdsvocDI",1783331279191]