[{"id":"content:en:about.md","path":"/about","dir":"","title":"Hi, I'm Ewilan Rivière","description":"I started to develop my first real projects with Laravel (PHP), learning also to use Git. But I'm also very interested in the user experience, which pushes me to keep a link with the front-end with Vue.js and Nuxt.js (JavaScript / TypeScript) with CSS frameworks like Tailwind CSS. I want to implement the necessary for SEO, although I am not an expert in this field. On the mobile side, beyond web responsive, I do Flutter (Dart) in order to be able to deploy applications on Android and on iOS.","keywords":[],"body":" I started to develop my first real projects with Laravel (PHP), learning also to use Git. But I'm also very interested in the user experience, which pushes me to keep a link with the front-end with Vue.js and Nuxt.js (JavaScript / TypeScript) with CSS frameworks like Tailwind CSS. I want to implement the necessary for SEO, although I am not an expert in this field. On the mobile side, beyond web responsive, I do Flutter (Dart) in order to be able to deploy applications on Android and on iOS. I like to have knowledge on each part of the chain, from local development to deployment on a server (with NGINX), which leads me to configure quite often Linux servers for both production and local development. Therefore, I like to have efficient tools, whatever the development environment (Linux, Windows or macOS) to be able to easily use such or such version of a language. I develop best in groups, preferring communication and help to isolated development, as long as I have colleagues who also enjoy this way of working. I love to discuss technologies, I'm interested in the latest versions of the languages I use and I spend a lot of time reading documentation or making it, in Markdown of course. I always have too many projects on the go, but I also always have something to do! My favorite tools are : IDE : Visual Studio Code System: macOS (M1 chip), Android Server: Linux Debian Main technologies : Laravel , Vue.js , TypeScript , Node.js , Tailwind CSS , Vite In addition : GitHub Copilot , TablePlus , Figma , Ray"},{"id":"content:en:articles:laravel-filepond-livewire.md","path":"/articles/laravel-filepond-livewire","dir":"articles","title":"Laravel FilePond with Livewire","description":null,"keywords":["About","Let's start","Add FilePond","Livewire component"],"body":" About FilePond FilePond is a JavaScript library that can upload anything you throw at it, optimizes images for faster uploads, and offers a great, accessible, silky smooth user experience. When you want to build an image upload component in your Laravel application with Blade and Livewire , FilePond is a great choice. I use Filament as admin panel, and Filament use FilePond to upload images. This article offer to create a component to upload images with FilePond and Livewire, to upload files and retrieve them into Filament. And I use Tailwind CSS for styling, but you can use any CSS framework or vanilla CSS. Of course, if you want to build a native file upload component, you could use Alpine.js but you will have to handle all upload options with preview, progress, error handling, multiple files, etc. It's a lot of work to develop and maintain. I try to develop it... it was a nightmare. With FilePond, you can build a great image upload component in a few minutes. The main problem is documentation is not very clear and there is no example with Livewire. So, I will show you how to do it. FilePond documentation isn't really user-friendly, with few examples. I really advise to check this library with TypeScript to have a better understanding of the API. In this article, we will use Alpine.js and FilePond CDN to create a component without any dependency, but you could create an Alpine.data component to have a better code organization and reusability. Why Livewire? I use Livewire to build my components because it's a great library to build interactive UIs without writing JavaScript. You could just write PHP and create async components, it's just amazing. And Alpine.js? You could write vanilla JavaScript if you want, I use Alpine.js because it's a great library to write declarative JavaScript. It's a great companion to Livewire, and it's very lightweight. Source code If you want to have final result, without details, you can find the source code of this gist on GitHub . Don't forget to add @stack directives to your root Blade file, usually resources/views/layouts/app.blade.php . See Let's start section for more details. And window.appUrlStorage is a global variable to your application storage URL, usually {{ config('app.url') }}/storage . See Add FilePond section for more details. Let's start First, we will create a new Blade component with the command: php artisan make:command Field/Upload Now you will have two files, PHP file in app/View/Components/Field/Upload.php and Blade file in resources/views/components/field/upload.blade.php (if you have default views.paths in your config/view.php ). Before anything, you have to add Blade directives @stack to your root Blade file, usually resources/views/layouts/app.blade.php : < head >\n \n @stack ( 'head' )\n \n \n < body >\n \n @stack ( 'scripts' )\n Now we can push (only once) FilePond CDN to our head and after body sections: @pushOnce('head')\n < link href = \"https://unpkg.com/filepond/dist/filepond.css\" rel = \"stylesheet\" />\n < link\n href = \"https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css\"\n rel = \"stylesheet\"\n />\n @endPushOnce\n \n < div >\n \n \n \n @pushOnce('scripts')\n < script src = \"https://unpkg.com/filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.js\" >\n < script src = \"https://unpkg.com/filepond-plugin-file-validate-size/dist/filepond-plugin-file-validate-size.js\" >\n < script src = \"https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js\" >\n < script src = \"https://unpkg.com/filepond/dist/filepond.js\" >\n < script >\n FilePond. registerPlugin (FilePondPluginFileValidateType);\n FilePond. registerPlugin (FilePondPluginFileValidateSize);\n FilePond. registerPlugin (FilePondPluginImagePreview);\n \n @endPushOnce And now, FilePond is available, we can build our component: < div\n class = \"relative\"\n wire:ignore\n x-cloak\n x-data = \"{\n model: @entangle($attributes->whereStartsWith('wire:model')->first()),\n isMultiple: {{ $multiple ? 'true' : 'false' }},\n current: undefined,\n currentList: [],\n \n async URLtoFile(path) {},\n }\"\n x-init = \"async () => {}\"\n > Into PHP file, we will add some properties, we will inject them into our Alpine.js to fill FilePond configuration options. You can add some options, see FilePond documentation for more details. name=\"avatar\" is the name of the input. wire:model=\"image\" is the Livewire model. multiple is a boolean to allow multiple files. validate is a boolean to validate files. preview is a boolean to show preview. required is a boolean to make the input required. disabled is a boolean to disable the input. preview-max=\"200\" is the maximum height of the preview. accept=\"image/png, image/jpeg\" is the accepted MIME types. size=\"4mb\" is the maximum size of the file. number=\"4\" is the maximum number of files. We will set these options into FilePond to interact directly with the component. Handle props Before send data to Alpine.js, we will handle some props: public function render () : View | string\n {\n // Set boolean values\n if ( ! $this -> multiple) {\n $this -> multiple = 0 ;\n }\n \n if ( ! $this -> validate) {\n $this -> validate = 0 ;\n }\n \n if ( ! $this -> preview) {\n $this -> preview = 0 ;\n }\n \n if ( ! $this -> required) {\n $this -> required = 0 ;\n }\n \n if ( ! $this -> disabled) {\n $this -> disabled = 0 ;\n }\n \n // Prepare accept files to JSON\n if ( is_string ( $this -> accept)) {\n $this -> accept = explode ( ',' , $this -> accept);\n }\n \n $this -> accept = array_map ( 'trim' , $this -> accept);\n $this -> accept = array_filter ( $this -> accept);\n $this -> accept = array_unique ( $this -> accept);\n $this -> accept = array_values ( $this -> accept);\n $this -> accept = array_map ( 'strtolower' , $this -> accept);\n $fileTypes = $this -> accept;\n $this -> accept = json_encode ( $this -> accept);\n \n // Set size human for UI\n $this -> sizeHuman = $this -> size;\n \n // Prepare files types for UI\n foreach ($fileTypes as $type) {\n $new = explode ( '/' , $type);\n \n if ( array_key_exists ( 1 , $new)) {\n $this -> acceptHuman[] = \".{ $new [ 1 ]}\" ;\n }\n }\n \n $this -> acceptHuman = implode ( ', ' , $this -> acceptHuman);\n \n return view ( 'components.field.upload-file' );\n } Add some HTML Now, we can add some HTML to our component: < div class = \"relative\" wire:ignore x-cloak x-data = \"...\" >\n @if ($label)\n < div class = \"flex items-center justify-between\" >\n < label\n class = \"block text-sm font-medium leading-6 text-gray-900 dark:text-gray-100\"\n for = \"{{ $name }}\"\n >\n {{ $label }} @if ($required)\n < span class = \"text-red-500\" title = \"Required\" >*\n @endif\n \n < div class = \"text-xs text-gray-400\" >Size max: {{ $sizeHuman }}\n \n @endif\n < div class = \"flex items-center justify-between text-xs text-gray-400\" >\n < div >Formats: {{ $acceptHuman }}\n < div >\n {{ $multiple ? 'Multiple' : 'Single' }} @if ($multiple)\n < span >({{ $number }} files max)\n @endif\n \n \n < div class = \"mt-5\" >\n < input type = \"file\" x-ref = \"{{ $attributes->get('ref') ?? 'input' }}\" />\n \n @error('image')\n < p class = \"mt-2 text-sm text-red-600\" >{{ $message }}\n @enderror\n FilePond will replace the input get('ref') ?? 'input' }}\" /> . Add FilePond Now we can add FilePond to our component. But first, we need to add global method. I use the window variable window.appUrlStorage , I register it globally this variable in root Blade view. < script >\n window.appUrlStorage = \"{{ config('app.url').'/storage' }}\" ;\n Retrieve uploaded files We just add a method to retrieve uploaded files, URLtoFile() . < div\n class = \"relative\"\n wire:ignore\n x-cloak\n x-data = \"{\n model: @entangle($attributes->whereStartsWith('wire:model')->first()),\n isMultiple: {{ $multiple ? 'true' : 'false' }},\n current: undefined,\n currentList: [],\n \n async URLtoFile(path) {\n let url = `${window.appUrlStorage}/${path}`;\n let name = url.split('/').pop();\n const response = await fetch(url);\n const data = await response.blob();\n const metadata = {\n name: name,\n size: data.size,\n type: data.type\n };\n let file = new File([data], name, metadata);\n return {\n source: file,\n options: {\n type: 'local',\n metadata: {\n name: name,\n size: file.size,\n type: file.type\n }\n }\n }\n }\n }\"\n x-init = \"async () => {}\"\n >\n ...\n The method URLtoFile() will fetch the file from the storage and return a FilePond object to display it. The user can delete it to upload a new file. Add FilePond options We will add File some options to FilePond: < div\n class = \"...\"\n x-data = \"{\n ...\n }\"\n x-init = \"async () => {\n let picture = model\n let files = []\n let exists = []\n if (model) {\n if (isMultiple) {\n currentList = model.map((picture) => `${window.appUrlStorage}/${picture}`);\n await Promise.all(model.map(async (picture) => exists.push(await URLtoFile(picture))))\n } else {\n if (picture) {\n exists.push(await URLtoFile(picture))\n }\n }\n }\n files = exists\n let modelName = '{{ $attributes->whereStartsWith('wire:model')->first() }}'\n \n const notify = () => {\n new Notification()\n .title('File uploaded')\n .body(`You can save changes!`)\n .success()\n .seconds(1.5)\n .send()\n }\n \n const pond = FilePond.create($refs.{{ $attributes->get('ref') ?? 'input' }});\n pond.setOptions({\n allowMultiple: {{ $multiple ? 'true' : 'false' }},\n server: {\n process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {\n @this.upload(modelName, file, load, error, progress)\n },\n revert: (filename, load) => {\n @this.removeUpload(modelName, filename, load)\n },\n remove: (filename, load) => {\n @this.removeFile(modelName, filename.name)\n load();\n },\n },\n allowImagePreview: {{ $preview ? 'true' : 'false' }},\n imagePreviewMaxHeight: {{ $previewMax ? $previewMax : '256' }},\n allowFileTypeValidation: {{ $validate ? 'true' : 'false' }},\n acceptedFileTypes: {{ $accept ? $accept : 'null' }},\n allowFileSizeValidation: {{ $validate ? 'true' : 'false' }},\n maxFileSize: {!! $size ? \" '\" . $size . \"'\" : 'null' !!},\n maxFiles: {{ $number ? $number : 'null' }},\n required: {{ $required ? 'true' : 'false' }},\n disabled: {{ $disabled ? 'true' : 'false' }},\n onprocessfile: () => notify()\n });\n pond.addFiles(files)\n \n pond.on('addfile', (error, file) => {\n if (error) {\n console.log('Oh no');\n return;\n }\n });\n }\"\n >\n ...\n Some details Into global model , with @entangle , we can find all uploaded files. To handle existing files, we use previous method URLtoFile() to fetch the file from the storage. If multiple is enabled, we create an array, otherwise we create an array with just one file. modelName is binded with Livewire to handle the upload. let picture = model\n let files = []\n let exists = []\n if (model) {\n if (isMultiple) {\n currentList = model. map (( picture ) => `${ window . appUrlStorage }/${ picture }` );\n await Promise . all (model. map ( async ( picture ) => exists. push ( await URLtoFile (picture))))\n } else {\n if (picture) {\n exists. push ( await URLtoFile (picture))\n }\n }\n }\n files = exists\n let modelName = '{{ $attributes->whereStartsWith(' wire:model ')->first() }}' To send notifications when upload process is finished, we use Filament notifications . const notify = () => {\n new Notification ()\n . title ( \"File uploaded\" )\n . body ( `You can save changes!` )\n . success ()\n . seconds ( 1.5 )\n . send ();\n }; And finally, we create FilePond instance with some options. First, we need to bind it to previous input element. const pond = FilePond. create ($refs.{{ $attributes -> get ( 'ref' ) ?? 'input' }}); Now, we can add options from PHP file, you can add more options if you need it. You can see some Livewire methods with @this.upload , @this.removeUpload and @this.removeFile , it's really amazing to handle uploads with Livewire you need to add Livewire\\WithFileUploads trait to your Livewire component. And with onprocessfile() event, we notify the user that the upload is finished. pond. setOptions ({\n allowMultiple: {{ $multiple ? 'true' : 'false' }},\n server: {\n process : ( fieldName , file , metadata , load , error , progress , abort , transfer , options ) => {\n @ this . upload (modelName, file, load, error, progress)\n },\n revert : ( filename , load ) => {\n @ this . removeUpload (modelName, filename, load)\n },\n remove : ( filename , load ) => {\n @ this . removeFile (modelName, filename.name)\n load ();\n },\n },\n allowImagePreview: {{ $preview ? 'true' : 'false' }},\n imagePreviewMaxHeight: {{ $previewMax ? $previewMax : '256' }},\n allowFileTypeValidation: {{ $validate ? 'true' : 'false' }},\n acceptedFileTypes: {{ $accept ? $accept : 'null' }},\n allowFileSizeValidation: {{ $validate ? 'true' : 'false' }},\n maxFileSize: {!! $size ? \"'\" . $size . \"'\" : 'null' !! },\n maxFiles: {{ $number ? $number : 'null' }},\n required: {{ $required ? 'true' : 'false' }},\n disabled: {{ $disabled ? 'true' : 'false' }},\n onprocessfile : () => notify ()\n }); To add existing files, we use addFiles() method. pond. addFiles (files); And finally, we can add some events to handle errors and success messages. pond. on ( \"addfile\" , ( error , file ) => {\n if (error) {\n console. log ( \"Oh no\" );\n return ;\n }\n }); You can see FilePond documentation for more details. Livewire component You can create a Livewire component to create a form with FilePond field. php artisan make:livewire SettingsForm You need Livewire\\WithFileUploads trait to handle uploads and you can use $rules to add some validation rules for files. You have to set values with mount() method. 'nullable|image|max:2048' ,\n 'gallery.*' => 'nullable|image|mimes:jpg,jpeg,png|max:2048' ,\n ];\n \n public function mount ()\n {\n /** @var User */\n $user = Auth :: user ();\n \n $this -> avatar = $user -> avatar;\n $this -> gallery = $user -> gallery;\n }\n \n public function save ()\n {\n $this -> validate ();\n \n /** @var User */\n $user = Auth :: user ();\n \n // Save avatar and gallery\n }\n \n public function render ()\n {\n //\n }\n } And finally, you can create a view with FilePond field. < div >\n < x-field.upload name = \"avatar\" label = \"Avatar\" wire:model = \"avatar\" />\n < x-field.upload\n name = \"gallery\"\n label = \"Gallery\"\n wire:model = \"gallery\"\n multiple\n />\n < button wire:click = \"save\" >Save\n .ct-806137{color:#B392F0;}\n.ct-767297{color:#E1E4E8;}\n.ct-244866{color:#9ECBFF;}\n.ct-211346{color:#85E89D;}\n.ct-326375{color:#6A737D;}\n.ct-857441{color:#F97583;}\n.ct-533739{color:#79B8FF;}\n.ct-499703{color:#FDAEB7;font-style:italic;}\n.ct-657843{color:#FFAB70;}"},{"id":"content:en:articles:vue-3-svg.md","path":"/articles/vue-3-svg","dir":"articles","title":"SVG on Vue 3","description":"Manage SVG on Vue 3 / Nuxt 3","keywords":["Add Node dependency","Add SVG for test","Create component","Usage"],"body":" This guide use Vue 3 / Nuxt 3 with Vite . From github.com/jpkleemans/vite-svg-loader , you can use a specific module with Nuxt 3 if you want: github.com/gitFoxCode/nuxt-icons . Add Node dependency From github.com/jpkleemans/vite-svg-loader pnpm add vite-svg-loader -D npm install vite-svg-loader --save-dev yarn add vite-svg-loader -D Add to Vue 3 import svgLoader from \"vite-svg-loader\" ;\n \n export default defineConfig ({\n plugins: [\n vue (),\n svgLoader (), // https://github.com/jpkleemans/vite-svg-loader#readme\n ],\n }); Add to Nuxt 3 import { defineNuxtConfig } from \"nuxt\" ;\n import svgLoader from \"vite-svg-loader\" ;\n \n export default defineNuxtConfig ({\n vite: {\n plugins: [\n svgLoader (), // https://github.com/jpkleemans/vite-svg-loader#readme\n ],\n },\n }); Add SVG for test Create a directory like assets into your project and add a new SVG like github.svg . - assets\n - icons\n - github.svg\n - components\n - svg-icon.vue (soon)\n [vite|nuxt].config.ts Here an example of github.svg from https://icones.js.org < svg\n xmlns = \"http://www.w3.org/2000/svg\"\n xmlns:xlink = \"http://www.w3.org/1999/xlink\"\n aria-hidden = \"true\"\n role = \"img\"\n class = \"iconify iconify--mdi\"\n width = \"32\"\n height = \"32\"\n preserveAspectRatio = \"xMidYMid meet\"\n viewBox = \"0 0 24 24\"\n >\n < path\n d = \"M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33c.85 0 1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2Z\"\n >\n Remove all attributes like class , width or height to avoid conflicts of sizing and add fill=\"currentColor\" or stroke=\"currentColor\" to allow CSS color on SVG. < svg\n xmlns = \"http://www.w3.org/2000/svg\"\n xmlns:xlink = \"http://www.w3.org/1999/xlink\"\n aria-hidden = \"true\"\n role = \"img\"\n preserveAspectRatio = \"xMidYMid meet\"\n viewBox = \"0 0 24 24\"\n fill = \"currentColor\"\n >\n < path\n d = \"M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33c.85 0 1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2Z\"\n >\n Create component Watch out ../assets/icons/${props.name}.svg because path is relative to my stack, if your assets directory is not on same place, update path. < script setup lang = \"ts\" >\n const props = defineProps <{\n name ?: string ;\n }>();\n \n const currentIcon = computed (() =>\n defineAsyncComponent ({\n loader : () => import ( `../assets/icons/${ props . name }.svg` ),\n loadingComponent: {\n template: \"\" ,\n },\n errorComponent: {\n template: \"error\" ,\n },\n delay: 200 ,\n timeout: 3000 ,\n suspensible: true ,\n })\n );\n const attrs = useAttrs ();\n \n \n < template >\n < span >\n < component :is = \"currentIcon\" :class = \"attrs.class\" />\n \n Nuxt 3 For Nuxt 3, you have to add client-only . < template >\n < span >\n < client-only >\n < component :is = \"currentIcon\" :class = \"attrs.class\" />\n < template # fallback />\n \n \n Usage < template >\n < div >\n < svg-icon name = \"github\" class = \"w-6 h-6\" />\n \n Don't use subdirectories into assets/icons , because build will not compile nested SVG, put all your icons in root of icons . .ct-806137{color:#B392F0;}\n.ct-767297{color:#E1E4E8;}\n.ct-244866{color:#9ECBFF;}\n.ct-533739{color:#79B8FF;}\n.ct-857441{color:#F97583;}\n.ct-326375{color:#6A737D;}\n.ct-211346{color:#85E89D;}\n.ct-657843{color:#FFAB70;}\n.ct-499703{color:#FDAEB7;font-style:italic;}"},{"id":"content:en:projects:bookshelves.md","path":"/projects/bookshelves","dir":"projects","title":"Bookshelves","description":"Web application to handle eBooks.","keywords":[],"body":" Web application to handle eBooks. Amazing web application to handle eBooks."},{"id":"content:en:projects:fastify-utils.md","path":"/projects/fastify-utils","dir":"projects","title":"Fastify Utils","description":"Collection of utilities for fastify framework, built to improve DX.","keywords":[],"body":" Collection of utilities for fastify framework, built to improve DX."},{"id":"content:en:projects:kiwilan-node-filesystem.md","path":"/projects/kiwilan-node-filesystem","dir":"projects","title":"Kiwilan's Node Filesystem","description":"Node module to improve native filesystem with Laravel like helpers.","keywords":[],"body":" Node module to improve native filesystem with Laravel like helpers. Node package built to improve native filesystem with Laravel like helpers."},{"id":"content:en:projects:koguart.md","path":"/projects/koguart","dir":"projects","title":"Koguart","description":"Platform to share your miniatures (like Warhammers) or to search for the perfect miniature for your next project.","keywords":[],"body":" Platform to share your miniatures (like Warhammers) or to search for the perfect miniature for your next project."},{"id":"content:en:projects:memorandum.md","path":"/projects/memorandum","dir":"projects","title":"Memorandum","description":"Blog & documentation.","keywords":[],"body":" Blog & documentation."},{"id":"content:en:projects:nuxt-svg-transformer.md","path":"/projects/nuxt-svg-transformer","dir":"projects","title":"Nuxt SVG transformer","description":"Nuxt 3 module. Transform SVG to inject dynamically into Vue component, type included.","keywords":[],"body":" Nuxt 3 module. Transform SVG to inject dynamically into Vue component, type included."},{"id":"content:en:projects:nuxt-typed-link.md","path":"/projects/nuxt-typed-link","dir":"projects","title":"Nuxt TypedLink","description":"Nuxt 3 module. Add new component, TypedLink, to type your routes in templates.","keywords":[],"body":" Nuxt 3 module. Add new component, TypedLink, to type your routes in templates."},{"id":"content:en:projects:php-archive.md","path":"/projects/php-archive","dir":"projects","title":"PHP Archive","description":"PHP package to handle archives (.zip, .rar, .tar, .7z) or .pdf with hybrid solution.","keywords":["Requirements","Features","Installation","Usage","Testing","About","Changelog","Contributing","Security Vulnerabilities","Credits","License"],"body":" PHP package to handle archives (.zip, .rar, .tar, .7z) or .pdf with hybrid solution. PHP package to handle archives ( .zip , .rar , .tar , .7z ) or .pdf with hybrid solution (native/ p7zip ), designed to works with eBooks ( .epub , .cbz , .cbr , .cb7 , .cbt ). Supports Linux, macOS and Windows. For some formats ( .rar and .7z ) rar PHP extension or p7zip binary could be necessary, see Requirements . Requirements PHP >= 8.1 Depends of archive type and features you want to use Type Native Dependency .zip , .epub , .cbz ✅ N/A .tar , .tar.gz , .cbt ✅ N/A .rar , .cbr ❌ rar PHP extension or p7zip binary .7z , .cb7 ❌ p7zip binary .pdf ✅ Optional (for extraction) imagick PHP extension ALL ❌ p7zip binary ( rar PHP extension and imagick PHP extension are optional) Here you can read some installation guides for dependencies p7zip guide rar PHP extension guide imagick PHP extension guide On macOS , for .rar extract, you have to install rar binary to extract files, p7zip not support .rar extraction. On Windows , for .pdf extract, imagick PHP extension have to work but my tests failed on this feature . So to extract PDF pages I advice to use WSL . If you want more informations, you can read section About . Features List files as ArchiveItem array With files method: list of files With first method: first file With last method: last file With find method: find first file that match with path property With filter method: find all files that match with path property Content of file With content method: content of file as string (useful for images) With text method: content of text file (binaries files return null ) Extract files With extract method: extract files to directory With extractAll method: extract all files to directory Metadata of archive with title , author , subject , creator , creationDate , modDate , status and comment properties Useful for PDF files Count files Create or edit archives, only with .zip format With make method: create or edit archive With addFiles method: add files to archive With addFromString method: add string to archive With addDirectory and addDirectories methods: add directories to archive With save method: save archive Installation You can install the package via composer: composer require kiwilan/php-archive Usage Read and extract With archive file ( .zip , .rar , .tar , .7z , epub , cbz , cbr , cb7 , cbt , tar.gz , .pdf ). $archive = Archive :: test ( 'path/to/archive.zip' );\n \n $files = $archive -> files (); // ArchiveItem[]\n $count = $archive -> count (); // int of files count\n \n $images = $archive -> filter ( 'jpeg' ); // ArchiveItem[] with `jpeg` in their path\n $metadataXml = $archive -> find ( 'metadata.xml' ); // First ArchiveItem with `metadata.xml` in their path\n $content = $archive -> content ($metadataXml); // `metadata.xml` file content\n \n $paths = $archive -> extract ( '/path/to/directory' , [$metadataXml]); // string[] of extracted files paths\n $paths = $archive -> extractAll ( '/path/to/directory' ); // string[] of extracted files paths PDF files works with same API than archives but with some differences. $archive = Archive :: test ( 'path/to/file.pdf' );\n \n $metadata = $archive -> metadata (); // Metadata of PDF\n \n $content = $archive -> content ($archive -> first ()); // PDF page as image\n $text = $archive -> text ($archive -> first ()); // PDF page as text Create You can create archive with method Archive::make method. Works only with .zip archives. $archive = Archive :: make ( 'path/to/archive.zip' );\n $archive -> addFiles ([\n 'path/to/file1.txt' ,\n 'path/to/file2.txt' ,\n 'path/to/file3.txt' ,\n ]);\n $archive -> addFromString ( 'test.txt' , 'Hello World!' );\n $archive -> addDirectory ( 'path/to/directory' );\n $archive -> save (); Edit You can edit archive with same method Archive::make method. $archive = Archive :: make ( 'path/to/archive.zip' );\n $archive -> addFromString ( 'test.txt' , 'Hello World!' );\n $archive -> save (); Testing composer test About This package was inspired by this excellent post on StackOverflow which make state of the art of PHP archive handling. The package Gemorroj/Archive7z was also a good source of inspiration cause it's the only package that handle .7z archives with wrapper of p7zip fork binary. But I would to handle all main archives formats with native PHP solution it possible, and use p7zip binary only if native solution is not available. State of the art of PHP archive handling: .zip with ZipArchive .tar with PharData .rar with RarArchive if rar extension is installed .7z can't be handled with native PHP solution Type Is native Solution ZIP ✅ Native TAR ✅ Native RAR ❌ rar or p7zip binary 7ZIP ❌ p7zip binary PDF ❌ smalot/pdfparser Why not full wrapper of p7zip binary? This solution is used by Gemorroj/Archive7z , and it works well. But another problem is the usage of the p7zip fork which is not the official p7zip binary and can be difficult to install on some systems. PHP can handle natively some archive formats, but not all. So I choose to use native PHP solution when it's possible, and use p7zip binary with official version when it's not possible. Case of rar The rar PHP extension is not installed by default on PHP, developers have to install it manually. This extension is not actively maintained and users could have some compilation problems. To install it with PHP 8.1 or 8.2, it's necessary to compile manually the extension, you could read this guide if you want to install it (for PHP 8.2, you will have a warning message but it's not a problem, the extension will work). But rar PHP extension is a problem because it's not sure to have a compatibility with future PHP versions. So I choose to handle rar archives with p7zip binary if rar PHP extension is not installed. Case of 7zip PHP can't handle .7z archives natively, so I choose to use p7zip binary. You will have to install it on your system to use this package. You could read this guide if you want to install it. Case of pdf PHP can't handle .pdf archives natively, so I choose to use smalot/pdfparser package, embedded in this package. To extract pages as images, you have to install imagick extension you could read this guide if you want to install it. eBooks and comics This package can handle .epub , .cbz , .cbr , .cb7 , .cbt archives, it's depends of the extension, check requirements section. More Alternatives: Gemorroj/Archive7z : handle many archives with p7zip-project/p7zip binary splitbrain/php-archive : native PHP solution to handle .zip and .tar archives Documentation: List files in .7z , .rar and .tar archives using PHP: https://stackoverflow.com/a/39163620/11008206 Compression and Archive Extensions: https://www.php.net/manual/en/refs.compression.php Changelog Please see CHANGELOG for more information on what has changed recently. Contributing Please see CONTRIBUTING for details. Security Vulnerabilities Please review our security policy on how to report security vulnerabilities. Credits Kiwilan All Contributors spatie for spatie/package-skeleton-php smalot/pdfparser for PDF parser 7-zip for p7zip binary License The MIT License (MIT). Please see License File for more information. .ct-806137{color:#B392F0;}\n.ct-767297{color:#E1E4E8;}\n.ct-244866{color:#9ECBFF;}\n.ct-857441{color:#F97583;}\n.ct-533739{color:#79B8FF;}\n.ct-326375{color:#6A737D;}"},{"id":"content:en:projects:php-audio.md","path":"/projects/php-audio","dir":"projects","title":"PHP Audio","description":"PHP package to parse and update audio files metadata, with JamesHeinrich/getID3.","keywords":[],"body":" PHP package to parse and update audio files metadata, with JamesHeinrich/getID3 ."},{"id":"content:en:projects:php-ebook.md","path":"/projects/php-ebook","dir":"projects","title":"PHP eBook","description":"PHP package to read metadata and extract covers from eBooks (.epub, .cbz, .cbr, .cb7, .cbt, .pdf).","keywords":[],"body":" PHP package to read metadata and extract covers from eBooks (.epub, .cbz, .cbr, .cb7, .cbt, .pdf). PHP package to read metadata and extract covers from eBooks ( .epub , .cbz , .cbr , .cb7 , .cbt , .pdf ). Supports Linux, macOS and Windows. Works with kiwilan/php-archive , for some formats ( .cbr and .cb7 ) rar PHP extension or p7zip binary could be necessary, see Requirements ."},{"id":"content:en:projects:php-opds.md","path":"/projects/php-opds","dir":"projects","title":"PHP OPDS","description":"PHP package to create OPDS feed (Open Publication Distribution System) for eBooks.","keywords":[],"body":" PHP package to create OPDS feed (Open Publication Distribution System) for eBooks."},{"id":"content:en:projects:php-rss.md","path":"/projects/php-rss","dir":"projects","title":"PHP RSS","description":"PHP package to generate RSS feeds with presets.","keywords":[],"body":" PHP package to generate RSS feeds with presets."},{"id":"content:en:projects:php-xml-reader.md","path":"/projects/php-xml-reader","dir":"projects","title":"PHP XML Reader","description":"PHP package to read XML with nice API.","keywords":[],"body":" PHP package to read XML with nice API."},{"id":"content:en:projects:seeds.md","path":"/projects/seeds","dir":"projects","title":"Seeds","description":"API to use random pictures with seeders.","keywords":[],"body":" API to use random pictures with seeders."},{"id":"content:en:projects:social-oembed.md","path":"/projects/social-oembed","dir":"projects","title":"Social oEmbed","description":"API to offer OpenGraph meta or oEmbed media.","keywords":[],"body":" API to offer OpenGraph meta or oEmbed media."},{"id":"content:en:projects:typescriptable-laravel.md","path":"/projects/typescriptable-laravel","dir":"projects","title":"Typescriptable for Laravel","description":"PHP package for Laravel to type Eloquent models.","keywords":[],"body":" PHP package for Laravel to type Eloquent models. PHP package for Laravel to type Eloquent models and routes with autogenerated TypeScript, ready for Inertia with associated NPM package."},{"id":"content:en:terms.md","path":"/terms","dir":"","title":"Terms","description":"I agree that the information inserted in this form will be collected and kept for a maximum of two years, in accordance with the GDPR in force in Europe.","keywords":[],"body":" I agree that the information inserted in this form will be collected and kept for a maximum of two years, in accordance with the GDPR in force in Europe. This data will be used only to contact me and will not be transmitted to a third party. They will be destroyed when the period authorized by the GDPR is exceeded or if I make the request by this same form."}]