PHP - BlogFlockAll things PHP2025-02-18T06:34:34.028ZBlogFlockPhpStorm : The IDE that empowers PHP developers | The JetBrains Blog, Exakat, Rob Allen, Blog entries :: mwop.netWriting out the page contents in Playwright - Rob Allenhttps://akrabat.com/?p=72622025-02-11T11:00:00.000Z<p>I recently had a problem with a failing <a href="https://playwright.dev">Playwright</a> test that only happened when running in Docker.</p>
<p>The test that was failing was:</p>
<pre>
let locator = page.locator('a[href="/login"].nav-link');
await locator.click();
await expect(page).toHaveTitle(/Log in/);
</pre>
<p>The test clicks the link to go to /login and then checks that the next page's title contains the text "Log in". Not an especially complicated test, so I was quite surprised when it failed claiming that the title was blank.</p>
<p>To work out what was happening, I wanted to view the HTML that the Playwright browser was seeing. To do this I added this after the <tt>click()</tt> call.:</p>
<pre>
await page.waitForLoadState();
console.log("Page content: ", await page.content());
</pre>
<p>The HTML for the page was then rendered into my terminal and showed that the HTML wasn't what I expected. It was then <em>relatively easy</em> to sort the problem and all was well again.</p>
The State of PHP 2024 - PhpStorm : The IDE that empowers PHP developers | The JetBrains Bloghttps://blog.jetbrains.com/?post_type=phpstorm&p=5422152025-02-05T13:56:24.000Z
<p>PHP continues to be a cornerstone of web development, powering millions of websites around the world. Its vibrant and dedicated community values its flexibility and ease of use. But what does the current state of PHP development look like? </p>
<p>To uncover deeper insights and trends shaping the ecosystem, we’ve turned to our in-house expert, Brent Roose, Developer Advocate for PHP, who will guide us through the results of the <a href="https://www.jetbrains.com/lp/devecosystem-2024/" target="_blank" rel="noopener">State of Developer Ecosystem Survey 2024</a>. Join us as we explore the findings and learn how PHP developers are navigating the ever-evolving landscape of modern web development.</p>
<figure class="wp-block-image size-full"><img decoding="async" fetchpriority="high" width="2560" height="1440" src="https://blog.jetbrains.com/wp-content/uploads/2025/02/ps-featured-state-of-php_blog_1280x720_en-1.png" alt="" class="wp-image-543511"/></figure>
<h2 class="wp-block-heading">Participants</h2>
<p>We analyzed the responses of a diverse pool of 2,660 PHP developers from around the world.</p>
<h3 class="wp-block-heading">Professional experience</h3>
<p>The professional coding experience of the respondents varied widely, with a notable lean toward mid-career developers. </p>
<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeOC0n244AvUaJXZcFpIuS1VY6IrZi90LwZ1p3kgQyw_QDXWqBeLoOyAKW57BRB-5uEKDJwi1PIPxGkrJPXx-RxgOs89S6XjM1rbFrU25_ju0uF09lLZqYaIWwj7HM9SM9ynlbE?key=fKO6Jtjzu2cAEWpekcM4ijuH" alt=""/></figure>
<h3 class="wp-block-heading">Geographic distribution</h3>
<p>The survey attracted a highly international audience, with participants hailing from several different world regions. The largest group of respondents came from Japan (11%), followed by Mainland China (9%) and the United States (8%). Other notable regions included Brazil (7%) and India (6%). The representation of European countries, such as Germany (5%), France (4%), the Netherlands (2%), and Italy (2%), highlight the survey’s global reach.</p>
<h3 class="wp-block-heading">Age range</h3>
<p>The age distribution of the respondents shows a predominance of early- to mid-career developers. Developers in their 30s represent a significant portion of the community, followed closely by those in their 20s, which reflects PHP’s largely young and dynamic developer base. Meanwhile, developers in their 40s and 50s are also well-represented, highlighting the breadth of experience across the community. Younger respondents under 21 and those aged 60 or older make up the smallest groups, indicating a primarily mid-range demographic.</p>
<h3 class="wp-block-heading"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdMWOZ1n_RHSu9KIyKVTb54GDx6A4N7Z6aGz9vUy7F0YFHvCkTXIrpaL5EN6aqOBlgcTjWhRc2MNpZr8pSWIdp2DSEtBOfPNyjnxujdpaz89_ReMSRAHo8A4g2h2jRnS3rYSMMkag?key=fKO6Jtjzu2cAEWpekcM4ijuH" width="1700" height="1218"></h3>
<h3 class="wp-block-heading">Gender</h3>
<p>The survey’s gender distribution shows a striking lack of diversity, with the overwhelming majority of respondents identifying as male. Female representation is minimal, making up only a small percentage of participants, while non-binary, genderqueer, and other gender identities are almost entirely absent. The data highlights the persistent gender imbalance within the developer community and suggests a pressing need for greater inclusivity and representation in the field.</p>
<div class="blockquote">
<blockquote><p>“While the survey highlights gender imbalance and the lack of diversity, equality, and inclusion within the developer community, there are efforts to change that through initiatives like <a href="https://larabelles.com/" target="_blank" rel="noopener">Larabelles</a>. Larabelles is a community for PHP and Laravel developers who are underrepresented due to their gender. We encourage inclusivity, provide resources, and create opportunities for developers to help build a more supportive environment for everyone in tech. While we have not provided a complete solution yet, our efforts are important steps toward addressing the imbalance highlighted by the survey data. I encourage you to join our community and our efforts to improve the tech industry.”</p></blockquote>
<div class="blockquote__author">
<img decoding="async" class="blockquote__author-img" src="https://blog.jetbrains.com/wp-content/uploads/2025/02/zuzana.jpg" alt="Zuzana Kunckova">
<div class="blockquote__author-info">
<strong class="blockquote__author-title">Zuzana Kunckova, <a href="https://twitter.com/zuzana_kunckova" target="_blank" rel="noopener">X</a></strong>
<span class="blockquote__author-subtitle">Larabelles founder and a PHP and Laravel developer</span>
</div>
</div>
</div>
<h2 class="wp-block-heading">PHP versions in active use</h2>
<p>While 86% of PHP developers are using version 8 in 2024, adoption remains lower compared to the 96% who had adopted version 7 in 2020, a similar length of time after its release.</p>
<figure class="wp-block-image size-full"><img decoding="async" width="1700" height="1318" src="https://blog.jetbrains.com/wp-content/uploads/2025/02/03-2x-2.png" alt="" class="wp-image-543651"/></figure>
<h2 class="wp-block-heading">PHP frameworks</h2>
<p><a href="https://laravel.com/" target="_blank" rel="noopener">Laravel</a> continues to reign as the most popular PHP framework among our user base. Get deeper insights into the framework and its ecosystem in the <a href="https://stateoflaravel.com/results" target="_blank" rel="noopener">State of Laravel 2024</a> survey and our <a href="https://blog.jetbrains.com/phpstorm/2024/09/laravel-trends-2024-the-latest-market-insights/">overview</a> of it.</p>
<div class="blockquote">
<blockquote><p>“As the creator of Laravel, it’s a profound privilege to continue developing a framework that empowers developers and companies around the world. Laravel’s mission has always been to help teams ship amazing, well-tested, and beautifully architected applications quickly and with confidence. It’s an honor to see the incredible solutions our community builds every day, and I’m grateful to play a role in supporting their success.”</p></blockquote>
<div class="blockquote__author">
<img decoding="async" class="blockquote__author-img" src="https://blog.jetbrains.com/wp-content/uploads/2025/02/taylor-otwell.png" alt="Taylor Otwell">
<div class="blockquote__author-info">
<strong class="blockquote__author-title">Taylor Otwell, <a href="https://x.com/TaylorOtwell" target="_blank">X</a></strong>
<span class="blockquote__author-subtitle">Creator of Laravel </span>
</div>
</div>
</div>
<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeJz6QqdFuSXvd-L0xusLzAdfQnHSt1drrrXycVbMZlJpsxxoeCLTBf6mjV3VsvsvU0loql_G16xLrXfm0Rg0SDnjiURv5BMSWp5Wgmx9CzZluCgATkfdnR0Mz9-er3wwqE9gH1xQ?key=fKO6Jtjzu2cAEWpekcM4ijuH" alt=""/></figure>
<div class="blockquote">
<blockquote><p>“13 years ago, someone on the PHP subreddit <a href="https://www.reddit.com/r/PHP/comments/u72v7/comment/c4swdt0/" target="_blank" rel="noopener">wrote</a>: ‘Laravel is my favorite framework right now. But deep down I wish I was a symfony2 guru and drove a nicer car.’ It’s intriguing to see how the PHP community has shifted in the past decade: Laravel used to be the underdog, but now it’s the most popular framework by far. What I think adds to Laravel’s success is its focus on code usability over ‘being correct’: 95% of the time, it just works. Combine that with a <a href="https://blog.jetbrains.com/phpstorm/2024/07/the-best-laravel-tutorials-and-resources-for-developers-2/#documentation-and-community-resources">very strong community</a>, both online and offline, and you get a recipe for success. Laravel’s journey is truly inspiring, and I think every programmer should be aware of it.”</p></blockquote>
<div class="blockquote__author">
<img decoding="async" class="blockquote__author-img" src="https://blog.jetbrains.com/wp-content/uploads/2024/09/brent-roose.jpg" alt="Brent Roose, JetBrains Developer Advocate">
<div class="blockquote__author-info">
<strong class="blockquote__author-title">Brent Roose, <a href="https://www.youtube.com/@phpannotated" target="_blank" rel="noopener">PHP Annotated</a> YouTube channel</strong>
<span class="blockquote__author-subtitle">Developer Advocate for PhpStorm</span>
</div>
</div>
</div>
<h2 class="wp-block-heading">PHP development environments</h2>
<h3 class="wp-block-heading">Most used IDE or editor</h3>
<p>While PhpStorm is still used by more than half of the respondents, VS Code is steadily gaining popularity among developers, especially beginners.</p>
<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXd_LncLHCzVThZkoT5gs5hEjT7f2JDuiX9-GwsqfRHTDv8ipT0HX4f_sEG9rzZ_RsZbvecgp02Xtizf-rp-VLceWA6vnEIZd10UoLjCNmhZ4XC_quOf0S5AA4beuIHJOIe9lnHB?key=fKO6Jtjzu2cAEWpekcM4ijuH" alt=""/></figure>
<div class="blockquote">
<blockquote><p>“We see that most people who use PhpStorm keep using it indefinitely. At the same time, there’s a new generation of developers growing up who (understandably) reach for tools like VS Code to start their coding journey: It’s free and there’s a lower learning curve compared to a full-blown IDE like PhpStorm. <br />
However, I’ve been telling people the same thing for years (long before I worked as an advocate at JetBrains): It’s worth investing time in learning the proper tools. Just like a carpenter has to spend time and effort learning to operate heavy machinery, it’s worth learning how to use an IDE to its full potential as a programmer. It’s a short-term investment that will make you so much more productive in the long run.<br />
Recently I’ve been working on a ‘<a href="https://www.jetbrains.com/phpstorm/getting-started/" target="_blank" rel="noopener">Getting Started with PhpStorm</a>’ series that addresses this group. The goal of this series is to get you started with PhpStorm, so that you feel productive with it within minutes. I cover topics like IDE actions, refactoring, testing, debugging, etc. I think it’s worth the effort, so I’d say: ‘Give it a try.’”</p></blockquote>
<div class="blockquote__author">
<img decoding="async" class="blockquote__author-img" src="https://blog.jetbrains.com/wp-content/uploads/2024/09/brent-roose.jpg" alt="Brent Roose, JetBrains Developer Advocate">
<div class="blockquote__author-info">
<strong class="blockquote__author-title">Brent Roose, <a href="https://www.youtube.com/@phpannotated" target="_blank" rel="noopener">PHP Annotated</a> YouTube channel</strong>
<span class="blockquote__author-subtitle">Developer Advocate for PhpStorm</span>
</div>
</div>
</div>
<p class="has-text-align-center"><a class="jb-download-button" href="https://www.jetbrains.com/phpstorm/download/" target="_blank" rel="noopener">Try PhpStorm</a></p>
<h3 class="wp-block-heading">IDE or editor of choice per framework</h3>
<p><a href="https://www.jetbrains.com/phpstorm/" target="_blank" rel="noopener">PhpStorm</a> (including <a href="https://www.jetbrains.com/idea/" target="_blank" rel="noopener">IntelliJ IDEA Ultimate</a> with the PHP plugin) is the leading choice among PHP developers across most frameworks, particularly <a href="https://symfony.com/" target="_blank" rel="noopener">Symfony</a>, where it is used by a substantial majority. For Laravel developers, PhpStorm also holds the top spot and faces competition from <a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VS Code</a>, the second most popular choice. Among <a href="https://wordpress.org/" target="_blank" rel="noopener">WordPress</a> developers, VS Code narrowly surpasses PhpStorm as the preferred editor, likely reflecting WordPress’s broader user base and its inclination toward lightweight tools.</p>
<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeiQ4G52eNswGNrvMmexMpMYlT_TMA1bR3ha-NrTxTjVrd9oxvvstUncvq6Ci3JfgLGW8ZfnfEfqGufplvaDs60iUi1CpJO-EJtji6w_66sD2Bj5Yu-SaQJZpCVCFeqbBLKZ3THfQ?key=fKO6Jtjzu2cAEWpekcM4ijuH" alt=""/></figure>
<h3 class="wp-block-heading">Debugging</h3>
<p>When it comes to how PHP developers debug, the picture is almost unchanged from 2023, with the majority of those surveyed still favoring dumping values over using a dedicated debugger.</p>
<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeBgI2LiyxvAvit37ymmM9c5-WhvrYSHnZOlomLb4sqYNViBqVESB4ZHnSN_f9vjPyKm46Op_bDQCMvn6u6LT8r-shu1wJpHNEaVhHGR1VBQH7bygkzGG0yttK6ZGhbYkvjX9iHPA?key=fKO6Jtjzu2cAEWpekcM4ijuH" alt=""/></figure>
<div class="blockquote">
<blockquote><p>“In my experience, a debugger is a really powerful tool, but it’s also difficult to get started with. Because PHP doesn’t have a built-in debugger, there’s an additional installation process you need to go through to set it up. On top of that, debugging itself is a skill that needs practice. <br />
I think there’s a lot of room for improvement when it comes both to learning debugging as well as to making the debugger experience more frictionless. I’m looking forward to next year though, as the PhpStorm team is going to work to make debugging more enjoyable. Meanwhile, I’ve already made a video about getting started with Xdebug, check it out <a href="https://www.jetbrains.com/phpstorm/getting-started/episode-6/" target="_blank" rel="noopener">here</a>.”</p></blockquote>
<div class="blockquote__author">
<img decoding="async" class="blockquote__author-img" src="https://blog.jetbrains.com/wp-content/uploads/2024/09/brent-roose.jpg" alt="Brent Roose, JetBrains Developer Advocate">
<div class="blockquote__author-info">
<strong class="blockquote__author-title">Brent Roose, <a href="https://www.youtube.com/@phpannotated" target="_blank" rel="noopener">PHP Annotated</a> YouTube channel</strong>
<span class="blockquote__author-subtitle">Developer Advocate for PhpStorm</span>
</div>
</div>
</div>
<h3 class="wp-block-heading">Testing frameworks</h3>
<p><a href="https://phpunit.de/index.html" target="_blank" rel="noopener">PHPUnit</a> is still the most widely used testing framework, with only a slight decrease in users, while <a href="https://pestphp.com/" target="_blank" rel="noopener">Pest</a> retains the 13% share it achieved in 2023.</p>
<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdqibJjQZSqMRoL0qWKnAhoRJGFOgIrkcABzGg9DkI8PdgohRaFubNkHYUNPop18zXhnlQwDXMj15HK1baELFA1zi-_Fqm4ZYwcpJTyhCIsQdJfiZ6HClEdQfy18v8N05kl7h-PzA?key=fKO6Jtjzu2cAEWpekcM4ijuH" alt=""/></figure>
<div class="blockquote">
<blockquote><p>“I’m incredibly grateful for the growth of Pest in 2024. Its daily downloads on Packagist have doubled in just one year. With features like built-in snapshot testing, stress testing, mutation testing, architectural testing, type coverage, and more, Pest has grown into an incredibly powerful testing framework. Thank you to everyone who made this possible!”</p></blockquote>
<div class="blockquote__author">
<img decoding="async" class="blockquote__author-img" src="https://blog.jetbrains.com/wp-content/uploads/2025/02/nuno-maduro.jpg" alt="Nuno Maduro">
<div class="blockquote__author-info">
<strong class="blockquote__author-title">Nuno Maduro, <a href="https://x.com/enunomaduro" target="_blank">X</a></strong>
<span class="blockquote__author-subtitle">Creator of the Pest testing framework </span>
</div>
</div>
</div>
<h3 class="wp-block-heading">Code quality tools</h3>
<p>A cluster of 3 code quality tools stood above the rest in 2024. Despite the slight decrease from last year, <a href="https://cs.symfony.com/" target="_blank" rel="noopener">PHP CS Fixer</a> is still at the top. Meanwhile, second-ranked <a href="https://phpstan.org/" target="_blank" rel="noopener">PHPStan</a> closed the gap between itself and first place to just 1%. <a href="https://github.com/squizlabs/PHP_CodeSniffer" target="_blank" rel="noopener">PHP_CodeSniffer</a> continued its decline following last year’s 5 percentage point drop. </p>
<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcmciP3yBSbApw4mPpZROvnV5V0RGzvsS2YXEgixsvxyJRXaUMjfHq0HCh-a4ehoWa-R-juaxXGtwxcUfIdlsNdi1QB0cgEaH-uwxPCpT_1-_k3OBrm6BAuni4P-qdH0NRSRpMvSw?key=fKO6Jtjzu2cAEWpekcM4ijuH" alt=""/></figure>
<h2 class="wp-block-heading">Adoption of AI</h2>
<p>We uncovered some interesting trends regarding the use of AI tools for coding and development-related activities among PHP users. ChatGPT emerged as the most widely used AI tool, with GitHub Copilot following as the second most popular. Other tools, such as Google Gemini and JetBrains AI Assistant, saw less adoption overall.</p>
<figure class="wp-block-image"><img decoding="async" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeGNV5GgvVpbLZY0GxAZiOlkG_B2Mclqy-tjPmQh1ZqMHSmyYwKDfwEGj4IUkxQKHPLB8E4Y41SA6Cz1-hk6AFCWNcf0-JMe7uHmZNJ8Qf6vXGMCe5CGrLzwR-PqQ-sNTTivfMoIw?key=fKO6Jtjzu2cAEWpekcM4ijuH" alt=""/></figure>
<p>On company policies surrounding AI usage, attitudes remain varied. While 29% of respondents reported that AI tools are allowed for all projects, an almost equal proportion (27%) said their companies only allow AI tools for select projects. Interestingly, 27% of respondents reported that their companies do not have any explicit AI usage policy, reflecting the ongoing normalization of these tools in development environments. Meanwhile, only a small percentage (7%) of organizations prohibit the use of third-party AI tools altogether.</p>
<p>These findings reveal a growing acceptance of AI in development workflows, with ChatGPT and GitHub Copilot leading the way as key contributors to productivity and problem-solving. Despite being relatively new to the race, we’re constantly <a href="https://blog.jetbrains.com/blog/2024/10/22/introducing-mellum-jetbrains-new-llm-built-for-developers/">improving</a> our own JetBrains AI Assistant to bring the most efficient AI experience to developers.</p>
<p class="has-text-align-center"><a class="jb-download-button" href="https://www.jetbrains.com/ai/" target="_blank" rel="noopener">Try AI Assistant</a></p>
<h2 class="wp-block-heading">The PHP Foundation: Projects and focus for 2025</h2>
<p>The <a href="https://thephp.foundation/" target="_blank" rel="noopener">PHP Foundation</a> is a non-profit organization established in November 2021 to support the development and sustainability of the PHP programming language. As one of the founding members of the PHP Foundation, JetBrains has been instrumental in supporting this initiative, providing funding and helping to drive collaboration within the global PHP community.<br><br>The foundation team is responsible for many of the recent additions to the language and ecosystem, including <a href="https://thephp.foundation/blog/2024/11/01/how-hooks-happened/" target="_blank" rel="noopener">property hooks</a>, <a href="https://stitcher.io/blog/new-in-php-84#asymmetric-visibility-rfc" target="_blank" rel="noopener">asymmetric visibility</a>, <a href="https://stitcher.io/blog/new-in-php-84#lazy-objects-rfc" target="_blank" rel="noopener">lazy objects</a>, a <a href="https://thephp.foundation/blog/2024/11/19/pie-pre-release/" target="_blank" rel="noopener">new tool for installing extensions</a> called <a href="https://github.com/php/pie/" target="_blank" rel="noopener">PIE</a>, and a security audit of the PHP source code – all in 2024 alone!</p>
<p>So what’s coming in 2025? The main focus for the foundation will remain the same – improving the language for users and ensuring security and maintenance. Roman Pronskiy, PhpStorm Developer Advocate and Executive Director of The PHP Foundation, asked the community to share their opinions and ideas on X:</p>
<blockquote class="twitter-tweet" data-dnt="true"><p lang="en" dir="ltr">Hey folks, as we’re planning <a href="https://twitter.com/ThePHPF?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">@ThePHPF</a>'s 2025 strategy, I’d love to hear from you. <br><br>What’s been frustrating you about PHP or ecosystem lately? Any tools, workflows, or ideas you think could make things better?</p>— Roman Pronskiy (@pronskiy) <a href="https://twitter.com/pronskiy/status/1877672616355340392?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">January 10, 2025</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>We received many interesting suggestions about what we should prioritize. Here are just a few of them:</p>
<ul>
<li>A production-ready, built-in web server</li>
<li>A database connection pool</li>
<li>WASM support</li>
<li>Bundled extensions</li>
<li>Generics (of course)</li>
<li>Records/structs</li>
</ul>
<p>But the one that stood out more in this particular thread was the redesign of the <a href="http://php.net" target="_blank" rel="noopener">php.net</a> website! What do you think?</p>
<p>There are some ongoing developments such as native modules, <a href="https://wiki.php.net/rfc/pattern-matching" target="_blank" rel="noopener">pattern matching</a>, and others. Follow The PHP Foundation’s social media channels to learn about the coming projects: <a href="https://twitter.com/thephpf" target="_blank" rel="noopener">X</a>, <a href="https://phpc.social/@thephpf" target="_blank" rel="noopener">Mastodon</a>, <a href="https://www.linkedin.com/company/phpfoundation/" target="_blank" rel="noopener">LinkedIn</a>, and <a href="https://bsky.app/profile/thephpf.bsky.social" target="_blank" rel="noopener">Bluesky</a>.</p>
<h2 class="wp-block-heading">Conclusion</h2>
<p>The PHP ecosystem is clearly continuing to evolve as developers adopt new tools, technologies, and practices. PhpStorm remains a top choice for many, particularly among those using frameworks like Symfony and Laravel, while VS Code has established a strong foothold within the WordPress community. The growing adoption of AI tools reflects an increasing reliance on AI to assist with coding and development tasks, with developers viewing these tools as collaborators and problem solvers. However, the survey also highlights areas for growth, such as improving gender diversity, broadening representation in the field, and fostering greater inclusivity. As the PHP community moves forward, these insights into today’s development landscape reveal opportunities to strengthen both the tools and the community as a whole.</p>
<p>Keep up to date with all the latest news with our curated <a href="https://www.jetbrains.com/lp/php-annotated/" target="_blank" rel="noopener">PHP Annotated</a> monthly overview.</p>
Activating rather than relaunching a menu bar app on macOS - Rob Allenhttps://akrabat.com/?p=72592025-02-04T11:00:00.000Z<p>One very minor thing that's been bugging me since macOS Sequoia came out is that if you launch an app that lives in your menu bar, but also has a hidden Dock icon a second time, then the Dock icon will re-appear.</p>
<p>This happens to me a lot because I use <a href="https://www.alfredapp.com">Alfred</a> to launch apps and also to bring an app to the front. This works because opening a running app will bring it to the foreground. You can test this using the Terminal with the <tt>open -a</tt> command if you want to.</p>
<p>I've used this workflow for years with the <a href="https://www.getharvest.com/apps/mac">Harvest</a> app. Harvest lives in my menu bar and can be accessed using the mouse. However, I tend to just open it again with Alfred which opens the main window which is anchored to the menu bar icon and then press space to stop the current timer or <tt>cmd+n</tt> to enter the details to start a new one.</p>
<p>Since Sequoia, I've noticed that when I use Alfred to open Harvest's main window, the Harvest icon reappears on my Dock. I don't want this, so I wrote an Alfed Workflow to fix it.</p>
<h2>Alfred Workflow</h2>
<p>The workflow consists of two blocks: a Keyword input and an AppleScript action.</p>
<p><img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/01/alfred-harvest-workflow.png" alt="Alfred harvest workflow." border="0" width="340" height="133" /></p>
<p>The Keyword input block simply responds to the word "Harvest" as that's what I type:</p>
<p><img fetchpriority="high" decoding="async" src="https://akrabat.com/wp-content/uploads/2025/01/alfred-harvest-keyword-input.png" alt="Alfred harvest keyword input." border="0" width="450" height="258" /></p>
<p>Then, to either start Harvest, or activate it, I use some AppleScript:</p>
<pre lang="AppleScript">
tell application "System Events"
if not (exists process "Harvest") then
tell application "Harvest" to launch
else
tell application "Harvest" to activate
end if
end tell
</pre>
<p>AppleScript remains one of the harder languages I write and I have to look things up every time, but at least LLMs make this easier. Once written, it's quite easier to see what it does. The magic is that with this script we activate it rather than launch it once running.</p>
<p>In Alfred, it looks like this:</p>
<p><img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/01/alfred-harvest-applescript.png" alt="Alfred harvest applescript." border="0" width="500" height="362" /></p>
<h2>Training Alfred</h2>
<p>That's it. All I needed to do then was continually use Alfred with variations of "H", "Ha", "Har", etc to train it to put my new "Open Harvest" action at the top of the list.</p>
PHP Annotated – January 2025 - PhpStorm : The IDE that empowers PHP developers | The JetBrains Bloghttps://blog.jetbrains.com/?post_type=phpstorm&p=5414152025-01-29T11:26:50.000Z<p><a href="https://blog.jetbrains.com/phpstorm/2025/01/php-annotated-january-2025/"><img decoding="async" class="alignnone size-full" src="https://blog.jetbrains.com/wp-content/uploads/2025/01/ps-featured_blog_1280x720_en.png" alt="PHP Annotated" width="900"></a></p>
<style>@media (min-width: 769px) { main .article-section .content ul:not([class]):not([id]) li ul:not([class]):not([id]) { margin-top: 0; margin-bottom: 24px; } } main .article-section .content ul:not([class]):not([id]) li, main .article-section .content ul:not([class]):not([id]) > li {padding-bottom: 18px;} main .article-section .content ul:not([class]):not([id]) li ul:not([class]):not([id]) li {padding-bottom: 0;} img.alignico {margin-right: 10px;margin-top: 5px;float: left;} summary {display: list-item;cursor: pointer;font-style: italic; } section.article-section a {color: #7755f3} code {color: red;} #roman-pronskiy,.copy-heading:has(#roman-pronskiy){margin-top: 0;} main li a[href^="https://github.com"]:before {background: no-repeat 2px center url(data:image/svg+xml;utf8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgZmlsbD0ibm9uZSIgdmlld0JveD0iMCAwIDMyIDMyIj48cGF0aCBmaWxsPSIjMjQyOTJFIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNiAwYTE2IDE2IDAgMCAwLTUgMzEuMmMuNy4xIDEtLjQgMS0uOHYtM2MtNCAuOC01LTEtNS40LTEuOC0uMS0uNS0xLTItMS42LTIuMy0uNi0uMy0xLjQtMSAwLTEgMS4yIDAgMi4xIDEuMSAyLjQgMS42IDEuNSAyLjQgMy44IDEuNyA0LjcgMS4zLjEtMSAuNi0xLjcgMS0yLjEtMy41LS40LTcuMy0xLjgtNy4zLTggMC0xLjcuNy0zLjEgMS43LTQuMi0uMi0uNC0uNy0yIC4xLTQuMyAwIDAgMS40LS40IDQuNCAxLjdhMTQuOCAxNC44IDAgMCAxIDggMGMzLjEtMi4xIDQuNC0xLjcgNC40LTEuNyAxIDIuMi40IDMuOS4yIDQuM2E2IDYgMCAwIDEgMS42IDQuM2MwIDYuMS0zLjcgNy41LTcuMyA3LjkuNi41IDEuMSAxLjQgMS4xIDN2NC4zYzAgLjQuMyAxIDEuMS44QTE2IDE2IDAgMCAwIDE2IDBaIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=);content: "";padding-left: 20px;} main li a[href^="https://www.youtube.com"]:before {background: no-repeat 0px center url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' version='1.1' viewBox='0 0 68 48' width='100%25'%3E%3Cpath class='ytp-large-play-button-bg' d='m .66,37.62 c 0,0 .66,4.70 2.70,6.77 2.58,2.71 5.98,2.63 7.49,2.91 5.43,.52 23.10,.68 23.12,.68 .00,-1.3e-5 14.29,-0.02 23.81,-0.71 1.32,-0.15 4.22,-0.17 6.81,-2.89 2.03,-2.07 2.70,-6.77 2.70,-6.77 0,0 .67,-5.52 .67,-11.04 l 0,-5.17 c 0,-5.52 -0.67,-11.04 -0.67,-11.04 0,0 -0.66,-4.70 -2.70,-6.77 C 62.03,.86 59.13,.84 57.80,.69 48.28,0 34.00,0 34.00,0 33.97,0 19.69,0 10.18,.69 8.85,.84 5.95,.86 3.36,3.58 1.32,5.65 .66,10.35 .66,10.35 c 0,0 -0.55,4.50 -0.66,9.45 l 0,8.36 c .10,4.94 .66,9.45 .66,9.45 z' fill='%23FF0000' fill-opacity='0.81'%3E%3C/path%3E%3Cpath d='m 26.96,13.67 18.37,9.62 -18.37,9.55 -0.00,-19.17 z' fill='%23fff'%3E%3C/path%3E%3Cpath d='M 45.02,23.46 45.32,23.28 26.96,13.67 43.32,24.34 45.02,23.46 z' fill='%23ccc'%3E%3C/path%3E%3C/svg%3E"); content: "";padding-left: 18px;background-size: 16px;}</style>
<p>Welcome to the January edition of PHP Annotated! This recap is carefully handcrafted and brings you the most interesting developments in the PHP community over the past couple of months, so you don’t have to sift through the noise, we’ve done it for you.</p>
<h2 id="highlights">Highlights</h2>
<ul>
<li><a href="https://www.php.net/releases/8.4/" target="_blank" rel="noopener"><strong>PHP 8.4</strong></a><br />
PHP 8.4 was officially released on November 21, 2024, and by now, version 8.4.3 is already available.</p>
<p> This major language update brings many new features, such as property hooks, asymmetric visibility, an updated DOM API, performance improvements, bug fixes, and general code cleanup.</p>
<p> If you want to learn more about all the goodies in the release, visit <a href="https://php.watch/versions/8.4" target="_blank" rel="noopener">php.watch</a> and <a href="https://stitcher.io/blog/new-in-php-84" target="_blank" rel="noopener">stitcher.io</a>.</p>
<p> There are also some lesser known improvements that you can learn more about from the Tideways blog:</p>
<ul>
<li><a href="https://tideways.com/profiler/blog/php-8-4-improves-closure-naming-for-simplified-debugging" target="_blank" rel="noopener">PHP 8.4 improves Closure Naming for simplified debugging</a>.</li>
<li><a href="https://tideways.com/profiler/blog/whats-new-in-php-8-4-in-terms-of-performance-debugging-and-operations" target="_blank" rel="noopener">What’s new in PHP 8.4 in terms of performance, debugging, and operations</a>.</li>
</ul>
<p> <strong>Install or upgrade to PHP 8.4</strong></p>
<ul>
<li>Windows: Compiled binaries available at <a href="http://windows.php.net" target="_blank" rel="noopener">windows.php.net</a>.</li>
<li>Fedora/RHEL/CentOS: Available as a software collection (php84) from the <a href="https://blog.remirepo.net/" target="_blank" rel="noopener">Remi repo</a>.</li>
<li>macOS: PHP 8.4 can be installed via Homebrew using the <a href="https://github.com/shivammathur/homebrew-php/packages" target="_blank" rel="noopener">shivammathur/homebrew-php</a> tap.</li>
<li>Docker: PHP 8.4 images are now available on <a href="https://hub.docker.com/_/php" target="_blank" rel="noopener">Docker Hub</a> with <a href="https://hub.docker.com/_/php/tags?page=1&name=8.4" target="_blank" rel="noopener">8.4 tags</a>.</li>
<li><a href="https://herd.laravel.com/" target="_blank" rel="noopener">Herd</a> also comes with PHP 8.4 supported.</li>
</ul>
<p> Watch a 📺 <a href="https://www.youtube.com/live/1AL2oDt9q38" target="_blank" rel="noopener">Celebrating PHP 8.4</a> stream with Nicolas, Brent, and Roman:<br />
<iframe width="560" height="315" src="https://www.youtube.com/embed/1AL2oDt9q38?si=0fCv3BcMeWpwGBMg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</li>
<li><strong>PHP 8.2 goes in security-fixes-only phase</strong>
<p> Starting this year, PHP versions now follow <a href="https://wiki.php.net/rfc/release_cycle_update" target="_blank" rel="noopener"><strong>a four-year support timeline</strong></a>: two years of active support followed by two years of security fixes.</p>
<p> For PHP 8.1 security patches will be provided until December 31, 2025, while PHP 8.2 will be maintained until December 31, 2026. The recent <a href="https://www.php.net/releases/8_2_27.php" target="_blank" rel="noopener">PHP 8.2.27</a> release marked the end of its active support phase.
</li>
<li><a href="https://phpstan.org/blog/phpstan-2-0-released-level-10-elephpants" target="_blank" rel="noopener"><strong>PHPStan 2.0 has been released</strong></a>
<p> This update introduces Level 10 code analysis with stricter handling of mixed types, and adds support for the List type. You can also expect reduced memory consumption and improved performance.</p>
<p> Markus Staab shares interesting technical insights about improving PHPStan:<br />
<a href="https://staabm.github.io/2024/11/26/phpstan-mixed-types.html" target="_blank" rel="noopener">A mixed type PHPStan journey</a>, <a href="https://staabm.github.io/2024/11/17/phpstan-performance-on-different-hardware.html" target="_blank" rel="noopener">PHPStan performance on different hardware</a>, <a href="https://staabm.github.io/2024/11/28/phpstan-php-version-in-scope.html" target="_blank" rel="noopener">My new PHPStan focus: multi-phpversion support</a>.
</li>
<li><strong>🎂 <a href="https://thephp.foundation/blog/2024/11/22/php-foundation-turns-three/" target="_blank" rel="noopener">The PHP Foundation turned three</a></strong>
<p> The PHP Foundation was established <a href="https://blog.jetbrains.com/phpstorm/2021/11/the-php-foundation/">three years ago</a>. Over the past year, the PHP Foundation has supported the work of ten <a href="https://thephp.foundation/structure/#core_developers" target="_blank" rel="noopener">core developers</a>, and made a significant contribution to the PHP language.</p>
<p> Consider <a href="https://thephp.foundation/donate/" target="_blank" rel="noopener">supporting</a> the PHP Foundation via <a href="https://opencollective.com/phpfoundation" target="_blank" rel="noopener">OpenCollective</a> or <a href="https://github.com/sponsors/thephpf" target="_blank" rel="noopener">GitHub Sponsors</a>.</p>
<p> The PHP Foundation has been also active with developments:</p>
<ul>
<li><a href="https://thephp.foundation/blog/2024/11/01/how-hooks-happened/" target="_blank" rel="noopener">PHP 8.4: How Property Hooks Happened</a> – Larry Garfield details the decade-long journey of implementing Property Hooks in PHP 8.4.</li>
<li>The PHP Installer for Extensions <a href="https://github.com/php/pie" target="_blank" rel="noopener">php/pie</a> reached version 0.5!<br />
Learn more about it from <a href="https://blog.codito.dev/2024/11/pie-new-extension-installer-for-php/" target="_blank" rel="noopener">Pie: new extension installer for PHP</a> post by Grzegorz Korba.
</li>
</ul>
</li>
<li><a href="https://github.com/rectorphp/rector/releases/tag/2.0.0" target="_blank" rel="noopener"><strong>Rector 2.0</strong></a><br />
This major release updates dependencies (PHPStan 2 and PHP-Parser 5), runs 10–15% faster, and has <a href="https://getrector.com/blog/5-new-features-in-rector-20" target="_blank" rel="noopener">5 new features</a>.
</li>
<li><a href="https://dunglas.dev/2024/11/frankenphp-1-3-massive-performance-improvements-watcher-mode-dedicated-prometheus-metrics-and-more/" target="_blank" rel="noopener"><strong>FrankenPHP 1.3</strong></a><br />
In this release, expect performance improvements, watcher mode, dedicated Prometheus metrics, and more.
</li>
<li><a href="https://packagist.com/features/conductor" target="_blank" rel="noopener"><strong>Conductor – Automatic dependency updates for Composer</strong></a><br />
The Packagist team announced a new tool that is similar to Dependabot, but specifically tailored to PHP projects.
</li>
<li><a href="https://x.com/s0yuka/status/1863908809317486635" target="_blank"><strong>You can now run code examples directly on the php.net website</strong></a><br />
<img decoding="async" src="https://blog.jetbrains.com/wp-content/uploads/2025/01/Screenshot-2025-01-28-at-10.19.06-AM.png" alt="" width="670">
</li>
</ul>
<h2 id="php-core">PHP Core</h2>
<ul>
<li><strong>✅ <a href="https://wiki.php.net/rfc/curl_share_persistence_improvement" target="_blank" rel="noopener">RFC: Add persistent curl share handles</a></strong>
<p> PHP 8.5 will bring a new function <code>curl_share_init_persistent()</code>, which would allow cURL handles to be stored in global memory and reused in subsequent requests.</p>
<p> Persistence allows PHP scripts to eliminate the overhead of establishing a connection (and DNS lookups, SSL session IDs, etc.) and can improve performance and reliability.
</li>
<li>✅ <a href="https://wiki.php.net/rfc/closures_in_const_expr" target="_blank" rel="noopener"><strong>RFC: Support Closures in constant expressions</strong></a>
<p> In PHP 8.5 it will be possible to use closures in previously</p>
<ul>
<li>In attribute parameters,</li>
<li>As default values of properties and parameters.</li>
<li>Constants and class constants.</li>
</ul>
<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-linenumbers="false" data-enlighter-title="">
<?php
class Foo
{
public static Closure $callback = static function ($item) { echo "Hello world"; };
}
function my_array_filter(
array $array,
Closure $callback = static function ($item) { return !empty($item); },
) {
// ...
}
</pre>
</li>
<li>✅ <strong><a href="https://wiki.php.net/rfc/attributes-on-constants" target="_blank" rel="noopener">RFC: Attributes on Constants</a></strong>
<p> Attributes were first introduced in the <a href="https://wiki.php.net/rfc/attributes_v2" target="_blank" rel="noopener">RFC: Attributes v2</a>. Daniel Scherzer proposes to add support for attributes on compile-time non-class constants.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="php" data-enlighter-linenumbers="false" data-enlighter-title="">
#[\MyAttribute]
const EXAMPLE = 1;
</pre>
</li>
<li>✅ <strong><a href="https://wiki.php.net/rfc/error_backtraces_v2" target="_blank" rel="noopener">RFC: Error Backtraces v2</a></strong>
<p> Previously, unlike exceptions, PHP errors did not provide backtraces, which made it difficult to figure out their underlying cause. PHP 8.5 will come with a new ini setting <code>fatal_error_backtraces=1</code> which will generate detailed error messages and trace for <code>E_ERROR</code>s.
</li>
<li><strong>📣 <a href="https://wiki.php.net/rfc/records" target="_blank" rel="noopener">RFC: Records</a> vs. <a href="https://wiki.php.net/rfc/dataclass" target="_blank" rel="noopener">RFC: Data Class</a> vs. <a href="https://wiki.php.net/rfc/structs-v2" target="_blank" rel="noopener">RFC: Structs</a></strong>
<p> Apparently, there is a big interest in the community to add a simple native way of creating <a href="https://en.wikipedia.org/wiki/Value_object" target="_blank" rel="noopener">Value Objects</a> in PHP.</p>
<p> Which one would you prefer?
</li>
<li>
<iframe width="560" height="315" src="https://www.youtube.com/embed/BvAcP6RtlAA?si=czt_aNe3rM29YuMr" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</li>
</ul>
<h2 id="tools">Tools</h2>
<ul>
<li><a href="https://github.com/ringPHP/php-mrloop" target="_blank" rel="noopener">ringPHP/php-mrloop</a> – A PHP port of the mrloop eventware designed to harness the powers of <code>io_uring</code>. Read an <a href="https://agiroloki.medium.com/introducing-ext-mrloop-f85ed4d8881d" target="_blank" rel="noopener">Introducing ext-mrloop</a> blog post to learn why io_ring is better than <code>select()</code>, <code>poll()</code>, and <code>epoll()</code> async io implementations.</li>
<li><a href="https://github.com/loupe-php/loupe" target="_blank" rel="noopener">loupe-php/loupe</a> – A full text search engine with tokenization, stemming, typo tolerance, filters, and geo support based on only PHP and SQLite.</li>
<li><a href="https://github.com/twigstan/twigstan" target="_blank" rel="noopener">twigstan/twigstan</a> – TwigStan is a static analyzer for Twig templates powered by PHPStan.</li>
<li><a href="https://github.com/azjezz/psl/" target="_blank" rel="noopener">azjezz/psl</a> – A modern, consistent, centralized, well-typed, non-blocking set of APIs for PHP programmers.</li>
<li><a href="https://endor.dev/s/lamp84" target="_blank" rel="noopener">Full LAMP Sandbox</a> – (PHP + WebServer + DB) <a href="https://docs.endor.dev/faq/general/" target="_blank" rel="noopener">running 100% inside your browser</a>.</li>
<li><a href="https://github.com/carthage-software/mago" target="_blank" rel="noopener">carthage-software/mago</a> – A toolchain for PHP that aims to provide a set of tools to help developers write better code. Built in Rust.</li>
<li><a href="https://github.com/smoqadam/pvm" target="_blank" rel="noopener">smoqadam/pvm</a> – A simple bash script to manage multiple PHP versions on Linux and macOS.</li>
<li><a href="https://github.com/tnylea/php-ext" target="_blank" rel="noopener">tnylea/php-ext</a> – A Chrome extension to show PHP (Laravel) devtools console.</li>
<li><a href="https://github.com/phikiphp/phiki" target="_blank" rel="noopener">phikiphp/phiki</a> – Syntax highlighting powered by TextMate grammars in PHP.</li>
<li><a href="https://github.com/coduo/php-humanizer" target="_blank" rel="noopener">coduo/php-humanizer</a> – A useful tool to transform different strings and numbers to human-readable form.</li>
<li><a href="https://docs.roadrunner.dev/docs/releases/v2024-3-0" target="_blank" rel="noopener">RoadRunner v2024.3.0</a> – The PHP application server got a major update with <a href="https://docs.roadrunner.dev/docs/php-worker/auto-scaling" target="_blank" rel="noopener">auto workers scaling</a>.</li>
</ul>
<h2 id="ai">AI</h2>
<ul>
<li><a href="https://github.com/echolabsdev/prism" target="_blank" rel="noopener">echolabsdev/prism</a> – A unified interface for working with LLMs in Laravel. Supports Anthropic, DeepSeek, Gemini, Groq, Mistral, Ollama, OpenAI, and xAI APIs.
<p> Also extensible with <a href="https://github.com/jordan-price/toolbox" target="_blank" rel="noopener">jordan-price/toolbox</a>.
</li>
<li><a href="https://github.com/CodeWithKyrian/whisper.php" target="_blank" rel="noopener">CodeWithKyrian/whisper.php</a> – Local Speech to Text in PHP made easy thanks to Whisper.cpp and OpenAI.</li>
<li><a href="https://github.com/deepseek-php/deepseek-php-client" target="_blank" rel="noopener">deepseek-php/deepseek-php-client</a> – Supercharged community-maintained PHP API client that allows you to interact with deepseek API.</li>
</ul>
<h2 id="phpstorm">PhpStorm</h2>
<ul>
<li><a href="https://blog.jetbrains.com/phpstorm/2024/11/phpstorm-2024-3-is-now-available/"><strong>PhpStorm 2024.3 is now available</strong></a>
<p> The new PhpStorm comes with full PHP 8.4 support, inline AI prompts, Laravel Herd support.</p>
<p> Support for <code>.env</code> files <a href="https://blog.jetbrains.com/phpstorm/2025/01/support-for-env-files/">is now built into PhpStorm</a>. Previously it required installing a separate plugin.</p>
<p> JetBrains also announced a closed beta for <a href="https://blog.jetbrains.com/junie/2025/01/meet-junie-your-coding-agent-by-jetbrains/"><strong>Junie, AI Coding Agent</strong></a> for IDEs.
</li>
<li><a href="https://plugins.jetbrains.com/plugin/26121-metastorm/edit" target="_blank" rel="noopener">MetaStorm</a> – This plugin allows extending PhpStorm’s behaviour and adding support for your custom frameworks with a few lines in a config file. It unlocks both references and autocompletion at regular places such as <code>method($object,<property of object>), render(<file name>)</code>, etc.
</li>
<li><a href="https://github.com/buggregator/phpstorm-plugin" target="_blank" rel="noopener">buggregator/phpstorm-plugin</a> – This plugin works in the pair with <a href="https://github.com/buggregator/trap" target="_blank" rel="noopener">buggregator/trap</a> and allows dumping and debugging PHP projects just inside the IDE. Supports VarDumper server, Xhprof profiler, local SMTP server, local Sentry, and much more.</li>
<li><a href="https://plugins.jetbrains.com/plugin/26412-cron--crontab-support/edit" target="_blank" rel="noopener">Cron & Crontab Support Plugin</a>
</li>
</ul>
<h2 id="frameworks">Frameworks</h2>
<ul>
<li><a href="https://github.com/symfony/symfony/pull/59032" target="_blank" rel="noopener"><strong>Symfony 7.2.0 has just been released</strong></a><br />
Check the <a href="https://symfony.com/blog/category/living-on-the-edge/7.2" target="_blank" rel="noopener">Living on the Edge</a> category on this blog to learn about the main features of this new stable release.
</li>
<li><a href="https://new.drupal.org/drupal-cms" target="_blank" rel="noopener"><strong>Drupal CMS</strong></a><br />
Previously known as <a href="https://www.drupal.org/about/starshot" target="_blank" rel="noopener">Drupal Starshot Initiative</a>, Drupal CMS is the new way of creating web apps based on Drupal with no-code building experience.
</li>
<li><a href="https://github.com/thedevdojo/wave" target="_blank" rel="noopener">thedevdojo/wave</a> – The SaaS starter kit based on Laravel.</li>
<li><a href="https://adamadam.blog/2025/01/08/wordpress-as-a-git-repo/" target="_blank" rel="noopener">WordPress as a git repo</a> by Adam Zieliński – A promising addition that might be landed in WordPress core, allowing using markdown files as a backend for WordPress site.</li>
<li><a href="https://www.geocod.io/code-and-coordinates/2025-01-13-how-geocodio-keeps-300M-addresses-up-to-date/" target="_blank" rel="noopener">How Geocodio keeps 300M addresses up to date</a> with Laravel and SQLite.</li>
<li><a href="https://github.com/Naoray/laravel-github-monolog" target="_blank" rel="noopener">Naoray/laravel-github-monolog</a> – Laravel log Channel for GitHub issues.</li>
</ul>
<h2 id="misc">Misc</h2>
<ul>
<li><a href="https://davorminchorov.com/articles/building-maintainable-php-applications-data-transfer-objects" target="_blank" rel="noopener">Building Maintainable PHP Applications: Data Transfer Objects</a> by Davor Minchorov.</li>
<li><a href="https://jump24.co.uk/journal/playtime-with-php-attributes/" target="_blank" rel="noopener">Playtime with PHP Attributes</a> by Pete Wond.</li>
<li><a href="https://tomasvotruba.com/blog/why-final-classes-make-rector-and-phpstan-more-powerful" target="_blank" rel="noopener">Why Final Classes make Rector and PHPStan more powerful</a> by Tomas Votruba.</li>
<li><a href="https://phpfashion.com/en/property-hooks-in-php-8-4-game-changer-or-hidden-trap" target="_blank" rel="noopener">Property Hooks in PHP 8.4: Game Changer or Hidden Trap?</a> by David Grudl.</li>
<li><a href="https://stitcher.io/blog/php-version-stats-january-2025" target="_blank" rel="noopener">PHP version stats: January, 2025</a> by Brent.</li>
<li><a href="https://chrastecky.dev/programming/unleash-feature-flags-in-php" target="_blank" rel="noopener">Unleash: Feature flags in PHP</a> by Dominik Chrástecký.</li>
<li><a href="https://heera.it/the-dangers-of-phps-unserialize-and-how-to-stay-safe" target="_blank" rel="noopener">The Dangers of PHP’s unserialize and How to stay safe</a> by Sheikh Heera.</li>
<li><a href="https://f2r.github.io/en/stop-using-pseudo-types" target="_blank" rel="noopener">Stop using Pseudo-Types</a> by Frédéric Bouchery.</li>
<li><a href="https://jonathanpurvis.co.uk/importing-1-7-billion-rows-of-csv-data-from-stripe-with-php/" target="_blank" rel="noopener">Importing 1.7 billion rows of CSV data from Stripe</a> by Jon Purvis.</li>
<li><a href="https://github.com/azjezz/php-pretty-diff" target="_blank" rel="noopener">azjezz/php-pretty-diff</a> – A nice demo repository on how to use Rust code in a PHP project with FFI.</li>
<li>
TIL: You can use #️⃣ emoji instead of <code>#</code> symbol in comments and attributes in PHP!<br />
<a href="https://x.com/joshmanders/status/1862869552620511596" target="_blank"><img decoding="async" src="https://blog.jetbrains.com/wp-content/uploads/2025/01/Screenshot-2025-01-29-at-12.09.52-PM.png" width="598"></a>
</li>
</ul>
<h2 id="conferences">Conferences</h2>
<p>These PHP events are all worth a visit, and some are still accepting presentation proposals:</p>
<ul>
<li><a href="https://laracon.eu/" target="_blank" rel="noopener">Laracon EU 2025</a> – Amsterdam, The Netherlands, February 3–4.</li>
<li><a href="https://www.phpconference.co.uk/" target="_blank" rel="noopener">PHP UK Conference 2025</a> – London, UK, February 19.</li>
<li><a href="https://laracon.in/" target="_blank" rel="noopener">Laracon India 2025</a> – Ahmedabad, India, March 8–9. <a href="https://docs.google.com/forms/d/e/1FAIpQLSeuj8ao7NGu-DhfTTZrGXIPO42IZrJnPHUf6Qk_uiHeA0sM0A/viewform" target="_blank" rel="noopener">CFP</a>
</li>
<li><a href="https://phpcon-odawara.jp/2025/" target="_blank" rel="noopener">PHP Conference Odawara 2025</a> – Japan, April 12.</li>
<li><a href="https://phptek.io/" target="_blank" rel="noopener">php[tek] 2025</a> – Chicago, IL, USA, May 20–22.</li>
<li><a href="https://summit.phpers.pl/en/" target="_blank" rel="noopener">PHPers Summit 2025</a> – Poznań, Poland, May 24–25. <a href="https://summit.phpers.pl/en/call-for-papers" target="_blank" rel="noopener">CFP</a>
</li>
<li><a href="https://websummercamp.com/2025/news/call-for-papers-for-web-summer-camp-2025-is-now-open" target="_blank" rel="noopener">Summer Camp</a> – Opatija, Croatia, July 3–5. <a href="https://websummercamp.com/2025/news/call-for-papers-for-web-summer-camp-2025-is-now-open" target="_blank" rel="noopener">CFP</a> 🆕</li>
</ul>
<p>To find a PHP meetup happening near you, check out <a href="https://www.php.net/cal.php?cm=05&cy=2024" target="_blank" rel="noopener">the calendar on php.net</a>.</p>
<h2 id="fun">Fun</h2>
<ul>
<li>
<iframe width="560" height="315" src="https://www.youtube.com/embed/AJRGxd9cVaY?si=60IjCDT3uftQYIRo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</li>
<li>
<iframe loading="lazy" width="560" height="315" src="https://www.youtube.com/embed/Jk8q7MNeWeQ?si=9iSLt_LsVgPz9chx" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</li>
</ul>
<hr />
</p>
<p>If you have any interesting or useful links to share via PHP Annotated, please leave a comment on this post or let us know <a href="https://twitter.com/pronskiy" target="_blank" rel="noopener">on X/Twitter</a>.</p>
<p style="text-align: left;" align="center"><a style="color: white" class="jb-download-button" title="Complete this form and get PHP Annotated Monthly delivered fresh to your email" href="https://info.jetbrains.com/PHP-Annotated-Subscription.html" target="_blank" rel="noopener">Subscribe to PHP Annotated</a></p>
<div class="about-author ">
<div class="about-author__box">
<div class="row">
<div class="about-author__box-img">
<img decoding="async" src="https://blog.jetbrains.com/wp-content/uploads/2022/07/php-annotated-roman.png" alt="" loading="lazy">
</div>
<div class="about-author__box-text">
<h4>Roman Pronskiy</h4>
<p>Developer Advocate at <a href="https://twitter.com/PhpStorm" target="_blank" rel="noopener">@PhpStorm</a>, Executive Director at <a href="https://twitter.com/ThePHPF" target="_blank" rel="noopener">@The PHP Foundation</a>.</p>
<p><a href="https://twitter.com/pronskiy" target="_blank" rel="noopener">Twitter</a> | <a href="https://github.com/pronskiy" target="_blank" rel="noopener">GitHub</a></p>
</div>
</div>
</div>
</div>
Using uv as your shebang line - Rob Allenhttps://akrabat.com/?p=72542025-01-28T11:00:00.000Z<p>I create a fair few scripts in my <tt>~/bin/</tt> directory to automate tasks. Since discovering <a href="https://akrabat.com/defining-python-dependencies-at-the-top-of-the-file/"><tt>uv</tt> and inline script metadata</a>, I've started using Python far more for these.</p>
<p>As <tt>~/bin</tt> is on my path, I want to run the script by calling it directly on the command line. To do this, I use this shebang:</p>
<pre>
#!/usr/bin/env -S uv run --script
</pre>
<p>The command line will now run <tt>uv run --script</tt> and pass the file as the argument. <tt>uv</tt> ignores the shebang and then runs the rest of the file as a normal Python file. </p>
<p>Once I've ensured that that script has executable permissions via <tt>chmod a+x {filname}</tt>, I'm now good to go with simple command line scripts written in Python that automatically handle their dependencies!</p>
Comments are Back - Blog entries :: mwop.nethttps://mwop.net/blog/2025-01-25-comments-are-back.html2025-01-25T18:46:59.000Z<p>For a number of years, I was using <a href="https://disqus.com">Disqus</a> to provide comments on my blog.
However, I was increasingly unhappy with how bloated the solution was, how many additional entries I was having to put into my Content Security Policy, and unsure how comfortable I was with having a third party own comments to my own site.</p>
<p>So last year, I removed comments from my site entirely.</p>
<p>This worked fine, and I didn't really think about it much, until somebody reached out to me via email recently, with what was essentially a comment on a blog post, and I realized that nobody else but me was going to benefit from it.</p>
<p>So I started thinking about how to go about adding comments again.</p>
<h3>Build it?</h3>
<p>I started, of course, by building.</p>
<p>I modeled what was essential to a comment; decided I'd use Markdown, with limits, to allow commenters some ability to style and format their comments; even got a schema setup in my database.</p>
<p>And then I realized a few things:</p>
<ul>
<li>I'd need an admin for moderating comments.</li>
<li>I'd likely need some way to notify at least myself when a new comment was added.</li>
<li>What about letting folks know their comment was submitted successfully?
Or that it was published?
(I planned to moderate all comments by default.)</li>
<li>What about letting folks know when a new comment was published on a post they'd previously commented on?</li>
<li>What about allowing folks to unsubscribe from those notifications?
And would I allow both granular (per blog post) and general (full site) unsubscription?</li>
<li>What about allowing folks to request all comments be deleted (per GDPR)?</li>
</ul>
<p>The more I thought about it, the more work I was seeing, and I wasn't sure how much I wanted to develop it.</p>
<h3>Research</h3>
<p>So I started researching commenting systems, and quickly found a variety of generic solutions exist, thankfully.
The research then boiled down to identifying which ones are actively maintained (because anything like a commenting system will likely need security updates and occasional updates to ensure compatibility with browsers and evolving security policies), which ones had the features I wanted, and how easily I'd be able to implement the solution.</p>
<p>I eventually settled on <a href="https://remark42.com">Remark42</a>.</p>
<p>Remark42 is privacy-focused, and allows me to keep all the data.
While there are a number of SSO integrations, they primarily use OAuth2, meaning that the integration is only for purposes of authentication.
I was also able to enable an email authentication option; this sends an email to the user with a token that they then pasted back into the form to authenticate.</p>
<p>As for the email, I was able to set it up to use an existing SMTP user to send out emails.
These are used for users who authenticate via email, sending notifications to me of new comments, and managing individual user notifications of comments.</p>
<p>I run it under its own domain (comments.mwop.net), and have functionality to embed comments via JavaScript in my site.
I was able to configure it such that it will only accept comments from specific domains, which helps prevent abuse of the system.
While I'd love to have the comments integrated on my site without JavaScript, the fact that I did not need to develop any of this on my own was a huge benefit.</p>
<p>Better: it uses limited Markdown for comment styling, and has built-in links to documentation on what you can use.</p>
<h3>Configuring Remark42</h3>
<p>I run the service using Docker Compose, using a single service in my Compose file:</p>
<pre><code class="language-yaml hljs yaml" data-lang="yaml"><span class="hljs-attr">services:</span>
<span class="hljs-attr">remark42:</span>
<span class="hljs-attr">image:</span> <span class="hljs-string">umputun/remark42:latest</span>
<span class="hljs-attr">container_name:</span> <span class="hljs-string">"remark42"</span>
<span class="hljs-attr">hostname:</span> <span class="hljs-string">"comments.mwop.net"</span>
<span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
<span class="hljs-attr">logging:</span>
<span class="hljs-attr">driver:</span> <span class="hljs-string">json-file</span>
<span class="hljs-attr">options:</span>
<span class="hljs-attr">max-size:</span> <span class="hljs-string">"10m"</span>
<span class="hljs-attr">max-file:</span> <span class="hljs-string">"5"</span>
<span class="hljs-attr">ports:</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">"127.0.0.1:9010:8080"</span>
<span class="hljs-attr">env_file:</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">.env</span>
<span class="hljs-attr">volumes:</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">./var:/srv/var</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">./templates/email_confirmation_login.html.tmpl:/srv/email_confirmation_login.html.tmpl:ro</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">./templates/email_confirmation_subscription.html.tmpl:/srv/email_confirmation_subscription.html.tmpl:ro</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">./templates/email_reply.html.tmpl:/srv/email_reply.html.tmpl:ro</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">./templates/email_unsubscribe.html.tmpl:/srv/email_unsubscribe.html.tmpl:ro</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">./templates/error_response.html.tmpl:/srv/error_response.html.tmpl:ro</span>
</code></pre>
<p>A few remarks here:</p>
<ul>
<li>I found that the "hostname" field needs to match the host that the service will answer to publicly.
This was not documented, but until I made that change, it was inaccessible.</li>
<li>The service runs on port 8080 internally.
I mapped that to a port on my localhost, which I then reverse proxy to via Caddy (more on that below).</li>
<li>There are a number of templates for the emails.
I grabbed these from the <a href="https://github.com/umputun/remark42/tree/master/backend/app/templates/static">Remark42 git repository</a> and customized them.</li>
</ul>
<p>Configuration of Remark42 when run via Docker is done via environment variables.
I configured mine to allow email authentication, as well as OAuth2 via GitHub (as a large number of my readers have GitHub accounts).
(You can also configure Google, Facebook, Apple, Microsoft, Patreon, Discord, Telegram, and Yandex.)</p>
<p>These were the values I configured; the <a href="https://remark42.com/docs/configuration/parameters/">full list of configuration values is in the documentation</a>:</p>
<pre><code class="language-bash hljs bash" data-lang="bash"><span class="hljs-comment"># This is the actual URL to the Remark42 instance</span>
REMARK_URL=
<span class="hljs-comment"># This is an identifier used to allow the instance to handle multiple sites.</span>
<span class="hljs-comment"># It can be a single value, or multiple, comma-separated values</span>
SITE=
<span class="hljs-comment"># A shared secret key for signing JWTs</span>
SECRET=
<span class="hljs-comment"># Whether or not to log debug messages</span>
DEBUG=<span class="hljs-literal">false</span>
<span class="hljs-comment"># A comma-separated list of hosts that are allowed to interact with the service</span>
ALLOWED_HOSTS=
<span class="hljs-comment"># The "Same-Site" cookie policy to use.</span>
<span class="hljs-comment"># I discovered through trial and error it needed to be "none"</span>
AUTH_SAME_SITE=none
<span class="hljs-comment"># GITHUB config</span>
<span class="hljs-comment"># If you use GitHub for OAuth2, these are the client ID and secret, respectively</span>
AUTH_GITHUB_CID=
AUTH_GITHUB_CSEC=
<span class="hljs-comment"># EMAIL SERVER CONNECTION</span>
SMTP_HOST=
SMTP_PORT=465
SMTP_TLS=<span class="hljs-literal">true</span>
SMTP_INSECURE_SKIP_VERIFY=<span class="hljs-literal">false</span>
SMTP_USERNAME=
SMTP_PASSWORD=
<span class="hljs-comment"># USER NOTIFICATION</span>
NOTIFY_USERS=email
<span class="hljs-comment"># The "From" address when sending email notifications</span>
NOTIFY_EMAIL_FROM=
<span class="hljs-comment"># The email subject line for email verifications</span>
NOTIFY_EMAIL_VERIFICATION_SUBJ=<span class="hljs-string">"mwop.net comments email verification"</span>
<span class="hljs-comment"># ADMIN NOTIFICATIONS</span>
NOTIFY_ADMINS=email
<span class="hljs-comment"># The "From" address for admin notifications</span>
NOTIFY_EMAIL_FROM=
<span class="hljs-comment"># A single email or comma-separated list of emails to which to send admin</span>
<span class="hljs-comment"># notifications</span>
ADMIN_SHARED_EMAIL=
<span class="hljs-comment"># User identifiers for admin users, to enable admin features for those users</span>
ADMIN_SHARED_ID=
<span class="hljs-comment"># Enable email authentication</span>
AUTH_EMAIL_ENABLE=<span class="hljs-literal">true</span>
<span class="hljs-comment"># The "From" address when sending email verifications</span>
AUTH_EMAIL_FROM=
<span class="hljs-comment"># The email subject line when sending email confirmation</span>
AUTH_EMAIL_SUBJ=<span class="hljs-string">"mwop.net confirmation"</span>
<span class="hljs-comment"># MISC</span>
EMOJI=<span class="hljs-literal">true</span>
</code></pre>
<blockquote>
<p>The <code>ADMIN_SHARED_ID</code> value must be setup AFTER you've authenticated a user in the system.
You can click on a user from any comment thread, which opens a sidebar.
At the top of the sidebar is the user display name, as well as their identifier, and you will copy this identifier to put in the <code>ADMIN_SHARED_ID</code> field.
When you do so, you'll need to restart the container.</p>
</blockquote>
<h3>Serving it with Caddy</h3>
<p>The Remark42 docs detail a number of different reverse proxy setups for serving it, including <a href="https://caddyserver.com">Caddy</a>... but, interestingly, only show Caddy configuration for serving it from a sub-path of an existing site.</p>
<p>My Caddy configuration for this was very minimal:</p>
<pre><code class="language-caddyfile">comments.mwop.net {
reverse_proxy localhost:9010 {
header_up X-Real-IP {remote}
header_down Strict-Transport-Security max-age=3153600
}
header {
Strict-Transport-Security max-age=3153600;
}
}
</code></pre>
<h3>Integrating with the application</h3>
<p>Once I had setup the server, I needed to integrate it in my application, and this is where things got tricky.
Why?
Because of some choices I've made along the way while developing my site:</p>
<ul>
<li>I have a pretty robust Content Security Policy, and needed to configure it to allow (a) pulling the JS for Remark42 from my comments host, and (b) allow it to display frames from it.</li>
<li>I use HTMX, and have enabled <a href="https://htmx.org/docs/#boosting">hx-boost</a>, which means I need to (a) load the Remark42 JS on every page, and (b) have some functionality for re-initializing it when a user navigates to a new page.
Further, I use Webpack to concatenate and minimize my JS, which means I'd need to ensure that this integration is done in a way that will work correctly.</li>
</ul>
<h4>Content Security Policy</h4>
<p>The values used in <code>ALLOW_HOSTS</code> are used to populate a <code>frame-ancestors</code> Content Security Policy header by the Remark42 server.</p>
<p>This is important.</p>
<p>If you are defining a Content Security Policy on your application, you'll need to ensure that you have a <code>frame-src</code> setting that allows your Remark42 server.
This is in addition to allowing it as a <code>script-src</code>:</p>
<pre><code class="language-http hljs http" data-lang="http"><span class="hljs-attribute">Content-Security-Policy</span>: script-src https://comments.mwop.net; frame-src https://comments.mwop.net
</code></pre>
<p>I use <a href="https://github.com/paragonie/csp-builder">paragonie/csp-builder</a>, and you can set these via the following:</p>
<pre><code class="language-php hljs php" data-lang="php"><span class="hljs-meta"><?php</span>
<span class="hljs-keyword">use</span> <span class="hljs-title">ParagonIE</span>\<span class="hljs-title">CSPBuilder</span>\<span class="hljs-title">CSPBuilder</span>;
$csp = <span class="hljs-keyword">new</span> CSPBuilder([
<span class="hljs-string">'frame-src'</span> => [
<span class="hljs-string">'allow'</span> => [
<span class="hljs-string">'https://comments.mwop.net'</span>,
],
],
<span class="hljs-string">'script-src'</span> => [
<span class="hljs-string">'allow'</span> => [
<span class="hljs-string">'https://comments.mwop.net'</span>,
],
],
])
</code></pre>
<p>(I clearly have more in it that these values; this is to illustrate the pieces necessary to integrate Remark42.)</p>
<h4>HTMX Integration</h4>
<p>To start, I added the following tag to the <code><head></code> element of my layout:</p>
<pre><code class="language-html hljs xml" data-lang="html"><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">defer</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://comments.mwop.net/web/embed.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
</code></pre>
<p>This ensures that the first page a user comes to on my site loads the JS, but that it's done in the background.</p>
<p>Next, I needed to make a change to my site JavaScript.</p>
<p>I use Webpack, and have the following in my site JS, among other things:</p>
<pre><code class="language-javascript hljs javascript" data-lang="javascript"><span class="hljs-built_in">window</span>.htmx = <span class="hljs-built_in">require</span>(<span class="hljs-string">"htmx.org"</span>);
</code></pre>
<p>I discovered through trial and error that you MUST define a <code>remark_config</code> variable at the global (window) level of your JS; otherwise, regardless of how you try and create a Remark42 instance, it will fail.</p>
<p>Additionally, I was going to need to (a) register a listener on the <code>REMARK42::ready</code> event so it would initialize on an initial page load, and (b) register a listener on the <code>htmx:load</code> event so that it would re-initialize after a page swap occurs.</p>
<p>The end result looks like this:</p>
<pre><code class="language-javascript hljs javascript" data-lang="javascript"><span class="hljs-keyword">const</span> remark_config = {
<span class="hljs-attr">host</span>: <span class="hljs-string">"USE SAME VALUE AS 'REMARK_URL' CONFIG HERE"</span>,
<span class="hljs-attr">site_id</span>: <span class="hljs-string">"USE A VALUE FROM 'SITE' CONFIG HERE"</span>,
<span class="hljs-attr">theme</span>: <span class="hljs-string">"dark"</span>, <span class="hljs-comment">// can be light, dark, or system</span>
<span class="hljs-attr">no_footer</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// I didn't want to include the "powered by" footer</span>
};
<span class="hljs-keyword">let</span> remark42Instance = <span class="hljs-literal">null</span>;
<span class="hljs-keyword">const</span> initComments = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.REMARK42) {
<span class="hljs-keyword">if</span> (remark42Instance) {
remark42Instance.destroy();
}
node = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'remark42'</span>);
<span class="hljs-keyword">if</span> (node === <span class="hljs-literal">null</span>) {
<span class="hljs-keyword">return</span>;
}
remark42Instance = <span class="hljs-built_in">window</span>.REMARK42.createInstance({
<span class="hljs-attr">node</span>: node,
...remark_config,
});
}
};
<span class="hljs-built_in">window</span>.remark_config = remark_config;
<span class="hljs-built_in">window</span>.htmx = <span class="hljs-built_in">require</span>(<span class="hljs-string">"htmx.org"</span>);
<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'REMARK42::ready'</span>, () => {
initComments();
});
<span class="hljs-built_in">window</span>.htmx.on(<span class="hljs-string">"htmx:load"</span>, () => {
initComments();
});
</code></pre>
<p>The <code>initComments()</code> function checks to see if a global <code>REMARK42</code> object exists; this is registered by the Remark42 JS when it loads, so if it doesn't exist, it means the JS hasn't been loaded on the page yet.
From there, it checks to see if the <code>remark42Instance</code> variable is non-null, and, if so, calls its <code>destroy()</code> method.
Next, it checks to see if a DOM element with the ID <code>remark42</code> is present; if so, it creates a new Remark42 instance, passing that node and the previously defined Remark42 configuration.</p>
<p>When the Remark42 JS emits the <code>REMARK42::ready</code> event, or HTMX emits the <code>htmx:load</code> event, I trigger the function.
This ensures it's triggered on an initial page load, and on any subsequent DOM load event triggered by HTMX.</p>
<h4>Adding comments to a page</h4>
<p>Now, to add comments to a page, I only need to add the following:</p>
<pre><code class="language-html hljs xml" data-lang="html"><span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"remark42"</span>></span><span class="hljs-tag"></<span class="hljs-name">div</span>></span>
</code></pre>
<p>And I can use any tag I want here, just so long as the ID is set.</p>
<h3>Final thoughts</h3>
<p>I'm fairly happy with this solution.</p>
<p>I didn't have to build it all myself, I keep ownership of the data instead of handing it to a third-party, my users get reasonable privacy (including the right to request deletion of all their comments), and I get tools to moderate and manage comments.</p>
<p>I'd love it if I didn't have to use JS for this, and could theme the comments myself.
That said, <a href="https://remark42.com/docs/contributing/api/">Remark42 has an API</a>, so technically I could build some site integration that delegates to it behind the scenes if I really want to.
As it is, the "dark" theme integrates reasonably well with my existing site styles, so there's no immediate need for me to do this currently.</p>
<p>Let me know what you think... comments are on, after all!</p>
<div class="h-entry">
<img class="u-photo photo" width="50" src="https://avatars0.githubusercontent.com/u/25943?v=3&u=79dd2ea1d4d8855944715d09ee4c86215027fa80&s=140" alt="matthew">
<a class="u-url u-uid p-name" href="https://mwop.net/blog/2025-01-25-comments-are-back.html">Comments are Back</a> was originally
published <time class="dt-published" datetime="2025-01-25T12:46:59-06:00">25 January 2025</time>
on <a href="https://mwop.net">https://mwop.net</a> by
<a rel="author" class="p-author" href="https://mwop.net">Matthew Weier O'Phinney</a>.
</div>
Adoption levels of recent PHP features - Exakathttps://www.exakat.io/?p=155242025-01-24T17:25:34.000Z<h1 id="toc_0">Adoption levels of recent PHP features<img fetchpriority="high" decoding="async" class="alignleft size-medium wp-image-15525" src="https://www.exakat.io/wp-content/uploads/2025/01/high-building.320-300x300.jpg" alt="" width="300" height="300" srcset="https://www.exakat.io/wp-content/uploads/2025/01/high-building.320-150x150@2x.jpg 300w, https://www.exakat.io/wp-content/uploads/2025/01/high-building.320-150x150.jpg 150w, https://www.exakat.io/wp-content/uploads/2025/01/high-building.320-100x100.jpg 100w, https://www.exakat.io/wp-content/uploads/2025/01/high-building.320.jpg 320w" sizes="(max-width: 300px) 100vw, 300px" /></h1>
<p>While browsing the <a href="https://app.daily.dev/squads/phpdev">PHP devsquad</a>, I ran into this article that listed the <a href="https://medium.com/@hiadeveloper/phps-hidden-treasures-10-powerful-features-you-didn-t-know-you-needed-2cd15b7f400b">top 10 powerful features of PHP</a>. Beside the list, it made me wonder what are the adoption levels of recent PHP features, based on this list.</p>
<p>Once in a while, I like to check such top 10, to see how main stream PHP features are becoming. After all, there is a step between what is announced at the new version release, and how features are adopted (I’m looking at you, <a href="https://www.php.net/manual/en/language.operators.comparison.php">spaceship operator</a>. And working on <a href="https://www.exakat.io/">Exakat</a> tends to keep me looking at <a href="https://php-tips.readthedocs.io/en/latest/tipSection.html">exotic</a> parts of PHP.</p>
<h2 id="toc_1">A Top 10 PHP Features</h2>
<p>Here is the list from the article. Can you find your own favorite features?</p>
<ul>
<li><a href="https://www.php.net/manual/en/language.generators.php">Generators</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.traits.php">Traits</a></li>
<li><a href="https://www.php.net/manual/en/language.oop5.anonymous.php">Anonymous Classes</a></li>
<li><a href="https://www.php.net/manual/en/migration70.new-features.php#migration70.new-features.null-coalesce-op">Null Coalesce Operator</a></li>
<li><a href="https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list">Ellipsis (Variadic, …) Operator</a></li>
<li><a href="https://www.php.net/manual/en/class.datetimeimmutable.php">DateTimeImmutable Class</a></li>
<li><a href="https://www.php.net/manual/en/class.iterator.php">Iterator</a></li>
<li><a href="https://www.php.net/manual/en/functions.arrow.php">Arrow Functions</a></li>
<li><a href="https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments">Named Arguments</a></li>
<li><a href="https://www.php.net/manual/en/control-structures.match.php">Match Expression</a></li>
</ul>
<h2 id="toc_2">PHP Features Adoption in Open Source Code</h2>
<p>Now, here is a survey that check how these features are used, in a corpus of over 3100 PHP open source projects.</p>
<ul>
<li>Generators 11,00 %</li>
<li>Traits 33,00 %</li>
<li>Anonymous Class 17,00 %</li>
<li>Coalesce 47,00 %</li>
<li>Ellipsis (…) 31,00 %</li>
<li>DateImmutable 16,00 %</li>
<li>Iterator 34,00 %</li>
<li>Arrow Functions 23,00 %</li>
<li>Named Arguments 21,00 %</li>
<li>Match 17,00 %</li>
</ul>
<p>Over one third of adoption in the code for Iterators, Ellipsis Coalesce and traits: to compare with, <code>arrays</code> are used in 66% of all projects, so that’s a good level, compared to one of the PHP star features.</p>
<p>Generators, Match, DateTimeImmutable and Anonymous Classes may need a bit more drum rolling, or more wide spread use cases. There are quite a number of tutorials to create them, but are there enough use cases blogs to help push their usage? When you have some ready, <a href="https://bsky.app/profile/dseguy.bsky.social">ping me on dseguy.bsky.social</a>, or <a href="https://app.daily.dev/squads/phpdev">on the Dev Squad</a> !</p>
<h2 id="toc_3">Personal adoption of PHP Features</h2>
<p>I reviewed the list, and, honestly, I can say I have adopted half of them. Generators, Coalesce, ellipsis, named arguments and match are common.</p>
<p>I don’t work with dates, so that leave DateTimeImmutable out. I have found little use of Traits so far. I’m happy with Closures, in place of Arrow functions, and Generator, in place of Iterators. And I name every <a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/cite.ini.html">CITE</a> in the code.</p>
<h2 id="toc_4">Limitations of the survey</h2>
<p>The figures above are extracted from analyzing 3100+ PHP open source projects. Such projects may be small or big, and bigger projects tends to use more features. Some may be more recent than others, and some keep backward compatibility very high in the priorities. They include the top 1000 packages, the most common frameworks and applications, and some lesser known.</p>
<h2 id="toc_5">PHP is modernizing, one code after the other</h2>
<p>After a few years, it is interesting to check the adoption level of released features. They become actually adopted once they found their usage in everyone’s code, as much as possible.</p>
<p>While a top 10 PHP features is probably a bit arbitrary, it is good to see the community choice getting PHP more modern.</p>
<p>The post <a href="https://www.exakat.io/adoption-levels-of-recent-php-features/">Adoption levels of recent PHP features</a> appeared first on <a href="https://www.exakat.io">Exakat</a>.</p>
Always expand the Fantastical editor popover - Rob Allenhttps://akrabat.com/?p=71772025-01-21T11:00:00.000Z<p>My preferred calendar app for the Apple ecosystem is <a href="https://flexibits.com/fantastical">Fantastical</a> as I've found that it meets my needs well. One minor irritant is that the editor popover defaults to a collapsed view and I have to expand it to see everything, in particular the notes field which I use frequently. </p>
<p>I recently discovered that there's a hidden preference to change this. It's set via a custom url of <tt>x-fantastical3://defaults?key=AlwaysShowAll&value=1&type=bool&group=1</tt>. Just click this <a href="x-fantastical3://defaults?key=AlwaysShowAll&value=1&type=bool&group=1">link to set</a> and it will open in Fantastical and change the setting.</p>
<p>There's a helpful dialog to tell you what's going to happen too:<br />
<img fetchpriority="high" decoding="async" src="https://akrabat.com/wp-content/uploads/2024/10/2024-10-24-fantastical-confimation.png" alt="" title="2024-10-24-fantastical-confimation.png" border="0" width="300" height="311" /></p>
<p>Given that the Settings panel has an Advanced tab, you'd have thought that they could have put this in there.</p>
<p>To reset, use this <a href="x-fantastical3://defaults?key=AlwaysShowAll&value=0&type=bool&group=1">link to unset</a> which sets the value to zero.</p>
Support for .env Files: Now Built into PhpStorm - PhpStorm : The IDE that empowers PHP developers | The JetBrains Bloghttps://blog.jetbrains.com/?post_type=phpstorm&p=5372372025-01-16T20:14:13.000Z
<p><code>.env</code> files are widely used to configure applications by storing configuration settings, environment variables, and sensitive information. This eliminates the need to hardcode these values into the application code.</p>
<p>Previously, developers using PhpStorm had to manually install the .env Files Support plugin, originally created by <a href="https://adelf.tech/about" target="_blank" rel="noopener">Adel Faizrakhmanov</a>, to unlock dedicated features for working with these files.</p>
<p align="center"><iframe style="border: 0;" src="https://plugins.jetbrains.com/embeddable/card/9525" width="384px" height="319px"></iframe></p>
<p>Wanting to ensure that PhpStorm users have everything they need for web development already built into the IDE, we reached out to Adel about transferring ownership of his .env Files Support plugin to JetBrains for bundling – and he graciously supported this idea. Starting with PhpStorm 2024.3.2, this plugin is included out of the box, with JetBrains taking over its development while keeping it open source. </p>
<p class="has-text-align-center"><a class="jb-download-button" href="https://www.jetbrains.com/phpstorm/download/" target="_blank" rel="noopener">Download PhpStorm</a></p>
<div class="blockquote">
<blockquote><p>“I’m pleased that it has been bundled with the IDE. I hope JetBrains implements some features I didn’t have time to, like nested variables.”</p></blockquote>
<div class="blockquote__author">
<img decoding="async" class="blockquote__author-img" src="https://blog.jetbrains.com/wp-content/uploads/2025/01/adel_photo_small.jpg" alt="Adel Faizrakhmanov">
<div class="blockquote__author-info">
<strong class="blockquote__author-title">Adel Faizrakhmanov</strong>
<span class="blockquote__author-subtitle">Software Developer</span>
</div>
</div>
</div>
<h2 class="wp-block-heading">What’s inside?</h2>
<ul>
<li>Сode completion for the environment variable keys defined in the .env file, Dockerfile, or docker-compose.yml file.<br><img decoding="async" fetchpriority="high" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdxYSBwyCdwv3WWFudFt9L5d3CvJb4yWrHgYc0Mxqzo7Sej2jUIeJVT-2YMn6vO3VmFnZ6381YhUPqyPCYrMx8gbihIxVvbJxXt27VH9jT2wwkwwNkDW9u1FjqX-Nd9ROaezTz5?key=pqJc_FESifjhJCMTedSzDh1K" width="624" height="249"></li>
</ul>
<ul>
<li>Syntax highlighting and validation inspections in .env files.<br><img decoding="async" width="624" height="251" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXf2-KJlNBmS81wKqeROPKfmKI-BJ6QzoFROM6LY-euGr8jVv5JIqgPqzKlY4v2hVmUshJ-CtxEFZS3GldB6f4qu8mbwzOhqBJg7-vevRJ0OxegdLe6tc-H1sXBy1aiOu0hQBHG-hQ?key=pqJc_FESifjhJCMTedSzDh1K"></li>
</ul>
<ul>
<li>Navigation between environment variable declarations and usages.<br><img decoding="async" loading="lazy" width="624" height="249" src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcNktRHmC-Yp1i4tz8KJUAHr1eF_q2IKyZcIlSDnioxqqCNjvn_u2QfVxC5zI8ejsRIfWUniixPcigpTWuvNkk0WOT1BlyK3ZiATUfZngTX1M0R8Z2se2QcAK7_GCwJPityECwbrg?key=pqJc_FESifjhJCMTedSzDh1K"></li>
</ul>
<h2 class="wp-block-heading">You may also like</h2>
<p>Adel is widely known for the <a href="https://laravel-idea.com/" target="_blank" rel="noopener">Laravel Idea plugin</a>, which offers extensive support for Laravel development, including Laravel code generation, Eloquent completion, Laravel navigation, as well as autocompletion for Laravel routes, request fields, and validation rules.</p>
<div class="blockquote">
<blockquote><p>“.env Files Support was the first plugin I developed. Its sudden popularity inspired me to pay more attention to IntelliJ Platform plugin development, and now I’m working full time on Laravel Idea and other plugins.”</p></blockquote>
<div class="blockquote__author">
<img decoding="async" class="blockquote__author-img" src="https://blog.jetbrains.com/wp-content/uploads/2025/01/adel_photo_small.jpg" alt="Adel Faizrakhmanov">
<div class="blockquote__author-info">
<strong class="blockquote__author-title">Adel Faizrakhmanov</strong>
<span class="blockquote__author-subtitle">Software Developer</span>
</div>
</div>
</div>
<p align="center"><iframe loading="lazy" style="border: 0;" src="https://plugins.jetbrains.com/embeddable/card/13441" width="384px" height="319px"></iframe></p>
<p>If you’re a Laravel developer and haven’t tried this plugin yet, give it a shot!</p>
<h2 class="wp-block-heading">Conclusion</h2>
<p>PhpStorm continues to refine and expand its features to support PHP developers better. By bundling .env file support into the core IDE, JetBrains has removed yet another barrier to productivity.</p>
<p>If you haven’t updated PhpStorm yet, now’s the time to do it! </p>
<p class="has-text-align-center"><a class="jb-download-button" href="https://www.jetbrains.com/phpstorm/download/" target="_blank" rel="noopener">Download PhpStorm</a></p>
Fixing issues with Yubico's PAM U2F bindings in version 1.3.1 - Blog entries :: mwop.nethttps://mwop.net/blog/2025-01-15-pam-yubikey-1.3.1-fix.html2025-01-15T20:31:20.000Z<p>I've been using a <a href="https://www.yubico.com/products/yubikey-5-overview/">Yubikey</a> for years, now, and on each computer I use, I install their U2F (Universal 2 Factor) bindings for the linux Pluggable Authentication Modules (PAM) support, requiring usage of my Yubikey for login and sudo access.</p>
<p>Today, I updated my work machine, and didn't even notice that there were new pamu2fcfg and libpam-u2f packages, updating to version 1.3.1; I never really care, as everything just works. But when I came back to my machine after lunch, I was unable to login: I'd provide my password, but my Yubikey wouldn't activate.</p>
<p>I tested it on my personal machine, and everything was working fine. I tried pressing the key on my work machine, when in the password field, and it pasted in the OTP code, so clearly there's no USB issue.</p>
<p>So, after booting my rescue USB drive and disabling the U2F support, I (a) discovered that I'd had updates for the PAM U2F support earlier, and (b) searched for the phrase "yubikey pam u2f 1.3.1 breaks", which took me to <a href="https://github.com/Yubico/pam-u2f/issues/330">this report</a>.</p>
<p>The gist?</p>
<p><strong>Due to a CVE, the PAM U2F bindings now require that the <code>u2f_keys</code> file is writeable only by the owner.</strong></p>
<p>This can be accomplished pretty easily:</p>
<pre><code class="language-bash hljs bash" data-lang="bash"><span class="hljs-comment"># If you have systemwide keys:</span>
sudo chmod g-w,o-w /etc/yubico/u2f_keys
<span class="hljs-comment"># If you have per-user keys:</span>
chmod g-w,o-w <span class="hljs-variable">$HOME</span>/.config/Yubico/u2f_keys
</code></pre>
<p>Once I did that, I re-enabled my PAM U2F bindings, rebooted, and all worked fine again.</p>
<h2>Final thoughts</h2>
<p>I rarely think about permissions in my <code>$HOME/.config</code> directory, but I'm well aware that configuration for things like SSH and GPG require similar permissions masks. I think it's great that Yubico is doing this, but (a) it should have likely been like this all along, and (b) they really should have provided some sort of tooling or messaging with the update to help folks fix permissions issues before they become a problem. The fact that I only found out when I was unable to login to my machine was horrible, and I feel incredibly fortunate and privileged that I (a) had a rescue USB drive handy, and (b) the knowledge of what I needed to do to disable U2F so I could access my machine. Not all their users will be in that position.</p>
<div class="h-entry">
<img class="u-photo photo" width="50" src="https://avatars0.githubusercontent.com/u/25943?v=3&u=79dd2ea1d4d8855944715d09ee4c86215027fa80&s=140" alt="matthew">
<a class="u-url u-uid p-name" href="https://mwop.net/blog/2025-01-15-pam-yubikey-1.3.1-fix.html">Fixing issues with Yubico's PAM U2F bindings in version 1.3.1</a> was originally
published <time class="dt-published" datetime="2025-01-15T14:31:20-06:00">15 January 2025</time>
on <a href="https://mwop.net">https://mwop.net</a> by
<a rel="author" class="p-author" href="https://mwop.net">Matthew Weier O'Phinney</a>.
</div>
Global git ignore patterns - Rob Allenhttps://akrabat.com/?p=72202025-01-14T11:00:00.000Z<p>One thing that I've found helpful is to add a set of patterns to my global <a href="https://git-scm.com/docs/gitignore"><tt>git ignore</tt></a> file (<tt>config/git/ignore</tt> for me) that allow me to create temporary files that are automatically excluded from git.</p>
<p>The patterns I use are these:</p>
<pre>
# Ignore a file by renaming it with ignore its name
*.ignore
ignore.*
*.ignore.*
</pre>
<p>This lets me create a file with a prefix <tt>ignore.</tt>, a postfix of <tt>.ignore</tt> or add <tt>.ignore.</tt> somewhere in the middle of the filename. This is particular useful for adding before the extension that's used for syntax detection in an editor, such as with <tt>.yaml</tt> files.</p>
<p>It's a small thing, but so convenient when you want to keep a local file around for reference, but never accidentally commit it.</p>
Enabling a focus mode when an app is running on Mac - Rob Allenhttps://akrabat.com/?p=71962025-01-07T11:00:00.000Z<p>When I'm on a Zoom or FaceTime call, I want stop all notifications on my Mac so that I'm not distracted by them and would like this automated.</p>
<p>It's not easy to tell when a call is happening, so I simplified the problem to stopping all notifications if the Zoom or FaceTime is running as I only run these apps if I'm on call.</p>
<p>To do this, I created two Shortcuts to turn the <em>Do Not Disturb</em> focus mode on and off: </p>
<p><img fetchpriority="high" decoding="async" src="https://akrabat.com/wp-content/uploads/2024/11/shortcut-dnd-on.png" alt="Shortcut dnd on." title="shortcut-dnd-on.png" border="0" width="450" height="120" /></p>
<p><img decoding="async" src="https://akrabat.com/wp-content/uploads/2024/11/shortcut-dnd-off.png" alt="Shortcut dnd off." title="shortcut-dnd-off.png" border="0" width="450" height="118" /></p>
<p>I then created two Keyboard Maestro macros:</p>
<p><em>Video conferencing on</em>:<br />
<img decoding="async" src="https://akrabat.com/wp-content/uploads/2024/11/video-conferencing-on.png" alt="Video conferencing on." title="video-conferencing-on.png" border="0" width="499" height="423" /></p>
<p><em>Video conferencing off</em>:<br />
<img loading="lazy" decoding="async" src="https://akrabat.com/wp-content/uploads/2024/11/video-conferencing-off.png" alt="Video conferencing off." title="video-conferencing-off.png" border="0" width="499" height="426" /></p>
<p>These look for the launching and quitting of the relevant apps and executes the shortcut. There's a minor issue that if I close one app if both apps are running, then DND is turned off, but I don't leave these apps open, so I'm not worried about it. If I was, I'd add additional logic or separate macros.</p>
<p>Goal achieved! Whenever one of these apps are running (which is only when I'm on a call), the focus mode is enabled for me and is turned off again when I quit the app.</p>
PHP Constructors and Inheritance - Exakathttps://www.exakat.io/?p=153882025-01-06T15:36:30.000Z<h2 id="toc_0"><a href="https://www.exakat.io/wp-content/uploads/2025/01/shortline.320.jpg"><img decoding="async" class="alignleft wp-image-15389 size-medium" src="https://www.exakat.io/wp-content/uploads/2025/01/shortline.320-300x300.jpg" alt="PHP Constructors and Inheritance" width="300" height="300" srcset="https://www.exakat.io/wp-content/uploads/2025/01/shortline.320-150x150@2x.jpg 300w, https://www.exakat.io/wp-content/uploads/2025/01/shortline.320-150x150.jpg 150w, https://www.exakat.io/wp-content/uploads/2025/01/shortline.320-100x100.jpg 100w, https://www.exakat.io/wp-content/uploads/2025/01/shortline.320.jpg 320w" sizes="(max-width: 300px) 100vw, 300px" /></a>PHP Constructors and Inheritance</h2>
<p>When working in OOP PHP, understanding constructors is essential for manipulating objects. Constructors are magic methods that get invoked when an object is instantiated from a class. They typically handle the initialization of an object’s properties. These initialisations are split between parent and child classes, and the relationship between parent and child class constructors can be somewhat confusing. There is much to say about PHP constructors and inheritance.</p>
<p>In this post, we’ll explore PHP constructors in the context of inheritance and what it means when a child constructor explicitly calls the parent constructor.</p>
<h2 id="toc_1">What Is a Constructor in PHP?</h2>
<p>In PHP, a constructor is a magic function that is automatically executed when a new instance of a class is created. It is used to initialize the object’s properties or perform setup tasks that are required at the time of object creation.</p>
<p>The constructor in PHP is defined with the __construct method. Here’s a basic example:</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class X {
private $property;
public function __construct($argument) {
$this->property = $argument;
echo __METHOD__." was called\n";
}
}
$object = new X(1);
?>
</pre>
</div>
<p>In this example, when the new object X is instantiated, PHP automatically invokes the <code>__construct()</code> method to initialize the properties of the car object.</p>
<h2 id="toc_2">What Happens with Inheritance?</h2>
<p>When one class extends another in PHP, the child class inherits all public and protected methods from the parent class, including the constructor. However, parent constructors in PHP do not get called automatically, but in one case: when the child class doesn’t define a constructor, PHP calls the parent constructor automatically.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class X {
private $property;
public function __construct($argument) {
$this->property = $argument;
echo __METHOD__." was called\n";
}
}
class Y extends X {}
$object = new Y(1);
// X::__construct was called
?>
</pre>
</div>
<p>This is where things get interesting: when the child class defines its own constructor, it does not automatically inherit the parent constructor. Instead, the child class has full control over the construction process, and the parent constructor will only be called if the child explicitly calls it.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class X {
private $property;
public function __construct($argument) {
$this->property = $argument;
echo __METHOD__." was called\n";
}
}
class Y extends X {
public function __construct($argument) {
echo __METHOD__." was called\n";
}
}
$object = new Y(1);
// Y::__construct was called
?>
</pre>
</div>
<p>In this example, the <code>Y</code> class does not call call the parent class constructor, and only the local constructor is called. The parent class is not initialized at all, in particular the property <code>$property</code>.</p>
<p>To do so, the child constructor must call the parent constructor explicitly. Heuristically, the parent must be called first, so as to ensure all needed is set up, before the child object can use specific features. Yet, when the parent and the child are well decoupled, that doesn’t impact much the constructor.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class X {
private $property;
public function __construct($argument) {
$this->property = $argument;
echo __METHOD__." was called\n";
}
}
class Y extends X {
public function __construct($argument) {
parent::__construct($arguments);
echo __METHOD__." was called\n";
}
}
$object = new Y(1);
// X::__construct was called
// Y::__construct was called
?>
</pre>
</div>
<h2 id="toc_3">Fatal Error: Cannot Call Constructor</h2>
<p>From that situation, it is easy to conclude that calling the parent constructor is a duty for the child: even if it has no impact, it is safer to relay the call to the parent.</p>
<p>But PHP now needs a concrete method to call, and constructors are not compulsory.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class X {
private $p = 2;
// no constructor
}
class Y extends X {
private $property;
public function __construct($argument) {
parent::__construct();
$this->property = $argument;
echo __METHOD__." was called\n";
}
}
$object = new Y(1);
//Fatal error: Uncaught Error: Cannot call constructor
?>
</pre>
</div>
<p>In this scenario, PHP throws a fatal error: <code>Cannot call constructor</code>. This is quite confusing, at first, since there is actually a constructor in <code>Y</code>. This happens because the Y class defines a constructor, but the parent class <code>X</code> doesn’t. When PHP tries to find the parent constructor, it doesn’t exist.</p>
<p>That problem is not the case when there is a grand-parent class, with an explicit constructor. Any existing parent constructor will prevent the Fatal error.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class W {
public function __construct($argument) {
echo __METHOD__." was called\n";
}
}
class X extends W {
// no constructor
}
class Y extends X {
private $property;
public function __construct($argument) {
parent::__construct();
$this->property = $argument;
echo __METHOD__." was called\n";
}
}
$object = new Y(1);
// W::__construct was called
// Y::__construct was called
?>
</pre>
</div>
<h2 id="toc_4">Must call the parent constructor</h2>
<p>Interestingly, some PHP native classes exhibit a behavior where they throw a LogicException if a child class doesn’t explicitly call the parent constructor.</p>
<p>Consider the <a href="https://www.php.net/manual/en/class.splfileobject.php">SplFileObject</a> class, which is used to provide a OOP syntax to files. When extended, it requires a proper setup of the file path by the parent class. When <code>SplFileObject</code> is extended and lacks the call to the parent constructor, a <code>LogicException</code> is thrown at execution time:</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class MyFile extends \SplFileObject
{
public function __construct()
{
}
public function fgets(): string
{
// It actually doesn't use the file
return "MyFile";
}
}
$cl = new MyFile();
try {
$cl->fgets();
} catch (\Error $e) {
var_dump($e);
}
?>
</pre>
</div>
<p>This native behavior ensures that the necessary setup is always performed correctly.</p>
<h2 id="toc_5">The Difficulty of Managing Parent Constructors</h2>
<p>While PHP’s constructor inheritance model is straightforward, it introduces subtle problems in more complex scenarios. One of the primary issues is determining whether the parent class even has a constructor to call.</p>
<p>It is difficult to setup a coding convention without prior knowledge of the parent hierarchy (the family?):</p>
<ul>
<li>always call the parent constructor, and end up coding <a href="https://exakat.readthedocs.io/en/latest/Reference/Rules/Functions/EmptyFunction.html">empty constructors</a></li>
<li>only call the parent constuctor when needed, and forget one that <a href="https://exakat.readthedocs.io/en/latest/Reference/Rules/Php/MustCallParentConstructor.html">need it</a>
<ul>
<li>Static code analysis may help with that.</li>
</ul>
</li>
</ul>
<p>Furthermore, when dealing with frameworks where there is no full control over the parent class, for example, extending a third-party class, it may be hard to know when to call the parent constructor. This introduces a certain level of uncertainty into the design, potentially leading to bugs that are identified at execution time only.</p>
<p>And, finally, not calling the parent is also a valid strategy when creating mocks or unplugging existing code. Cases are rare to short-circuit the parent constructor, but do exist.</p>
<h2 id="toc_6">A Potential Solution: Default Empty Constructor</h2>
<p>An idea to alleviate this issue would be to have PHP provide an empty constructor by default, similar to how it works with properties. In cases where a child class explicitly calls a parent class that does not define a constructor, PHP could automatically provide an empty constructor that does nothing. With inheritance, the parent __construct() may be in a grand-parent, though it is already handled that way today.</p>
<p>This way, a child class could still call parent::__construct() without worrying about whether or not the parent class has a constructor.</p>
<p>Here’s an illustration:</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class X {
// no constructor
}
class Y extends X {
private $property;
public function __construct($argument) {
parent::__construct(); // does nothing
$this->property = $argument;
echo __METHOD__." was called\n";
}
}
$object = new Y(1);
// Y::__construct was called
?>
</pre>
</div>
<p>In this case, PHP would simply use an empty constructor in <code>X</code> if it wasn’t explicitly defined, thus preventing errors and simplifying the inheritance model.</p>
<h2 id="toc_7">Conclusion</h2>
<p>PHP’s handling of constructors in the context of inheritance can be tricky. When a child class overrides a parent constructor, it must explicitly call the parent’s constructor if any. Otherwise, a fatal error or unexpected behavior may arise. Additionally, some PHP classes enforce constructor calls through exceptions, adding another layer of complexity.</p>
<p>In more complex applications or frameworks, the uncertainty of whether to call a parent constructor—especially when dealing with third-party code—can introduce extra code burden. A default empty constructor mechanism could potentially make this process smoother, but for now, developers need to be diligent in understanding when and why to invoke a parent constructor.</p>
<p>The post <a href="https://www.exakat.io/php-constructors-and-inheritance/">PHP Constructors and Inheritance</a> appeared first on <a href="https://www.exakat.io">Exakat</a>.</p>
2024 in pictures - Rob Allenhttps://akrabat.com/?p=72352024-12-31T14:00:36.000Z<p>As usual, at the end of the year, I look back over the photos I have taken and think about the year. This year I have published 1162 photos to Flickr. It has been a good year, seeing friends and family, taking photos, attending conferences all coupled with enjoyable work for a good client.</p>
<p>I am pleased to have kept up with taking a least one photo every day as part of my <a href="https://project365.akrabat.com/2024.html">Project 365</a>, which allows me to remember what happened during this year of my life and reflect on the year as <a href="https://akrabat.com/category/year-in-pictures/">I usually do</a>. A montage of all my Project 365 photos is at the top of the page.</p>
<h2>January</h2>
<p>We started this year celebrating mum's 80th birthday. I also got out to the <a href="https://www.flickr.com/photos/akrabat/albums/72177720313898921">Severn Valley Railway</a> to see 43016 on its last day in traffic before overhaul. Overhauls take a while, so it'll be many years before it is in steam again. Away from the computer, I worked on a model railway which is very very slowly coming together and also continued our tradition of meeting long distance friends for lunch at a pub half-way between our towns.</p>
<p><a title="View 'Mum's 80th celebration' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53447976657"><img fetchpriority="high" decoding="async" height="213" style="float:left;" border="0" alt="Mum's 80th celebration." width="320" src="https://live.staticflickr.com/65535/53447976657_064d89bbdb_n.jpg" /></a><a title="View '43106's last day in traffic before overhaul' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53451025580"><img decoding="async" height="213" style="float:left;margin-left: 3px;" border="0" alt="43106's last day in traffic before overhaul" width="320" src="https://live.staticflickr.com/65535/53451025580_735a19a312_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">February</h2>
<p>I attended OpenUK's <a href="https://www.flickr.com/photos/akrabat/albums/72177720314659865/">State of Open Con</a> again this year where I had the surprise of seeing <a href="https://www.flickr.com/photos/akrabat/53516376547">Liz</a>! It was so good to catch up with her and many other amazing people. I spoke at <a href="https://www.flickr.com/photos/akrabat/albums/72177720314862292/">PHPUK</a> the following month, also at The Brewery, catching up with more people and, later in February, finished <em>Tears of the Kingdom</em>.</p>
<p><a title="View 'State of Open Con 24' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53517695565"><img decoding="async" height="213" style="float:left;" border="0" alt="State of Open Con 24." width="320" src="https://live.staticflickr.com/65535/53517695565_9fb4510740_n.jpg" /></a></a><a title="View 'PHPUK social after the first day' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53536632814"><img loading="lazy" decoding="async" height="213" style="float:left;margin-left: 3px;" border="0" alt="PHPUK social after the first day." width="320" src="https://live.staticflickr.com/65535/53536632814_728e88692f_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">March</h2>
<p>March saw the first <a href="https://www.flickr.com/photos/akrabat/albums/72177720315509488/">Dutch PHP Conference</a> since the pandemic. It's under new management and part of AppDevCon now. I also got to the <a href="https://www.flickr.com/photos/akrabat/albums/72177720315625712">KVWR's steam gala</a> this year, again enjoying Kevin's company while there.</p>
<p><a title="View 'Derick & James' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53594047740"><img loading="lazy" decoding="async" height="213" style="float:left;" border="0" alt="Derick & James." width="320" src="https://live.staticflickr.com/65535/53594047740_8110db0a73_n.jpg" /></a><a title="View '75078 on a goods train' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53605178927"><img loading="lazy" decoding="async" height="213" style="float:left;margin-left: 3px;" border="0" alt="75078 on a goods train." width="320" src="https://live.staticflickr.com/65535/53605178927_25cedf1842_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">April</h2>
<p>In April, I did a fair bit of railway modelling and also went to <a href="https://www.flickr.com/photos/akrabat/albums/72177720316692205/">php[tek]</a> for the first time in many years. I also met up with my colleagues Andrés and Sarah along with catching up with so many other people that I've not seen in a while. There was a <a href="https://wurstcon.com/faq/">WorstCon</a> too! After the conference, a few of us went into Chicago to see the sights and ended up at a rooftop bar; not a bad way to end a trip to America!</p>
<p><a title="View 'Ben tells the origin story of WurstCon' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53698861540"><img loading="lazy" decoding="async" height="213" style="float:left;" border="0" alt="Ben tells the origin story of WurstCon." width="320" src="https://live.staticflickr.com/65535/53698861540_53697718f0_n.jpg" /></a><a title="View 'Drinks on a roof top bar in Chicago with friends' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53698829594"><img loading="lazy" decoding="async" height="213" style="float:left;margin-left: 3px;" border="0" alt="Drinks on a roof top bar in Chicago with friends." width="320" src="https://live.staticflickr.com/65535/53698829594_8128b2edcf_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">May</h2>
<p>May is birthday month. Both kids came home in the weekend between their birthdays which was lovely and I laying the track on my model railway layout. My birthday is a little later in May and I was at <a href="https://www.flickr.com/photos/akrabat/albums/72177720317073355/">phpday</a>, which was quite wet when I walked around Verona with Kat and Rick. This month we retired my dad's layout as it's been over 2 years not since he was last able to model and operate it. As a result, I am now the custodian of some of his models and have put them in a display cabinet in in my office. I finished the month camping at <a href="https://www.flickr.com/photos/akrabat/albums/72177720317585589/">EMFCamp</a> which was fantastic.</p>
<p><a title="View 'Display cabinet for my dad's locomotives' on Flickr.com" href="https://www.flickr.com/photos/akrabat/54234140758"><img loading="lazy" decoding="async" height="213" style="float:left;" border="0" alt="Display cabinet for my dad's locomotives." width="320" src="https://live.staticflickr.com/65535/54234140758_a9db705264_n.jpg" /></a><a title="View 'Chatting with friends' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53767301951"><img loading="lazy" decoding="async" height="213" style="float:left;margin-left: 3px;" border="0" alt="Chatting with friends." width="320" src="https://live.staticflickr.com/65535/53767301951_732e020270_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">June</h2>
<p>In June, I went photographing in Shropshire at <a href="https://www.flickr.com/photos/akrabat/albums/72177720322836080">Ironbridge with Stuart</a>. Later in the month, I spoke at <a href="https://www.flickr.com/photos/akrabat/albums/72177720318167850">Laravel Live UK</a>, a very good conference, though I was ill afterwards with Covid. We finished the month with a trip to my sisters where I snagged a pic of our kids with their cousins.</p>
<p><a title="View 'Photographing' on Flickr.com" href="https://www.flickr.com/photos/akrabat/54233917485"><img loading="lazy" decoding="async" height="213" style="float:left;" border="0" alt="" width="320" src="https://live.staticflickr.com/65535/54233917485_b864f259bd_n.jpg" /></a><a title="View 'Cousins' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53825224683"><img loading="lazy" decoding="async" height="213" style="float:left;margin-left: 3px;" border="0" alt="" width="320" src="https://live.staticflickr.com/65535/53825224683_e1f0a92477_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">July</h2>
<p>July saw a trip to Croatia for <a href="https://www.flickr.com/photos/akrabat/albums/72177720318710785/">Web Summer Camp</a>. I picked up an Apple Vision Pro which I very much like. The wider-family picnic was this month too, where I snagged a selfie with dad.</p>
<p><a title="View 'Hallway track in the sunshine' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53850659683"><img loading="lazy" decoding="async" height="240" style="float:left;" border="0" alt="Hallway track in the sunshine." width="320" src="https://live.staticflickr.com/65535/53850659683_91379b5e7a_n.jpg" /></a><a title="View 'Selfie with dad!' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53858150292"><img loading="lazy" decoding="async" height="240" style="float:left;margin-left: 3px;" border="0" alt="Selfie with dad!." width="320" src="https://live.staticflickr.com/65535/53858150292_0f7fe12cb3_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">August</h2>
<p>With no conferences in August, I visited the <a href="https://www.flickr.com/photos/akrabat/albums/72177720319615015">North Yorkshire Moors railway</a> for their gala which was excellent. As always when I visit the NYMR, I visited friends and my sister which were also highlights of the trip. We discovered that we had failed our youngest as he hadn't seen <em>Life of Brian</em>, so we corrected that! I also took some photos over at the <a href="https://www.flickr.com/photos/akrabat/albums/72177720319976219">SVR on the last day of the month</a>.</p>
<p><a title="View 'North Yorkshire Moors Railway' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53931867973"><img loading="lazy" decoding="async" height="213" style="float:left;" border="0" alt="North Yorkshire Moors Railway." width="320" src="https://live.staticflickr.com/65535/53931867973_645634d83b_n.jpg" /></a><a title="View 'Bahamas approaching Crossing Cottage' on Flickr.com" href="https://www.flickr.com/photos/akrabat/53962830188"><img loading="lazy" decoding="async" height="213" style="float:left;margin-left: 3px;" border="0" alt="Bahamas approaching Crossing Cottage." width="320" src="https://live.staticflickr.com/65535/53962830188_34ea3156b2_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">September</h2>
<p>In September, I we had a close family meet-up, which included my aunt and cousin from America. My cousins kids really enjoy taking photos with my camera, so I bought the X-T3 with me as well as my X-H2 and they had an excellent time, taking some really nice photos of the day. Later in the month, I attended <a href="https://www.flickr.com/photos/akrabat/albums/72177720320549778">apidays London</a>, including a AYWH meet-up.</p>
<p><img loading="lazy" decoding="async" height="213" style="float:left;" border="0" alt="Taking photos." width="320" src="https://live.staticflickr.com/65535/53984875178_8acb8799c3_n.jpg" /><a title="View 'apidays London' on Flickr.com" href="https://www.flickr.com/photos/akrabat/54015069487"><img loading="lazy" decoding="async" height="213" style="float:left;margin-left: 3px;" border="0" alt="Apidays London." width="320" src="https://live.staticflickr.com/65535/54015069487_8f1c9f86a7_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">October</h2>
<p>Our holiday to Spain this year was at the end of September to early October. I then went to Stockholm for the <a href="https://www.flickr.com/photos/akrabat/albums/72177720321263136">Nordic APIs Platform Summit</a> and then to Manchester for <a href="https://www.flickr.com/photos/akrabat/albums/72177720321267742">OggCamp</a>! Later in the month, I toured St. Pancras station with the Midland Railway Society.</p>
<p><a title="View 'A panel closed the conference' on Flickr.com" href="https://www.flickr.com/photos/akrabat/54072081408"><img loading="lazy" decoding="async" height="240" style="float:left;" border="0" alt="A panel closed the conference." width="320" src="https://live.staticflickr.com/65535/54072081408_0c2c8dfe07_n.jpg" /></a><a title="View 'OggCamp' on Flickr.com" href="https://www.flickr.com/photos/akrabat/54072239968"><img loading="lazy" decoding="async" height="240" style="float:left;margin-left: 3px;" border="0" alt="" width="320" src="https://live.staticflickr.com/65535/54072239968_6129476400_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">November</h2>
<p>In November, <a href="https://www.flickr.com/photos/akrabat/albums/72177720321863876/">we went to Lisbon</a> for a long weekend. Lisbon is a lovely city and we really enjoyed pastel de nata! Stuart and I photographed in the <a href="https://www.flickr.com/photos/akrabat/albums/72177720321866290">Forest of Dean</a> and I spoke at PHP Sussex in Brighton. Later in the month, I travelled north in snowy weather to celebrate my sister's 50th where her husband had arranged a surprise party for her.</p>
<p><a title="View 'Funicular' on Flickr.com" href="https://www.flickr.com/photos/akrabat/54132299167"><img loading="lazy" decoding="async" height="213" style="float:left;" border="0" alt="" width="320" src="https://live.staticflickr.com/65535/54132299167_fc29cf481c_n.jpg" /></a><a title="View 'Jen's surprise party!' on Flickr.com" href="https://www.flickr.com/photos/akrabat/54162997271"><img loading="lazy" decoding="async" height="213" style="float:left;margin-left: 3px;" border="0" alt="Jen's surprise party!." width="320" src="https://live.staticflickr.com/65535/54162997271_fb767322ba_n.jpg" /></a></p>
<h2 style="clear: both; margin-top: 20px; padding-top: 20px;">December</h2>
<p>I visited Vienna in December to speak at my first <a href="https://www.flickr.com/photos/akrabat/albums/72177720322430202/">SymfonyCon</a> which was a lot of fun. Shoutout to the Liip folks for inviting meet join them on a walking tour of Vienna the day before the conference. We had a great Christmas where I very much enjoyed making many mince pies. At the very end of the the month, I got a final trip to the SVR in and took photos at the <a href="https://www.flickr.com/photos/akrabat/albums/72177720322861891">Winter Diesel Day</a>.</p>
<p><a title="View 'Core Team Q&A' on Flickr.com" href="https://www.flickr.com/photos/akrabat/54190824254"><img loading="lazy" decoding="async" height="213" style="float:left;" border="0" alt="Core Team Q&A." width="320" src="https://live.staticflickr.com/65535/54190824254_4d43e07e37_n.jpg" /></a><a title="View 'DSCF9919' on Flickr.com" href="https://www.flickr.com/photos/akrabat/54236418619"><img loading="lazy" decoding="async" height="213" style="float:left;margin-left: 3px;" border="0" alt="" width="320" src="https://live.staticflickr.com/65535/54236418619_cff9047105_n.jpg" /></a></p>
<p style="clear: both; margin-top: 3em; padding-top: 3em">
Looking back on 2024, this year was characterised by many conferences and other tech events, with not quite enough railway photography. 2025, will no doubt be another fascinating year and I will document it with my photos!</p>
Bitwise and Logical Operators in PHP - Exakathttps://www.exakat.io/?p=153702024-12-29T21:36:15.000Z<h2 id="toc_0"><a href="https://www.exakat.io/wp-content/uploads/2024/12/framboises.320.jpg"><img decoding="async" class="alignleft size-medium wp-image-15371" src="https://www.exakat.io/wp-content/uploads/2024/12/framboises.320-300x300.jpg" alt="Bitwise and logical operators" width="300" height="300" srcset="https://www.exakat.io/wp-content/uploads/2024/12/framboises.320-150x150@2x.jpg 300w, https://www.exakat.io/wp-content/uploads/2024/12/framboises.320-150x150.jpg 150w, https://www.exakat.io/wp-content/uploads/2024/12/framboises.320-100x100.jpg 100w, https://www.exakat.io/wp-content/uploads/2024/12/framboises.320.jpg 320w" sizes="(max-width: 300px) 100vw, 300px" /></a>Difference Between Bitwise and Logical Operators in PHP</h2>
<p>The distinction between <a href="https://www.php.net/manual/en/language.operators.bitwise.php">bitwise operators</a> and <a href="https://www.php.net/manual/en/language.operators.logical.php">logical operators</a> operators is often overlooked and confused. They are often used one for another: it is true they have some overlapping domains of application, and yet, their functionality differ significantly. Here’s a breakdown of how these operators work:</p>
<h3 id="toc_1">Logical Operators: Booleans at Play</h3>
<p>Logical operators in PHP operate on boolean values and return boolean results. For example:</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
var_dump(true && true); // Evaluates to true
?>
</pre>
</div>
<p>These operators are often represented using integers, specifically 0 and 1, where 0 is equivalent to false and 1 is equivalent to true. Consider the following example:</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
var_dump(1 || 0); // Evaluates to true
?>
</pre>
</div>
<p>Under the hood, PHP casts any value to a boolean before processing it with a logical operator. This ensures that logical operations are consistently boolean-focused.</p>
<h3 id="toc_2">Bitwise Operators: Working with Integers</h3>
<p>Bitwise operators, on the other hand, operate directly on integers. They treat integers as binary representations and perform logical operations, bit by bit. Hence, the name of bitwise operators. For instance:</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
var_dump(0 & 3); // Evaluates to 0, not false
var_dump(5 & 3); // Evaluates to 1
?>
</pre>
</div>
<p>Here’s what happens, in binary notation, in the second example:</p>
<div>
<pre><code class="language-none"> 101 (5 in binary)
& 011 (3 in binary)
---
001 (1 in binary)</code></pre>
</div>
<p>Each bit in the integer is evaluated using the corresponding logical operation, and the result is placed in the corresponding position of the resulting integer. Finally, this result is returned as an integer.</p>
<h3 id="toc_3">The Systemic Perspective</h3>
<p>From a broader perspective, you can think of bitwise operators as logical operators applied to multiple values simultaneously. For example, an 8-bit integer could be viewed as eight boolean values, with bitwise operators processing all bits in parallel.</p>
<h3 id="toc_4">Overlap Between Bitwise and Logical Operators</h3>
<p>While logical and bitwise operators serve different purposes, there is some functional overlap. Notably:</p>
<ul>
<li><code>!$a</code>: Negates the value of <code>$a</code>. For example, if <code>$a</code> is true or 0, <code>!$a</code> is false.</li>
<li><code>~$a</code>: Negates all the bits in $a. For instance, if <code>$a</code> is 0, <code>~$a</code> is -1, and not 1, although both are loosely equal to true. if <code>$a</code> is 1, <code>~$a</code> is -2, and not false.</li>
</ul>
<p>Then, their behavior diverges when $a is not strictly 0 or 1, making it essential to understand their respective contexts.</p>
<h2 id="toc_5">Conclusion</h2>
<p><a href="https://www.freecodecamp.org/news/logical-operators-in-php/">Logical operators</a> are works well for boolean logic, in particular for any true/false conditions. <a href="https://flatcoding.com/tutorials/php-programming/understanding-the-php-bitwise-operators/">Bitwise operators</a>, on the other hand, offer a powerful way to manipulate integers at the binary level, making them invaluable for tasks that involve low-level data processing.</p>
<p>The post <a href="https://www.exakat.io/bitwise-and-logical-operators-in-php/">Bitwise and Logical Operators in PHP</a> appeared first on <a href="https://www.exakat.io">Exakat</a>.</p>
Developing rst2pdf with uv - Rob Allenhttps://akrabat.com/?p=72042024-12-24T11:00:00.000Z<p>Thanks to <a href="https://github.com/kyluca">Kyle</a> and <a href="https://github.com/lornajane">Lorna</a>, we've moved <a href="https://rst2pdf.org">rst2pdf</a>'s development out of the dark ages of setup.py and into <a href="https://docs.astral.sh/uv/">uv</a> with <a href="https://packaging.python.org/en/latest/guides/writing-pyproject-toml/">pyproject.toml</a>.</p>
<p>As a result, I've changed the way I develop rst2pdf locally; these are my initial notes.</p>
<h3>Set up Python environment</h3>
<p>Given a clone of the <a href="https://github.com/rst2pdf/rst2pdf">rst2pdf git repository</a>, do this get going:</p>
<pre>
$ uv sync --all-extras
</pre>
<p>This will create a virtual environment in <tt>.venv</tt> with all dependencies installed. It will also install Python if required.</p>
<h3>Setup pre-commit</h3>
<pre>
$ uv tool install pre-commit
$ pre-commit install --allow-missing-config
This installs <a href="https://pre-commit.com">pre-commit</a> globally so it can be executed from any terminal window.
</pre>
<h3>Changing Python version</h3>
<p>If you want to use a different Python version, say 3.12:</p>
<pre>
$ echo 3.12 > .python-version
$ uv sync --all-extras
</pre>
<p><tt>uv</tt> will install the correct Python version and ensure that the virtual environment is correct</p>
<h3>Operations while developing</h3>
<p>Run tests:</p>
<pre>
$ uv run pytest -n auto
</pre>
<p>Run one test:</p>
<pre>
$ uv run pytest -k test_issue_255
</pre>
<p>Run: pre-commit checks:</p>
<pre>
$ pre-commit run --all-files --show-diff-on-failure
</pre>
<h3>Activate the visual environment </h3>
<p>If you want to run <tt>pytest</tt> or <tt>rst2pdf</tt> locally, then we can activate the Virtual environment as usual: </p>
<pre>. .venv/bin/activate</pre>
<p>Now we can run whatever's installed within the virtual environment directly which is convenient.</p>
<p>Use <tt>deactivate</tt> to turn it off.</p>
<h3>That's it</h3>
<p>That's all I'm using so far and it seems to be working as well as my previous pyenv system.</p>
A Weekly Priority List in Logseq Journal View - Blog entries :: mwop.nethttps://mwop.net/blog/2024-12-17-logseq-journal-priority-list.html2024-12-17T15:01:32.000Z<p>I've been using Logseq for a few years now, and even <a href="/blog/2023-12-01-advent-logseq.html">blogged about it last year</a>.
I appreciate how it surfaces todo items on the upcoming day(s), as well as anything I've given a due date in the coming week.
That said, one exercise I engage each week is a weekly prioritization for the upcoming week, where I note down the items most important for me to complete.
While many of these might have due dates, some might be aspirational or more along the lines of things I can do when I have a few minutes of down time between meetings.</p>
<p>For these, I generally keep a page in Logseq for the given week, named something like "2024w50", and I've been adding that into my "Favorites" list (and taking out the previous week's page!) so that it's easy to navigate to.
I also keep my weekly notes here; I have to do weekly reports for each brand, so having a dedicated location I can either tag in other notes or write them in directly is useful.</p>
<p>While it's easy to open this page into a sidebar, I generally do not want a sidebar open, as I find it distracting.
So I've been mulling over alternatives that would allow me to continue seeing my priorities easily, while giving me the same ability to group them by week.</p>
<h2>My solution</h2>
<p>The solution ended up being quite simple.</p>
<p>I'm now creating a structure like the following on that weekly page:</p>
<pre><code class="language-markdown hljs markdown" data-lang="markdown"><span class="hljs-section">## Priorities</span>
priorities:: 20241220
<span class="hljs-bullet">
- </span>First item in the list
<span class="hljs-bullet">- </span>Second item in the list
<span class="hljs-bullet">- </span>and so on
</code></pre>
<p>In my Logseq <code>config.edn</code>, I've added the following into my <code>:default-queries { :journals: }</code> section:</p>
<pre><code class="language-clojure hljs clojure" data-lang="clojure">#+BEGIN_QUERY
{
<span class="hljs-symbol">:title</span> <span class="hljs-string">"PRIORITIES"</span>
<span class="hljs-symbol">:inputs</span> [<span class="hljs-symbol">:today</span> <span class="hljs-symbol">:+1w</span>]
<span class="hljs-symbol">:query</span> [
<span class="hljs-symbol">:find</span> (<span class="hljs-name">pull</span> ?b [*])
<span class="hljs-symbol">:in</span> $ ?start ?end
<span class="hljs-symbol">:where</span>
[?b <span class="hljs-symbol">:block/properties</span> ?properties]
[(<span class="hljs-name"><span class="hljs-builtin-name">get</span></span> ?properties <span class="hljs-symbol">:priorities</span>) ?priorities]
[(<span class="hljs-name"><span class="hljs-builtin-name">>=</span></span> ?priorities ?start)]
[(<span class="hljs-name"><span class="hljs-builtin-name"><</span></span> ?priorities ?end)]
]
}
#+END_QUERY
</code></pre>
<p>This adds a block named "PRIORITIES" to the current journal, pulling all blocks marked with the tag "priorities" where the date is in the future.
Since I do my planning only a week in advance, this ensures I only see one such block on any given day, and ensures its in front of me each day as I start the journal.</p>
<div class="h-entry">
<img class="u-photo photo" width="50" src="https://avatars0.githubusercontent.com/u/25943?v=3&u=79dd2ea1d4d8855944715d09ee4c86215027fa80&s=140" alt="matthew">
<a class="u-url u-uid p-name" href="https://mwop.net/blog/2024-12-17-logseq-journal-priority-list.html">A Weekly Priority List in Logseq Journal View</a> was originally
published <time class="dt-published" datetime="2024-12-17T09:01:32-06:00">17 December 2024</time>
on <a href="https://mwop.net">https://mwop.net</a> by
<a rel="author" class="p-author" href="https://mwop.net">Matthew Weier O'Phinney</a>.
</div>
Sunrise and sunset times on the Mac command line - Rob Allenhttps://akrabat.com/?p=72232024-12-17T11:00:00.000Z<p>I recently discovered the <tt>/usr/libexec/corebrightnessdiag</tt> command line tool on macOS.</p>
<p>In particular, <tt>/usr/libexec/corebrightnessdiag nightshift-internal</tt> will give information about when the Mac's nightshift settings, including when sunrise and sunset are!</p>
<pre>
$ /usr/libexec/corebrightnessdiag nightshift-internal
Night Shift Status
{
AutoBlueReductionEnabled = 1;
BlueLightReductionSchedule = {
DayStartHour = 7;
DayStartMinute = 0;
NightStartHour = 22;
NightStartMinute = 0;
};
BlueReductionAvailable = 1;
BlueReductionEnabled = 0;
BlueReductionMode = 0;
BlueReductionSunScheduleAllowed = 1;
Version = 1;
}
Night Shift Sunset/Sunrise
{
isDaylight = 1;
nextSunrise = "2024-12-17 08:20:24 +0000";
nextSunset = "2024-12-17 15:49:34 +0000";
previousSunrise = "2024-12-15 08:18:51 +0000";
previousSunset = "2024-12-15 15:49:08 +0000";
sunrise = "2024-12-16 08:19:39 +0000";
sunset = "2024-12-16 15:49:19 +0000";
}
</pre>
<p>Hence, we can easily output the today's sunrise and sunset times with:</p>
<pre>/usr/libexec/corebrightnessdiag nightshift-internal | grep -E "sunrise|sunset" | awk '{print $1, $4}'</pre>
<p>This gives an output of:</p>
<pre>
sunrise 08:19:39
sunset 15:49:19
</pre>
<p>Useful!</p>
The Top 100 PHP functions in 2024 - Exakathttps://www.exakat.io/?p=153572024-12-16T21:54:59.000Z<h1 id="toc_0"><a href="https://www.exakat.io/wp-content/uploads/2018/05/keys.320.jpg"><img loading="lazy" decoding="async" class="alignleft wp-image-4255 size-medium" src="https://www.exakat.io/wp-content/uploads/2018/05/keys.320-300x300.jpg" alt="The 100 PHP functions to unlock the maximum power from the language" width="300" height="300" srcset="https://www.exakat.io/wp-content/uploads/2018/05/keys.320-150x150@2x.jpg 300w, https://www.exakat.io/wp-content/uploads/2018/05/keys.320-100x100.jpg 100w, https://www.exakat.io/wp-content/uploads/2018/05/keys.320-150x150.jpg 150w, https://www.exakat.io/wp-content/uploads/2018/05/keys.320.jpg 320w, https://www.exakat.io/wp-content/uploads/2018/05/keys.320-100x100@2x.jpg 200w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a>The 100 PHP functions that you have to know</h1>
<p>Here is the top 100 PHP functions : it is the list of the most often used PHP native functions.</p>
<p>The functions are named, and ranked from 1 to 100. The other 4500 functions are not ranked here. The frequency column represents how often this function is used across PHP code repositories : the reference corpus is a list of 2500 PHP Open Source projects (top 1000 composer, github/gitlab/gitee public repo, downloaded archives…). They were audited with <a href="https://www.exakat.io/">Exakat static analysis engine</a>, version 2.4.7. The average is the number of time the function is called within one project.</p>
<p>For example, 4 project out of 5 uses the <a href="https://php.net/count">count()</a> function, and when used, <a href="https://php.net/count">count()</a> is called around 61 times. It is both a popular and heavily used function. On the other hand, <a href="http://php.net/dirname">dirname()</a> is popular but rarely used (56% for 13 usages)</p>
<p>Click on the link to go to the documentation. Some insight at the bottom of the top 100.</p>
<p> </p>
<h2 id="toc-1">The 100 most popular PHP functions</h2>
<table>
<tbody>
<tr>
<td>1</td>
<td><a href="https://www.php.net/manual/en/function.file-exists.php">file_exists</a></td>
<td>66.05%</td>
<td>18</td>
</tr>
<tr>
<td>2</td>
<td><a href="https://www.php.net/manual/en/function.count.php">count</a></td>
<td>63.67%</td>
<td>87</td>
</tr>
<tr>
<td>3</td>
<td><a href="https://www.php.net/manual/en/function.str-replace.php">str_replace</a></td>
<td>63.58%</td>
<td>51</td>
</tr>
<tr>
<td>4</td>
<td><a href="https://www.php.net/manual/en/function.implode.php">implode</a></td>
<td>62.72%</td>
<td>48</td>
</tr>
<tr>
<td>5</td>
<td><a href="https://www.php.net/manual/en/function.substr.php">substr</a></td>
<td>60.30%</td>
<td>70</td>
</tr>
<tr>
<td>6</td>
<td><a href="https://www.php.net/manual/en/function.array-merge.php">array_merge</a></td>
<td>60.02%</td>
<td>51</td>
</tr>
<tr>
<td>7</td>
<td><a href="https://www.php.net/manual/en/function.sprintf.php">sprintf</a></td>
<td>59.89%</td>
<td>103</td>
</tr>
<tr>
<td>8</td>
<td><a href="https://www.php.net/manual/en/function.dirname.php">dirname</a></td>
<td>58.91%</td>
<td>17</td>
</tr>
<tr>
<td>9</td>
<td><a href="https://www.php.net/manual/en/function.explode.php">explode</a></td>
<td>58.30%</td>
<td>37</td>
</tr>
<tr>
<td>10</td>
<td><a href="https://www.php.net/manual/en/function.in-array.php">in_array</a></td>
<td>58.27%</td>
<td>49</td>
</tr>
<tr>
<td>11</td>
<td><a href="https://www.php.net/manual/en/function.strpos.php">strpos</a></td>
<td>58.08%</td>
<td>31</td>
</tr>
<tr>
<td>12</td>
<td><a href="https://www.php.net/manual/en/function.is-array.php">is_array</a></td>
<td>58.05%</td>
<td>73</td>
</tr>
<tr>
<td>13</td>
<td><a href="https://www.php.net/manual/en/function.strlen.php">strlen</a></td>
<td>56.37%</td>
<td>45</td>
</tr>
<tr>
<td>14</td>
<td><a href="https://www.php.net/manual/en/function.array-key-exists.php">array_key_exists</a></td>
<td>54.87%</td>
<td>88</td>
</tr>
<tr>
<td>15</td>
<td><a href="https://www.php.net/manual/en/function.array-keys.php">array_keys</a></td>
<td>54.72%</td>
<td>23</td>
</tr>
<tr>
<td>16</td>
<td><a href="https://www.php.net/manual/en/function.preg-match.php">preg_match</a></td>
<td>52.02%</td>
<td>40</td>
</tr>
<tr>
<td>17</td>
<td><a href="https://www.php.net/manual/en/function.file-get-contents.php">file_get_contents</a></td>
<td>51.76%</td>
<td>11</td>
</tr>
<tr>
<td>18</td>
<td><a href="https://www.php.net/manual/en/function.trim.php">trim</a></td>
<td>48.49%</td>
<td>37</td>
</tr>
<tr>
<td>19</td>
<td><a href="https://www.php.net/manual/en/function.is-string.php">is_string</a></td>
<td>48.46%</td>
<td>27</td>
</tr>
<tr>
<td>20</td>
<td><a href="https://www.php.net/manual/en/function.array-values.php">array_values</a></td>
<td>48.46%</td>
<td>10</td>
</tr>
<tr>
<td>21</td>
<td><a href="https://www.php.net/manual/en/function.is-file.php">is_file</a></td>
<td>46.90%</td>
<td>7</td>
</tr>
<tr>
<td>22</td>
<td><a href="https://www.php.net/manual/en/function.method-exists.php">method_exists</a></td>
<td>46.62%</td>
<td>11</td>
</tr>
<tr>
<td>23</td>
<td><a href="https://www.php.net/manual/en/function.array-map.php">array_map</a></td>
<td>46.11%</td>
<td>17</td>
</tr>
<tr>
<td>24</td>
<td><a href="https://www.php.net/manual/en/function.file-put-contents.php">file_put_contents</a></td>
<td>46.11%</td>
<td>7</td>
</tr>
<tr>
<td>25</td>
<td><a href="https://www.php.net/manual/en/function.strtolower.php">strtolower</a></td>
<td>45.44%</td>
<td>26</td>
</tr>
<tr>
<td>26</td>
<td><a href="https://www.php.net/manual/en/function.function-exists.php">function_exists</a></td>
<td>44.97%</td>
<td>21</td>
</tr>
<tr>
<td>27</td>
<td><a href="https://www.php.net/manual/en/function.preg-replace.php">preg_replace</a></td>
<td>44.40%</td>
<td>23</td>
</tr>
<tr>
<td>28</td>
<td><a href="https://www.php.net/manual/en/function.defined.php">defined</a></td>
<td>43.44%</td>
<td>27</td>
</tr>
<tr>
<td>29</td>
<td><a href="https://www.php.net/manual/en/function.is-dir.php">is_dir</a></td>
<td>41.70%</td>
<td>8</td>
</tr>
<tr>
<td>30</td>
<td><a href="https://www.php.net/manual/en/function.json-decode.php">json_decode</a></td>
<td>41.54%</td>
<td>10</td>
</tr>
<tr>
<td>31</td>
<td><a href="https://www.php.net/manual/en/function.json-encode.php">json_encode</a></td>
<td>41.16%</td>
<td>15</td>
</tr>
<tr>
<td>32</td>
<td><a href="https://www.php.net/manual/en/function.call-user-func.php">call_user_func</a></td>
<td>41.06%</td>
<td>5</td>
</tr>
<tr>
<td>33</td>
<td><a href="https://www.php.net/manual/en/function.array-filter.php">array_filter</a></td>
<td>41.00%</td>
<td>10</td>
</tr>
<tr>
<td>34</td>
<td><a href="https://www.php.net/manual/en/function.strtr.php">strtr</a></td>
<td>40.55%</td>
<td>6</td>
</tr>
<tr>
<td>35</td>
<td><a href="https://www.php.net/manual/en/function.fwrite.php">fwrite</a></td>
<td>40.08%</td>
<td>7</td>
</tr>
<tr>
<td>36</td>
<td><a href="https://www.php.net/manual/en/function.rtrim.php">rtrim</a></td>
<td>39.85%</td>
<td>8</td>
</tr>
<tr>
<td>37</td>
<td><a href="https://www.php.net/manual/en/function.strrpos.php">strrpos</a></td>
<td>39.54%</td>
<td>4</td>
</tr>
<tr>
<td>38</td>
<td><a href="https://www.php.net/manual/en/function.call-user-func-array.php">call_user_func_array</a></td>
<td>39.41%</td>
<td>4</td>
</tr>
<tr>
<td>39</td>
<td><a href="https://www.php.net/manual/en/function.filter-var.php">filter_var</a></td>
<td>39.25%</td>
<td>3</td>
</tr>
<tr>
<td>40</td>
<td><a href="https://www.php.net/manual/en/function.class-exists.php">class_exists</a></td>
<td>39.22%</td>
<td>17</td>
</tr>
<tr>
<td>41</td>
<td><a href="https://www.php.net/manual/en/function.header.php">header</a></td>
<td>38.71%</td>
<td>11</td>
</tr>
<tr>
<td>42</td>
<td><a href="https://www.php.net/manual/en/function.array-flip.php">array_flip</a></td>
<td>38.62%</td>
<td>3</td>
</tr>
<tr>
<td>43</td>
<td><a href="https://www.php.net/manual/en/function.realpath.php">realpath</a></td>
<td>38.11%</td>
<td>6</td>
</tr>
<tr>
<td>44</td>
<td><a href="https://www.php.net/manual/en/function.ini-get.php">ini_get</a></td>
<td>38.01%</td>
<td>6</td>
</tr>
<tr>
<td>45</td>
<td><a href="https://www.php.net/manual/en/function.get-class.php">get_class</a></td>
<td>36.01%</td>
<td>14</td>
</tr>
<tr>
<td>46</td>
<td><a href="https://www.php.net/manual/en/function.is-object.php">is_object</a></td>
<td>35.03%</td>
<td>15</td>
</tr>
<tr>
<td>47</td>
<td><a href="https://www.php.net/manual/en/function.unlink.php">unlink</a></td>
<td>34.96%</td>
<td>8</td>
</tr>
<tr>
<td>48</td>
<td><a href="https://www.php.net/manual/en/function.trigger-error.php">trigger_error</a></td>
<td>34.84%</td>
<td>8</td>
</tr>
<tr>
<td>49</td>
<td><a href="https://www.php.net/manual/en/function.array-shift.php">array_shift</a></td>
<td>34.71%</td>
<td>8</td>
</tr>
<tr>
<td>50</td>
<td><a href="https://www.php.net/manual/en/function.is-int.php">is_int</a></td>
<td>33.76%</td>
<td>7</td>
</tr>
<tr>
<td>51</td>
<td><a href="https://www.php.net/manual/en/function.spl-autoload-register.php">spl_autoload_register</a></td>
<td>33.57%</td>
<td>1</td>
</tr>
<tr>
<td>52</td>
<td><a href="https://www.php.net/manual/en/function.is-numeric.php">is_numeric</a></td>
<td>32.93%</td>
<td>13</td>
</tr>
<tr>
<td>53</td>
<td><a href="https://www.php.net/manual/en/function.strtoupper.php">strtoupper</a></td>
<td>32.93%</td>
<td>9</td>
</tr>
<tr>
<td>54</td>
<td><a href="https://www.php.net/manual/en/function.mkdir.php">mkdir</a></td>
<td>32.93%</td>
<td>4</td>
</tr>
<tr>
<td>55</td>
<td><a href="https://www.php.net/manual/en/function.array-pop.php">array_pop</a></td>
<td>32.52%</td>
<td>7</td>
</tr>
<tr>
<td>56</td>
<td><a href="https://www.php.net/manual/en/function.ltrim.php">ltrim</a></td>
<td>31.95%</td>
<td>5</td>
</tr>
<tr>
<td>57</td>
<td><a href="https://www.php.net/manual/en/function.headers-sent.php">headers_sent</a></td>
<td>31.95%</td>
<td>2</td>
</tr>
<tr>
<td>58</td>
<td><a href="https://www.php.net/manual/en/function.fopen.php">fopen</a></td>
<td>31.88%</td>
<td>8</td>
</tr>
<tr>
<td>59</td>
<td><a href="https://www.php.net/manual/en/function.array-unique.php">array_unique</a></td>
<td>31.79%</td>
<td>6</td>
</tr>
<tr>
<td>60</td>
<td><a href="https://www.php.net/manual/en/function.str-repeat.php">str_repeat</a></td>
<td>31.47%</td>
<td>9</td>
</tr>
<tr>
<td>61</td>
<td><a href="https://www.php.net/manual/en/function.basename.php">basename</a></td>
<td>31.25%</td>
<td>6</td>
</tr>
<tr>
<td>62</td>
<td><a href="https://www.php.net/manual/en/function.array-slice.php">array_slice</a></td>
<td>30.39%</td>
<td>5</td>
</tr>
<tr>
<td>63</td>
<td><a href="https://www.php.net/manual/en/function.fclose.php">fclose</a></td>
<td>29.91%</td>
<td>7</td>
</tr>
<tr>
<td>64</td>
<td><a href="https://www.php.net/manual/en/function.time.php">time</a></td>
<td>29.47%</td>
<td>16</td>
</tr>
<tr>
<td>65</td>
<td><a href="https://www.php.net/manual/en/function.is-callable.php">is_callable</a></td>
<td>29.37%</td>
<td>5</td>
</tr>
<tr>
<td>66</td>
<td><a href="https://www.php.net/manual/en/function.preg-match-all.php">preg_match_all</a></td>
<td>29.28%</td>
<td>4</td>
</tr>
<tr>
<td>67</td>
<td><a href="https://www.php.net/manual/en/function.is-bool.php">is_bool</a></td>
<td>29.15%</td>
<td>3</td>
</tr>
<tr>
<td>68</td>
<td><a href="https://www.php.net/manual/en/function.microtime.php">microtime</a></td>
<td>28.74%</td>
<td>5</td>
</tr>
<tr>
<td>69</td>
<td><a href="https://www.php.net/manual/en/function.spl-autoload-unregister.php">spl_autoload_unregister</a></td>
<td>28.71%</td>
<td>1</td>
</tr>
<tr>
<td>70</td>
<td><a href="https://www.php.net/manual/en/function.var-export.php">var_export</a></td>
<td>28.61%</td>
<td>4</td>
</tr>
<tr>
<td>71</td>
<td><a href="https://www.php.net/manual/en/function.array-unshift.php">array_unshift</a></td>
<td>28.58%</td>
<td>3</td>
</tr>
<tr>
<td>72</td>
<td><a href="https://www.php.net/manual/en/function.max.php">max</a></td>
<td>28.42%</td>
<td>7</td>
</tr>
<tr>
<td>73</td>
<td><a href="https://www.php.net/manual/en/function.array-search.php">array_search</a></td>
<td>28.29%</td>
<td>4</td>
</tr>
<tr>
<td>74</td>
<td><a href="https://www.php.net/manual/en/function.apcu-fetch.php">apcu_fetch</a></td>
<td>28.29%</td>
<td>0</td>
</tr>
<tr>
<td>75</td>
<td><a href="https://www.php.net/manual/en/function.extension-loaded.php">extension_loaded</a></td>
<td>28.20%</td>
<td>4</td>
</tr>
<tr>
<td>76</td>
<td><a href="https://www.php.net/manual/en/function.getcwd.php">getcwd</a></td>
<td>28.17%</td>
<td>2</td>
</tr>
<tr>
<td>77</td>
<td><a href="https://www.php.net/manual/en/function.preg-split.php">preg_split</a></td>
<td>28.14%</td>
<td>4</td>
</tr>
<tr>
<td>78</td>
<td><a href="https://www.php.net/manual/en/function.reset.php">reset</a></td>
<td>28.10%</td>
<td>6</td>
</tr>
<tr>
<td>79</td>
<td><a href="https://www.php.net/manual/en/function.end.php">end</a></td>
<td>28.04%</td>
<td>4</td>
</tr>
<tr>
<td>80</td>
<td><a href="https://www.php.net/manual/en/function.gettype.php">gettype</a></td>
<td>28.04%</td>
<td>4</td>
</tr>
<tr>
<td>81</td>
<td><a href="https://www.php.net/manual/en/function.stream-resolve-include-path.php">stream_resolve_include_path</a></td>
<td>27.85%</td>
<td>0</td>
</tr>
<tr>
<td>82</td>
<td><a href="https://www.php.net/manual/en/function.md5.php">md5</a></td>
<td>27.72%</td>
<td>6</td>
</tr>
<tr>
<td>83</td>
<td><a href="https://www.php.net/manual/en/function.getenv.php">getenv</a></td>
<td>27.66%</td>
<td>6</td>
</tr>
<tr>
<td>84</td>
<td><a href="https://www.php.net/manual/en/function.apcu-add.php">apcu_add</a></td>
<td>27.41%</td>
<td>0</td>
</tr>
<tr>
<td>85</td>
<td><a href="https://www.php.net/manual/en/function.ucfirst.php">ucfirst</a></td>
<td>27.34%</td>
<td>7</td>
</tr>
<tr>
<td>86</td>
<td><a href="https://www.php.net/manual/en/function.ksort.php">ksort</a></td>
<td>27.31%</td>
<td>3</td>
</tr>
<tr>
<td>87</td>
<td><a href="https://www.php.net/manual/en/function.date.php">date</a></td>
<td>27.18%</td>
<td>14</td>
</tr>
<tr>
<td>88</td>
<td><a href="https://www.php.net/manual/en/function.is-null.php">is_null</a></td>
<td>26.90%</td>
<td>27</td>
</tr>
<tr>
<td>89</td>
<td><a href="https://www.php.net/manual/en/function.parse-url.php">parse_url</a></td>
<td>26.61%</td>
<td>3</td>
</tr>
<tr>
<td>90</td>
<td><a href="https://www.php.net/manual/en/function.preg-quote.php">preg_quote</a></td>
<td>26.61%</td>
<td>4</td>
</tr>
<tr>
<td>91</td>
<td><a href="https://www.php.net/manual/en/function.array-reverse.php">array_reverse</a></td>
<td>26.58%</td>
<td>3</td>
</tr>
<tr>
<td>92</td>
<td><a href="https://www.php.net/manual/en/function.array-diff.php">array_diff</a></td>
<td>26.45%</td>
<td>4</td>
</tr>
<tr>
<td>93</td>
<td><a href="https://www.php.net/manual/en/function.base64-encode.php">base64_encode</a></td>
<td>26.14%</td>
<td>4</td>
</tr>
<tr>
<td>94</td>
<td><a href="https://www.php.net/manual/en/function.version-compare.php">version_compare</a></td>
<td>26.04%</td>
<td>4</td>
</tr>
<tr>
<td>95</td>
<td><a href="https://www.php.net/manual/en/function.preg-replace-callback.php">preg_replace_callback</a></td>
<td>25.91%</td>
<td>4</td>
</tr>
<tr>
<td>96</td>
<td><a href="https://www.php.net/manual/en/function.current.php">current</a></td>
<td>25.25%</td>
<td>3</td>
</tr>
<tr>
<td>97</td>
<td><a href="https://www.php.net/manual/en/function.round.php">round</a></td>
<td>25.25%</td>
<td>7</td>
</tr>
<tr>
<td>98</td>
<td><a href="https://www.php.net/manual/en/function.min.php">min</a></td>
<td>25.15%</td>
<td>4</td>
</tr>
<tr>
<td>99</td>
<td><a href="https://www.php.net/manual/en/function.serialize.php">serialize</a></td>
<td>25.02%</td>
<td>6</td>
</tr>
</tbody>
</table>
<h2 id="toc-1">Top 100 insights</h2>
<ul>
<li>The most commonly used PHP functions are <a href="https://www.php.net/manual/en/language.types.string.php">string</a> functions, and then <a href="https://www.php.net/manual/en/language.types.array.php">arrays</a>, then <a href="https://www.php.net/manual/en/ref.filesystem.php">files</a>.
<ul>
<li>Math functions are probably out of the ranking, as they are mostly based on operators</li>
<li>Databases functions (pdo, mysqli*, pg_*…) are probably out of scope, as they are based on classes or components</li>
</ul>
</li>
<li>No recently deprecated function is in the top 100.</li>
<li>Usage of <a href="https://getcomposer.org/">composer</a> may also reduce the need for some native function, by federating them in a component, and reducing their overall usage. For example, <a href="https://github.com/Seldaek/monolog">monolog</a> may use <a href="https://php.net/log">log</a>() and reduce its usage across the Open Source community.</li>
<li>Extension that makes it to the top 100 : <a href="https://www.php.net/manual/en/book.filter.php">filter</a>, <a href="https://www.php.net/manual/json/book.filter.php" class="broken_link">json</a>, <a href="https://www.php.net/manual/apcu/book.filter.php" class="broken_link">apcu</a>.</li>
<li>json_encode() and json_decode() are now used with the same exact frequency, or almost.</li>
<li><code>md5</code> is the only widely used crypto function. hash() is the next, in 159th position (not shown). This must change!</li>
<li>Debug functions, such as print_r() or var_dump() are out of the top 100. var_export() is a more ambiguous case.</li>
<li><code>array</code>, <code>echo</code>, <code>print</code>, <code>empty</code>, <code>isset</code> and other language constructs were not counted as functions in this ranking. They are probably trusting the first ranks anyway.</li>
<li>Several functions could and should be replaced by operators : <code>is_object()</code>, call_user_func(), call_user_func_array()</li>
<li>max() is more often than min()</li>
<li>Types are still a major concern of PHP code : is it a string? is it an object? No, it’s a resource!
<ul>
<li>Type system may go a long way, but there are still return values that needs testing.</li>
</ul>
</li>
<li>There is also a <a href="https://www.exakat.io/en/top-100-php-classes-that-you-should-know/">PHP top 100 for the PHP classes</a>. Well,</li>
</ul>
<h2>Challenges</h2>
<p>If you are learning PHP, it is a good idea to review the 100 functions ranked here : they are the features you’ll find the most often when landing on a coding team. They are not the only ones, but you’ll be less surprised when meeting them.</p>
<h2>More details?</h2>
<p>Follow us on <a href="https://bsky.app/profile/dseguy.bsky.social">BlueSky</a> or <a href="https://phpc.social/@dseguy">mastodon</a> if you want more details, more stats</p>
<p>The post <a href="https://www.exakat.io/the-top-100-php-functions-in-2024/">The Top 100 PHP functions in 2024</a> appeared first on <a href="https://www.exakat.io">Exakat</a>.</p>
How to call a method in PHP - Exakathttps://www.exakat.io/?p=153462024-12-11T13:26:45.000Z<h2 id="toc_0"><a href="https://www.exakat.io/wp-content/uploads/2024/12/phones.320.jpg"><img loading="lazy" decoding="async" class="size-medium wp-image-15347 alignleft" src="https://www.exakat.io/wp-content/uploads/2024/12/phones.320-300x300.jpg" alt="How to call a method in PHP" width="300" height="300" srcset="https://www.exakat.io/wp-content/uploads/2024/12/phones.320-150x150@2x.jpg 300w, https://www.exakat.io/wp-content/uploads/2024/12/phones.320-150x150.jpg 150w, https://www.exakat.io/wp-content/uploads/2024/12/phones.320-100x100.jpg 100w, https://www.exakat.io/wp-content/uploads/2024/12/phones.320.jpg 320w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a>All the ways to call a method in PHP</h2>
<p>There are many ways to call a method in PHP. Method calling is a very common task, and, although the most common syntax is the overwhelming majority, there are some niche syntaxes for specific usages. And some good old syntaxes too.</p>
<p>Since they are scattered across the manual, we have gathered them here. All of them.</p>
<h3 id="toc_1">The function classic way</h3>
<p>Hardcoded, directly in the source. This is the syntax that everyone learn first.</p>
<p>Note that the function name may be aliased, or imported from another namespace with the <code>use</code>expression. The alias only affects the call of the function, and not the function itself.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
use function foo as goo;
function foo($a = 1) { echo __FUNCTION__;}
foo(); // foo
goo(); // foo (!!)
?>
</pre>
</div>
<h3 id="toc_2">With a dynamic name</h3>
<p>A dynamic name means that the function’s name is stored in a string.</p>
<p>The string is usually stored in a variable or similar, and it is used in place of the function name: PHP reads the string in the variable, and the correct function is called.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
namespace A\B\C;
function foo($a = 1) { echo __FUNCTION__;}
$functionName = 'A\B\C\foo';
$functionName(); // works
$functionName = __NAMESPACE__.'\foo';
$functionName(); // works
$functionName = 'foo';
$functionName(); // fails
?>
</pre>
</div>
<p>Always use the fully qualified name in the string, as local names are not solved. This usually leads to surprises when inside a namespace.</p>
<p>As this must be done even for locally defined functions, <code>__NAMESPACE__</code> comes to the rescue.</p>
<h3 id="toc_3">With a string</h3>
<p>The previous entry has one edge case: it is possible to use a string as the name of the function and call it directly: indeed, there is no need to use a variable.</p>
<p>Compared to the classic way, the string looses the aliasing features, provided by the <code>use</code>expression.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
function foo($a = 1) { echo __FUNCTION__;}
'foo'();
$x = 'oo';
"f$x"();
?>
</pre>
</div>
<p>On the other hand, it is also possible to use the double quotes strings, and put some variables in it. This is a dynamic syntax.</p>
<h3 id="toc_4">With a closure</h3>
<p>Instead of calling the function by referencing its absolute name, PHP creates closures from any function or method. Since PHP 8.2, the <code>...</code> operator, used as only argument in the function, returns a closure that represents the function. Then, that closure may be used to actually call the original function.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
function foo($a = 1) { echo __FUNCTION__;}
$f = foo(...);
$f();
foo(...)(...)(...)();
?>
</pre>
</div>
<p>Also, for fun, there can be many closures creation before, finally, calling it. This is useless syntax, but rather funny.</p>
<h3 id="toc_5">With a call to <code>call_user_func()</code></h3>
<p>Why not call a function by calling … another function? Use the native <code>call_user_func</code> or <code>call_user_func_array</code> to call any function or method. The first parameter is the name of the function, and the same rules we have already seen apply : make the name of the function fully qualified.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
function foo($a = 1) { echo __FUNCTION__;}
call_user_func('\foo');
// convenient for dynamic list of array
call_user_func_array('\foo', []);
?>
</pre>
</div>
<p><code>call_user_func</code> and <code>call_user_func_array</code> are useful when both the function and the parameters are dynamic.</p>
<h3 id="toc_6">Calling methods with the array syntax</h3>
<p>The previous entry is also useful to call methods. Except that methods don’t have the same string syntax, so we need to introduce the array syntax.</p>
<p>Compared to calling a function, a method requires two parts: the object or the class, and the method name. And, of course, the parameters.</p>
<p>PHP features a special syntax, based on arrays: to call a method on an object, create an array of two elements, with the object and the method name. Then, call it like that.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class x {
function foo($a = 1) { echo __METHOD__;}
}
$x = new x;
$call = [$x, 'foo'];
$call();
[$x, 'foo']();
?>
</pre>
</div>
<p>Like for the strings, it is possible to call directly on the literal array, as long as it contains the correct information.</p>
<h3 id="toc_7">Calling methods with the array syntax and the relative class names</h3>
<p>The relative class names are <code>self</code>, <code>static</code> and <code>parent</code>. They are not class names per se, as it is not allowed to create classes with such names. But they to represent actual classes, which vary from definition to definition.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class x {
static function foo($a = 1) { echo __METHOD__;}
}
class y extends x {
function bar() {
[parent::class, 'foo']();
}
}
$y = new y;
$y->bar();
?>
</pre>
</div>
<p>The relative class names are collected at definition time. When the array is moved around the program, it will always refer to the method that was targeted initially, not at the point of execution.</p>
<p>Also, note that PHP doesn’t allow anymore the usage of any of the relative classnames in strings, since PHP 8.2. The code below produces a string error message: <code>Class "static" not found</code></p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class x {
static function foo($a = 1) { echo __METHOD__;}
}
class y extends x {
function bar() {
"static::foo"();
}
}
$y = new y;
$y->bar();
?>
</pre>
</div>
<h3 id="toc_8">Calling static methods</h3>
<p>The previous syntax is also available for static methods: then, the first element of the array may be an object or the name of the class. PHP picks up anything that is useful to make the call succeed.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class x {
static function foo($a = 1) { echo __METHOD__;}
}
$x = new x;
[$x, 'foo']();
[x::class, 'foo']();
?>
</pre>
</div>
<h3 id="toc_9">Calling methods with a string</h3>
<p>Calling methods with a string is possible, as long as the method is static. Then, the string should be written as <code>class::method</code>, and target a static method.</p>
<p>That approach is not possible with objects, which are not compatible with a string.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class x {
static function foo($a = 1) { echo __METHOD__;}
}
'x::foo'();
'X::FOO'();
?>
</pre>
</div>
<h3 id="toc_10">Calling the anonymous method</h3>
<p>One more way to call a method is to call it directly from an object.</p>
<p>That is the purpose of the <code>__invoke</code> method: it is a magic method, which is called automatically when the object itself is used as a function name. Like this:</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class x {
function __invoke($a = 1) { echo __METHOD__;}
}
$x = new x;
$x();
?>
</pre>
</div>
<p>Note that <code>__invoke</code> is not anonymous, as … it has a name. On the other hand, calling <code>$object()</code> means that there is no need for the name of the method to be called, so this is anonymous.</p>
<h3 id="toc_11">The Reflection call</h3>
<p>The <a href="https://www.php.net/manual/en/book.reflection.php">reflection API</a> allows for PHP code introspection, reading descriptive features of any defined structures. It may be lesser known that such structures are still executable, and this applies to the functions and methods.</p>
<p>In the case of methods and functions, the <a href="https://www.php.net/manual/en/class.reflectionfunction.php">reflectionFunction</a> class has an <a href="https://www.php.net/manual/en/reflectionfunction.invoke.php">invoke()</a> method and a <a href="https://www.php.net/manual/en/reflectionfunction.invoke.php">invokeArgs()</a> method.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
function foo() { echo __FUNCTION__; }
$foo = new ReflectionFunction('foo');
$foo->invoke();
?>
</pre>
</div>
<p>Thanks to <a href="https://bsky.app/profile/b-viguier.bsky.social">Benoit Viguier</a> for the reminder.</p>
<h3 id="toc_12">The evergreen eval() call</h3>
<p>One always have remember old PHP features, and eval() is definitely one of them. Since eval() requires some PHP code in a string, it is another way to call a method or a function or anything really.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
function foo() { echo __FUNCTION__; }
$code = "foo();";
eval($code);
?>
</pre>
</div>
<p>Thanks to <a href="https://bsky.app/profile/afilina.bsky.social">Anna Filina</a> for the reminder.</p>
<h3 id="toc_13">Calling methods the classic way</h3>
<p>Finally, the classic way to call methods: this is achieved with the object operator <code>-></code> and the static operator <code>::</code>.</p>
<div>
<pre class="brush: php; title: ; notranslate">
<?php
class x {
function foo($a = 1) { echo __METHOD__;}
static function bar($a = 1) { echo __METHOD__;}
}
$x->foo(); // classic method call
$x::bar(); // static method call on object
X::bar(); // static method call on class name
?>
</pre>
</div>
<h2 id="toc_14">That’s all folks!</h2>
<p>We have covered no less than eleven (11!!) different ways to call PHP methods and functions. The first and last are the most commonly used, with good reasons. The other ones covers niche usages, such as dynamic method call, or dynamic parameter collections.</p>
<p>The post <a href="https://www.exakat.io/call-a-method-in-php/">How to call a method in PHP</a> appeared first on <a href="https://www.exakat.io">Exakat</a>.</p>