Building desktop apps with PHP - Marcel Pociot

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right there we go so for those of you who wonder is this now a different conference why are we writing desktop applications now don't worry because we're going to write them with PHP um now the obvious question that probably some of you have is why on Earth should we use PHP to write a des of application and it's a fair question and I don't have like the right answer to it I have a couple of answers that I think are valid and one of them is we can I guess which is fine but in honesty I think it's good if we can give people who work with PHP so basically everyone who's now sitting here the ability to create a desktop application with their existing knowledge usage of Frameworks I think that's just great to just extend what you already can do with a language and then explore and go from there and well sort of besides that I like to do more of the weird things with PHP so um like I wrote expose which is um like a fully open-source version of something like enro so it allows you to share your local URLs and then share and tunnel them via Expose and then you can share those URLs with others the client is built entirely in PHP the server is build entirely in PHP so I like to do more of the weird things with PHP and well building desktop apps is one of those things I guess my name is Mara P I'm the CTO at Beyond code which uh is kind of a fancy way of saying I'm one of the co-owners of this three people company and we create uh software tools for PHP developers software tools for people who work with PHP and they in particular so for example we created laral herd uh if you have heard about it no pun intended um which is like a great way to get started with PHP and onboard you into laral and PHP in general and feel free to follow me on X if that's your thing now one of the application that we built at beond code is called tinco well don't worry it's not going to be an ad and this is like the very first screenshot that I shared of this this app was built in uh Swift and it sort of allows you to use something like Pell or PHP Artis and Tinker in a GUI application so you can write some PHP codes you can run it in the context of your application as I said I wrote the very first version of this app in Swift and shortly after releasing the app a lot of people asked us well this is cool but we're not on Mac we want to use this on Windows or we want to use this on Linux Linux but the application was written in Swift so my immediate reaction was not the best one and I thought well I'm just going to rewrite the app two more times natively for uh windows and for Linux and as I said we're only three people at Beyond code so this wasn't really a good choice because it would mean that we would have to maintain multiple code bases of our application every new feature that we would introduce we would have to rebuild it for each different platform so I started looking this was like five six years ago I started looking at Alternatives how we can have one single code base um and then basically ship this to different operating systems and back then those were the main popular Solutions and this is probably still the case uh so we have t and we have electron and T allows you to write your desktop applications in a combination of rust and JavaScript so rust is used sort of for the backend code of your desktop application and then the front end is sort of a combination of JavaScript and HTML and well electron is Javascript only plus HTML and back at the time T wasn't even a 1.0 release and I don't know anything about rust it's way out of my comfort zone so to say so for me the decision was clear I wanted to go with elron to build death applications now before we go and I'll show you how we can introduce PHP into this mix I first want to give you an overview of how a electron life cycle looks like so if we start an electron application this is basically what happens so we start an application like Discord or vs codes or tin well and the electron app basically spawns two processes so the first one is called the the main process or sometimes referred to as the background process and it's written in JavaScript and it allows you to communicate using the electron API with things like no. JS functionality so you have access to the file system to the crypto um to the crypto Library if you want to do something with that we can make use of native UI elements so for example we can um add something to our doc to the menu bar we can change things in the actual menu of our application and we have access to the operating system such as Touch ID for example and then this main process spawns another process which is the renderer process and well in terms of electron this is basically just a Chromium browser that starts and then renders some HTML with JavaScript and then if we want to communicate between those processes we have to do some interprocess communication which isn't really that easy with electrons so for example if you want to click on a button that shows a file picker then the render has the button when you click on it it would need to communicate with the main process where we open the file dialogue when the user selects a file that one gets sent back to the renderer so there's a lot of communication going on then but that's like the main idea of how electron applications work in general now I wanted to bring PHP into this mix so how would this look like well it's actually relatively easy so we still have this main process that starts with electron we can't do anything about this this is just how electron Works under the hood but now our main process basically spawns another process instead of just the renderer so we basically run something like PHP ortis and surf or if you're not that familiar with larel it's like the built-in PHP web server which gives us a URL pointing to our local PHP applic appc which is cool so we can take this URL give it to our renderer and say don't render this local HTML file but just show us the URL of our PHP based application so then we basically have a desktop application that is running a browser uh showing us our PHP application and if we want to communicate from PHP with the main process we can just do this with some kind of lightweight HTTP API so then we can still access all the native UI elements we can access any operating system stuff like touch ID Etc from PHP by having this kind of approach but this introduces a couple of problems so let's say I write an application like an actual desktop application with this and I use PHP and I want to ship this and give my app to you then I would need to tell you well do you have PHP installed in the first place because you needed to run my app then I need to make sure let's say I'm using PHP 8.3 that you have 8.3 installed as well maybe I'm using extensions that you don't have installed I need to tell my application users all this list of things that need they need to have installed in order to use my application or any settings all that setup and that's really annoying and that was like one of the main blockers so how can we avoid that but still use PHP and the idea to this is actually pretty pretty simple we just ship PHP along with our desktop application so we're basically using a statically compiled binary of PHP that has everything included in just one single file and this is also what works uh behind the scenes what Franken PHP does or larel herd so it's just one single binary with everything included it's only 21 megabytes if uh at least if you're working with electron that's lightweight so it's not that bad and by doing that we have full control over the whole process because we can decide which version of PHP do we want to bundle with our app which extensions do we want to bundle which any settings do we want to bundle so everything is just included in this one single file and it has everything uh built in that we need to run like a default laral application and this idea is what we have built into what is called native PHP so native PHP is a laravel package that allows you to create desktop applications with PHP and if I say we then that's me and Simon ham who's sitting here um so um that's why I'm going to show you how we can make use of this in an actual application so let's jump into the IDE and see how this works so as I said it's a larel package um now if you might may think well why is it larel specific the reason is quite simple I'm using laral Simon is using laral and we need to hook so many things into the framework to just make this work seamless that it was way easier to do this with a laral best package than doing it a framework independently it works in theory but we just haven't put in the work to provide it for something like Symphony yet so if you want to get started with it in a Lal application it's just a composer require way so you would do something like composer require native PHP slash and then just like I had the decision and was able to choose should I use electron or should I use T you can basically do the same thing with a soon with Native PHP so as of right now we have uh only an electron driver so we can do something like require native PHP electron um but Simon especially is working hard on the T implementation of this which will bring a whole lot of benefits compared to the electron approach because it will not spawn an actual HTTP server but it will communicate with PHP directly via the C library which is really exciting so as of right now you would just do composer require native PHP electron I already did this because I want to spare you the excitement of seeing composer run and then once that's done um we can run PHP Artisan native install and this is then going to do all the setup work for us it's going to download the npm dependencies it's going to download electron and it's also going to publish a configuration file and a service provider so I already did this as well and once that's done we are basically ready to go and start working on our application just like we would work with a traditional l app so we can run PHP Artisan native surf and just like running PHP Artisan surf this basically starts up our application in this sort of development mode so now we have already the def tools open just like we would in a regular browser and we have this little native application window and we have our doc icon that is the native PHP logo and well this uh already shows us the title of our app and then we have the basically the larel homepage of my application and what's interesting about this is if we take a look at the PHP version this is currently using PHP 8.1 in this demo but I'm using using locally PHP 8.3 so this is not using the PHP version that I have installed locally but it's already using the PHP version that we bundle so this is basically the same thing as you would do when you would then then ship this application to an actual user now let's see how we can make some modifications to this so as I said we have this service provider and if you're familiar with laral then this might look a bit different than your traditional service provider because it doesn't extend from laral service provider and the way that this native app service provider works is once electron in this case or once the native application part is ready to go once we have started the PHP web server we will call this boot method and in here you can then configure how your application should start up so for example you could put Global key bitings in here you can Define how the window should open how the menu should behave things like that so by default this just this window open and this is what we get so we make a couple of assumptions here so for example the title of your window um will be the title of of your Layel application the one that you have in your environment or config file then as I said we by default show the homepage of your Lal application and we can customize this by making some adjustments and just chain methods on this window open call so for example we can say we want to change the width of a window to I don't know 250 and the height should be 300 or let's say 400 to make it easier to see then if I save we have hot reloading in place so my application automatically restarts and now the default width and height of my window has changed as well now one problem is we can still make this like super small so let's fix this as well and we can just add a minimum width of 200 and whoops a minimum height of let's say 300 save this and with this very fluent API we can then Define the behavior of our window and now I'm no longer able to make this window smaller what else we can do is especially useful for this for this talk we can say that the window should be always on top like that and let's change the title as well and say title should be hello London all right so one annoying thing about this especially this hot reloading is that the window will always come back in the center of my screen and from a user perspective I kind of want this to remember where I last put it so if I resize the window and move it to somewhere on the right like this the next time I open my application I would expect from user perspective I would expect it to reopen at the exact same position the exact same width the exact same height so with Native PHP we can just chain another method call in here called Remember State and now it remembers the application state that we last set it to so if I'm now I don't know position it like this and then we trigger hot reloading Again by changing the title my application restarts and now it just remembered where I last put the window and then reopened it in the exact same way and um there are a lot of more things that you can do and customize the window so for example um like on Mac OS you can remove the title bar you can change the background opacity and um you can also open different routes or URLs so so if I have a different route that I want to open instead of the just the homepage I can just Define this here so I already created this dialogue route which is currently just a blank page so this is basically then the entry point of my desktop application and there are basically two kinds of apps so we have applications that have actual Windows like this one here and then we have applications that only live in the menu bar so for for example like Tuple or cleanshot or uh or her and we can do these kind of applications with Native PHP as well so just like we do the window open call we can create a menu bar application so just by calling menu bar create we now have this native PHP icon here and it pretty much behaves the same way as a window so when I click on this we see another browser window basically that op opens my PHP application and again by default this is the homepage of my app and we can change this as well just like we changed the window so for example we say that this should have a different width it should have a different height something like that and the route should be the menu bar again we have hot reloading and you know if we click we just have basically a different URL a different route of my larl application being displayed in the menu bar so uh that's really easy to create this kind of application as well one thing you might notice is that now the doc icon for my application is gone and that's kind of uh something that we made by choice because if you only want to create a menu bar application that does not have a window that's like the default behavior that you would expect you don't want to have a dock icon in this case but if you have like a combination you want to show the doc icon so we can just add the show do icon method on here and now well my doc icon Still Remains visible and I have my menu bar icon up top and now I have like a combination of these things in my app now the next thing I want to show you is we want to build something like a settings screen and usually this works by we have our menu up here and if I click on the first entry we would have something like preferences or settings and if I click on this a new window goes up so how can we do this with Native PHP well we can also customize the menu so we can just create a new menu and the first entry would be a submenu with the label PHP UK and then as the second argument we basically provide the actual submenu so we have let's say a label that says hello London then we have a separator and then we have the option to quit our application and like that you can just configure your native uh Native actual menu and once you're done with that we can just call the register method which tells our application that this is the menu that we want to register with this application so if we save this we now only have this one entry up here which says PHP UK then we have hello London and the option to quit our application now as you see all the other options are gone which are like the default ones like editing uh copy pasting text so we want to read them of course you could just manually do that but we can also make use of some methods that do that for us so for example here we have like the file menu I can just add this or the edit menu The View menu and the window menu so just by chaining these methods and you can also just reorder them however you want we can then build up our um Native application menu like that so now we want to add an entry to this first um submenu that says something like preferences or settings and when we click on it we want to actually open up a window and the way this would work like in a traditional JavaScript application is you click on this it dispatches an event then we listen for this click event and then open up a new window and that's basically what we do in Native PHP as well so we can add a special kind of item to our menu that is of type event and when we click on this this event that we pass in here will then be dis patched within our laral application so this would be the settings click event that we dispatch then we can Define the actual title of our menu item so let's say it's preferences and then we can also Define a hot key that we want to register along with this so by default on a Mech OS this would be command or control and comma so let's save this there we go so now we have this preferences icon entry in here which has the shortcut assigned to it as well and if we click on this well our event got dispatched but we're not listening for this yet so now to make this work we can just start listening for this event and this just to show you is just a plain laral event there's nothing special about this it's just as you would write your larel application is just one event class that we can listen for so if we now go to our event service provider we can start listening for this event and say listen for the settings click event and when this was clicked well what do we want to do we want to open up a new window that shows us a different URL basically so we can call the window facade and say we want to open the window and the route should be settings now one important thing about this if you have multiple windows is that you need to pass an ID when you open up a window and I'll show you why in a setting so in a second so let's say that this is called settings as well all right and let's also give this a title settings there we go so now if I click the event gets dispatched I have another tab from my developer tools and we have this new settings window open and just like we did in the uh service provider we can totally customize the width the height and whatnot of this specific window now the ID of this window is important because let's say I have the settings window already open I'm currently focusing my main window and I Rec click on this preferences then native PHP now knows a settings window already exists because we gave it this ID so it just switches the focus to the already open window and it's now recreating an actual new one so if we now close this and I press the shortcut then we have the settings window open back up again okay cool so now in the settings it's just a simple dropdown where we can change the background color and I want to now Implement some very basic uh functionality for my application so I want to be able to change the color and do basically two things first off when I change the color I want to persist this setting somewhere so when I reopen my window it should if I switch this to Green it should still say green the next time I reopen that and the next thing I want to do is well the default behavior that I would expect from a desktop application is if I switch the background color it should change this Windows color in real time as I changed the setting so there's no reloading pressing command R in desktop applications so let's see how we can make this work and um as freck showed you in the keynote we are using live wire for this so um as freak said it's basically allowing you to use something like a reactive State Management in PHP you don't need to use Livewire with Native PHP it's just that it works perfectly together because uh I don't need to write JavaScript which is kind of nice but you can use native PHP with um just plain blade views if you want you can also use just view or JavaScript for your front end you're not limited I'm just using lifeware for this demo so if we go to this settings component this is what it looks like so we have this component and we have a public property called color and when we Mount the component so this is basically when my component gets displayed and it's ready to go we make use of the settings facade that we ship with Native PHP that gives you basically a little key value store to store these kind of settings so when we Mount the component we just populate this color property with the color key from my settings so we basically pull out the settings that were stored if there isn't any settings stored we just use the default one which is white in this case and then if we update the color so this is like a magic method from lifewire so that every time we change the color property using the dropdown this method will be called automatically by Livewire and all we're doing here is we basically just set the new new value in this key Value Store to our color so just by doing that we should already be able to change this color to Green then I'm closing the setting let's reopen it and now it's green again because when we open the window we're pulling the setting from this key value store all right so that's already done now how can we communicate this setting change to the other window and if you think about how you would do this in like a traditional web application if you think that these are two individual tabs in your browser the way we could communicate between the windows is by using something like websockets so we we need to have some sort of real-time communication because this window had a request and this is the response so there's no uh like concurrent connection available for us to dispatch any events back and forth and um it works in fact with WAP sockets but we can do it even a bit simpler with Native PHP because we have this little repper of electron under the hood so if we go to this component that we show right here this called the dialog component then you can already see that we also have a color property so I already have hooked up that we want to show a color and we also have a method that we can update the color whenever it changes we just need to still basically listen for some kind of event and lifewire allows us to listen for events but um as I said the problem is there is no concurrent connection so if we would listen for an event we would need to manually reload and refresh our browser basically to trigger that so what we can do is we can use a special keyword keywords called native so we can listen for an event and as we're using the settings facade This One dispatches automatically an event every time some key in the setting key Value Store changes so we can listen for the setting changed event and once this changes we want to call this updated settings method so every time we change something in the drop down from our settings the settings will be written to the key value store this is going to dispatch this event we can then listen for this event basically in real time by using this native keyword on top of it and once we do that we call this method which just retrieves the new payload so the new color that we set in our settings and then we store this in in this property and because we're using lifewire we basically have free hot reloading because then liveware takes care of rendering our view so now if I'm going to change this we can now dispatch the vent and listen for this in the other window in real time which I think is really cool and again it didn't require us to write any single line of JavaScript Okay cool so um the next thing I want to show you is something that is really not doable with a traditional web application and I want to build bu something like a screenshot tool so I want to be able to select the window when I hover my mouse I want to highlight the window that is currently selected then I want to be able to click on the window and because I wanted to push my luck I brought like a thermal printer so maybe we will even print this but we'll see what the live demo gods have to say about that so first of all let's add some logic to open like a window picker so if if we go to this dialog component open up the view and then we're just going to add a new button to it so let's say pick window and when we click on this we want to open a picker so let's implement this open picker method so in our dialogue component we add a new method open picker now what does this do if we have this sort of functionality where we have the screenshot tool the way this actually works is there's like a full width full height transparent window on top of everything and when we move our Mouse around the window detects the current active window that is underneath the cursor and then it just places a rectangle at the exact same coordinates with some background color and that's what we want to do as well so we want to open a new window window that we give a different ID which is the Picker and then I have already created a macro because there's a lot going on in this called picker so let me show you what this picker macro does so in my app service provider I have this macro because well writing this would be a lot so as I said these window Pickers basically put uh a transparent window on top of everything so we're making use of the screen facade that we ship with Native PHP to get an array of all the displays like the physical displays connected to the computer for this demo I'm just going to use the first one because I don't have multiple connected and then we can in this macro just use the same methods as we would on the window itself so we give this window a title we're going to open a specific URL so this is like the Picker URL the width of my window is well it's the width width of my display the height is the height of my display the window should be transparent it should be always on top of everything else we want it to be focusable it needs to be closable because we want to even close it just by using an API except first Mouse is kind of a weird one this basically allows us to listen for Mouse move events it should be frameless it could be larger than the actual screen we don't want it to be minimized we don't want it to be resizable it should not be movable it should not have a shadow and the title bar is hidden so a lot of stuff but I think with that API it's still very readable uh to understand what is actually going on in here so that's what the what the Picker macro does and if I reload here we have this button now if I click on this it would be not as good because I would get like a giant 404 sitting on top of everything else that I couldn't close because it's not closable by by a button because the title bar is hting so we have to implement that logic first so let's add this picker route so I'm going to add a new route to my application called picker and in here we are going to um point this to another Live Wire component so we will create a live wire component called picker and then point this to our route like that now in our pick a component we render a view which is basically what we see once we click on this and this picker opens up in full time and what we want this to do is well it should have the height of the screen it should have the width of our screen and then we can use these Live Wire um events basically so we can say when we move the mouse we want want to get the highlighted window of the current cursor position so get highlighted window like that so let's add this method so to get the highlighted window we want to store this in like a property called highlighted window and to get it I'm cheating a bit because I already wrote this we're making use of the get highlighted window method on this window highlighter class and I'm showing you what this does so to get the current highlighted window we're making use of another uh facade that we give or class that we give with L with Native PHP the screen facade which we use to get the current cursor position then we get an array of all the currently active windows and I'm putting that in a cache for a couple seconds because otherwise every millisecond I move my mouse around it would get the currently active windows and we don't need that so we're caching that and then we have this we have the cursor position and we have the array of active windows so then we can just return the first window that is not the window picker itself because it sits on top of everything else and we would otherwise always highlight the window picker window so we ignore that and then we get the first window that is in the boundaries of my cursor position and then we just return it all right so then we have this in this highlighted window property and now in our view we can basically check if we have a highlighted window well we want to show some diff at the exact same position where my window underneath is so I have created a little snippet for that so we show this div when we click on the div if we want to select it which actually takes the screenshot and then closes the Picker the Highlight window is absolutely positioned let's change this to like blue so we want to have this blue like background just like we have with the default screenshot utility and then we have some styling to apply the top left width and height so the position and the width and height of this diff should be the same one as for the actual window that I have selected and then for the content we just show the title of the window okay so the last missing piece is the ability to select the window so if we add this selecting a window what it would need to do is well we want to take a screenshot so we can do this by using window highlighter take screenshot of the highlighted window and then we need to close the window again because otherwise our picker would still be open so we can just use the window facade call close and the native PHP automatically closes like we could pass an ID in here but native PHP just knows uh if we don't pass anything in there it just closes the current active window all right and taking a screenshot is really simple I'm just basically running a command line script screen capture which is pre- default uh pre-installed on Mac OS we give it the ID of the window and then we store that screenshot in our clipboard and um basically create the screenshot without a shadow all right so with a bit of luck this should now all work if I click on this so I can open the window picker and there we go so now we have this window sitting on top I can move the mouse around and highlight different windows I have the title in there one thing you might notice is that the coordinates are slightly shifted so it's uh a bit more more downwards than it should be and that's because by default we would now need to detect if the menu bar is visible and the height of the menu bar we would need to uh substract that from the window position but you get the idea now if I click our window was closed okay and we should have the screenshot in our clipboard so next up let's display what we have in our clipboard because we have access to that too in Native PHP so if we go to our dialogue component where we have this open picker window let's just display it here so we can say if we have something in our clipboard we want to show an image and the source of this image would just be the clipboard content and now we need to provide that content to the view so we can do this in our dialog component we can add another function called get clipboard property so this is if you're familiar with uh with vuejs or react this is sort of like a computed property and in here we can make use of the clipboard facad that we ship with Native PHP and this one allows us to get access to any image that is in the clipboard or also any text or HTML um if you pass a parameter to these methods you can also set the value of the clipboard so we're just interested in the clipboard content so like that now let me reload here that worked great right what did I [Music] miss clipboard [Music] clipboards should just work live demos right we could fix this that's why we have an hour long slot don't worry [Music] um maybe it's no longer in there there we go yeah maybe oh I think I just copied some text that removed the image from my clipboard and that's why it was no longer there okay so there's now our image and if I select different one this one is now in my clipboard but we don't see the new image in that window immediately and we can fix this very easily with lifewire so we can just say in our dialogue view lifewire has the ability to basically pull for changes so we can say wire pole and now in the background Livewire the view is actually going to reload every couple of seconds and see if there are new changes so if I'm now trying that again let's pick another screenshot then the changes get updated in real time automatically like that cool all right so the last missing piece is trying to print this thing I originally wanted to show you how easy it is to choose a printer using the native PHP facades which it is and you probably knew this because you're smarter than me I didn't these little things are not actual printers these are Bluetooth low energy printers so you can't just regularly print to them um so I'm using some weird Python scripts but but it still works sometimes so we'll see let me turn this thing on and then we just add a new button that we wire click to a print method we call it print now let's add that method and well apparently these things are called cat printers because well they look like a cat so there is a python project that allows us to print to these things so I made a class called cat printer and we can just print the clipboard property and pass it to this cat printer thing and the way this works is well the clipboard content is basically just the base 64 encoded image so we just take that Image store it into an actual file and then I'm going to run this python script give it the file name of the screenshot and then um every output that this python script generates we're going to use the info helper from laravel to just log it to our um lock file and now if I click here oh python that's what I meant all right we don't once again the the part about the clipboard there was no image right print and it's printing cool and what's really nice about this in my printer we had these info calls that would usually write to laravel's log file but with Native PHP we just redirect everything that that would get written to the log file to the def tools so we just see the output of these logs in the def tools which I think is really cool so yeah with a couple lines of PHP we now created a little desktop application that can change color the background color in real time we can have a window picker we can take screenshots we can even print screenshots and um that's pretty much the demo that I have for you native PHP has a ton of things that I didn't have the time to show you so you can create file picker dialogues you can ask the user to select um like files or folders We have access to system apis like touch ID we have full database support which is really cool so if you use something like laral models and eloquent and store them have migration it all just works out of the box so we create a sqlite database for you we run migrations every time your application starts um this is all handled for you uh Native PHP has full support for queuing so if you have any long running tasks you can just push it to a queue as you would do with your web application and then we have a queue worker running in the background that processes them we have support for the scheduler so if you have some uh tasks that you want to run like every minute or even every couple of seconds just add it to your console kernel as you would with laravel it will just work um we have a Auto updat auto updator that is included in Native PHP it currently supports GitHub um AWS S3 and digital ocean spaces access to the clipboard you can define global shortcuts to trigger some PHP logic by just listening for events you have access to Notifications you can create progress bars in the doc icon context menus deep linking from one app to your app which is really powerful and we dispatch a lot of events that you can listen to like moving the window around minimizing the window maximizing closing moving the mouse all that stuff and you can just listen to these events in PHP so that's what I have for you if you want to learn more about Native PHP go to Native php.com it's uh entirely open source come speak to me during the conference if you have any questions or want to ask something Simon is here uh I'm just putting you on the spot now if you want to talk about t go to talk to him as well and um thank you very much
Info
Channel: PHP UK Conference
Views: 4,073
Rating: undefined out of 5
Keywords:
Id: szi9OIef2oU
Channel Id: undefined
Length: 47min 39sec (2859 seconds)
Published: Thu Feb 15 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.