diff --git a/404.html b/404.html index fbcfc88b..9e8844ee 100644 --- a/404.html +++ b/404.html @@ -4,13 +4,13 @@ Page Not Found | Rekalogika.DEV - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/05b80167.9f6244af.js b/assets/js/05b80167.98de3201.js similarity index 82% rename from assets/js/05b80167.9f6244af.js rename to assets/js/05b80167.98de3201.js index b93b4260..a1c31692 100644 --- a/assets/js/05b80167.9f6244af.js +++ b/assets/js/05b80167.98de3201.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[559],{3905:(e,n,t)=>{t.d(n,{Zo:()=>c,kt:()=>h});var a=t(7294);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function o(e){for(var n=1;n=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var s=a.createContext({}),p=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):o(o({},n),e)),t},c=function(e){var n=p(e.components);return a.createElement(s.Provider,{value:n},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},d=a.forwardRef((function(e,n){var t=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),u=p(t),d=i,h=u["".concat(s,".").concat(d)]||u[d]||m[d]||r;return t?a.createElement(h,o(o({ref:n},c),{},{components:t})):a.createElement(h,o({ref:n},c))}));function h(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var r=t.length,o=new Array(r);o[0]=d;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[u]="string"==typeof e?e:i,o[1]=l;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var a=t(7462),i=(t(7294),t(3905));const r={title:"Manual Control"},o=void 0,l={unversionedId:"domain-event/manual-control",id:"domain-event/manual-control",title:"Manual Control",description:"To manually manage domain events, you can use",source:"@site/docs/domain-event/02-manual-control.md",sourceDirName:"domain-event",slug:"/domain-event/manual-control",permalink:"/domain-event/manual-control",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/domain-event/02-manual-control.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{title:"Manual Control"},sidebar:"docs",previous:{title:"Basic Usage",permalink:"/domain-event/basic-usage"},next:{title:"Immediate Dispatcher Handling & Troubleshooting",permalink:"/domain-event/immediate-dispatcher"}},s={},p=[{value:"Manual Dispatching",id:"manual-dispatching",level:2},{value:"Clearing Events",id:"clearing-events",level:2},{value:"Getting the Events in the Queue",id:"getting-the-events-in-the-queue",level:2},{value:"Immediate Dispatcher Installation",id:"immediate-dispatcher-installation",level:2}],c={toc:p},u="wrapper";function m(e){let{components:n,...t}=e;return(0,i.kt)(u,(0,a.Z)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"To manually manage domain events, you can use\n",(0,i.kt)("inlineCode",{parentName:"p"},"DomainEventAwareEntityManagerInterface")," in place of the regular\n",(0,i.kt)("inlineCode",{parentName:"p"},"EntityManagerInterface"),". It adds several methods to the Entity Manager that you\ncan use to manage domain event dispatching."),(0,i.kt)("h2",{id:"manual-dispatching"},"Manual Dispatching"),(0,i.kt)("p",null,"You can disable automatic dispatching on ",(0,i.kt)("inlineCode",{parentName:"p"},"flush()")," by calling\n",(0,i.kt)("inlineCode",{parentName:"p"},"setAutoDispatchDomainEvents(false)"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"/** @var DomainEventAwareEntityManagerInterface $entityManager */\n\n$entityManager->setAutoDispatchDomainEvents(false);\n\n// ...\n\n$entityManager->dispatchPreFlushDomainEvents();\n$entityManager->flush();\n$entityManager->dispatchPostFlushDomainEvents();\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Immediate dispatching is dispatched outside ",(0,i.kt)("inlineCode",{parentName:"p"},"DomainEventManager")," and\n",(0,i.kt)("inlineCode",{parentName:"p"},"DomainEventAwareEntityManager"),", and therefore unaffected by\n",(0,i.kt)("inlineCode",{parentName:"p"},"setAutoDispatchDomainEvents()"),".")),(0,i.kt)("h2",{id:"clearing-events"},"Clearing Events"),(0,i.kt)("p",null,"If the domain event queues are not empty at the end of the request,\n",(0,i.kt)("inlineCode",{parentName:"p"},"DomainEventManager")," will throw ",(0,i.kt)("inlineCode",{parentName:"p"},"UndispatchedEventsException"),". To prevent that\nfrom happening, if you disable auto-dispatch, you need to make sure that you\ndispatch both pre-flush and post-flush events as above. Alternatively, you can\nclear the events if you don't want them dispatched:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"/** @var DomainEventAwareEntityManagerInterface $entityManager */\n\n$entityManager->setAutoDispatchDomainEvents(false);\n\n// ...\n\n$entityManager->flush();\n$entityManager->clearDomainEvents();\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"In the event of an uncaught error, the framework will automatically\nclear undispatched events using the ",(0,i.kt)("inlineCode",{parentName:"p"},"kernel.exception")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"console.error"),"\nevents, so in such cases, you don't have to handle that manually. But if you\ncatch an exception that previously caused pending events not to be dispatched,\nyou need to manually clear the events.")),(0,i.kt)("h2",{id:"getting-the-events-in-the-queue"},"Getting the Events in the Queue"),(0,i.kt)("p",null,"You can get the undispatched events in the queue by calling ",(0,i.kt)("inlineCode",{parentName:"p"},"popDomainEvents()"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"/** @var DomainEventAwareEntityManagerInterface $entityManager */\n\n$events = $entityManager->popDomainEvents();\n")),(0,i.kt)("p",null,"This can be useful if you want to dispatch the events in another process, or\nstore them in a database, etc."),(0,i.kt)("h2",{id:"immediate-dispatcher-installation"},"Immediate Dispatcher Installation"),(0,i.kt)("p",null,"Immediate event dispatcher works by installing the event dispatcher to a static\nvariable. This installation happens on several opportunities:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"In these events: ",(0,i.kt)("inlineCode",{parentName:"li"},"kernel.request")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"console.command"),"."),(0,i.kt)("li",{parentName:"ul"},"During the initialization of ",(0,i.kt)("inlineCode",{parentName:"li"},"ManagerRegistry"),"."),(0,i.kt)("li",{parentName:"ul"},"During the initialization of an ",(0,i.kt)("inlineCode",{parentName:"li"},"EntityManagerInterface"),".")),(0,i.kt)("p",null,"When any of these don't occur, there is no opportunity to install the event\ndispatcher. This usually happens only in isolated unit tests. To fix the\nproblem, you can install a stub event dispatcher manually like the following."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use PHPUnit\\Framework\\TestCase;\nuse Rekalogika\\DomainEvent\\ImmediateDomainEventDispatcherInstaller;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcher;\n\nclass SomeTest extends TestCase\n{\n public function setUp(): void\n {\n $installer = new ImmediateDomainEventDispatcherInstaller(new EventDispatcher);\n $installer->install();\n\n }\n\n // ...\n}\n")),(0,i.kt)("p",null,"In integration tests where you have access to the service container, but the\ntests don't involve ",(0,i.kt)("inlineCode",{parentName:"p"},"EntityManager")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"ManagerRegistry"),", you can pull the\ninstaller from the container to install the immediate dispatcher:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\DomainEvent\\ImmediateDomainEventDispatcherInstaller;\nuse Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase;\n\nclass SomeTest extends KernelTestCase\n{\n public function setUp(): void\n {\n self::bootKernel();\n static::getContainer()\n ->get(ImmediateDomainEventDispatcherInstaller::class)->install();\n }\n\n // ...\n}\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[559],{3905:(e,n,t)=>{t.d(n,{Zo:()=>c,kt:()=>h});var a=t(7294);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function o(e){for(var n=1;n=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var s=a.createContext({}),p=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):o(o({},n),e)),t},c=function(e){var n=p(e.components);return a.createElement(s.Provider,{value:n},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},d=a.forwardRef((function(e,n){var t=e.components,i=e.mdxType,r=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),u=p(t),d=i,h=u["".concat(s,".").concat(d)]||u[d]||m[d]||r;return t?a.createElement(h,o(o({ref:n},c),{},{components:t})):a.createElement(h,o({ref:n},c))}));function h(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var r=t.length,o=new Array(r);o[0]=d;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[u]="string"==typeof e?e:i,o[1]=l;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var a=t(7462),i=(t(7294),t(3905));const r={title:"Manual Control"},o=void 0,l={unversionedId:"domain-event/manual-control",id:"domain-event/manual-control",title:"Manual Control",description:"To manually manage domain events, you can use",source:"@site/docs/domain-event/02-manual-control.md",sourceDirName:"domain-event",slug:"/domain-event/manual-control",permalink:"/domain-event/manual-control",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/domain-event/02-manual-control.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{title:"Manual Control"},sidebar:"docs",previous:{title:"Basic Usage",permalink:"/domain-event/basic-usage"},next:{title:"Immediate Dispatcher Handling & Troubleshooting",permalink:"/domain-event/immediate-dispatcher"}},s={},p=[{value:"Manual Dispatching",id:"manual-dispatching",level:2},{value:"Clearing Events",id:"clearing-events",level:2},{value:"Getting the Events in the Queue",id:"getting-the-events-in-the-queue",level:2},{value:"Immediate Dispatcher Installation",id:"immediate-dispatcher-installation",level:2}],c={toc:p},u="wrapper";function m(e){let{components:n,...t}=e;return(0,i.kt)(u,(0,a.Z)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"To manually manage domain events, you can use\n",(0,i.kt)("inlineCode",{parentName:"p"},"DomainEventAwareEntityManagerInterface")," in place of the regular\n",(0,i.kt)("inlineCode",{parentName:"p"},"EntityManagerInterface"),". It adds several methods to the Entity Manager that you\ncan use to manage domain event dispatching."),(0,i.kt)("h2",{id:"manual-dispatching"},"Manual Dispatching"),(0,i.kt)("p",null,"You can disable automatic dispatching on ",(0,i.kt)("inlineCode",{parentName:"p"},"flush()")," by calling\n",(0,i.kt)("inlineCode",{parentName:"p"},"setAutoDispatchDomainEvents(false)"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"/** @var DomainEventAwareEntityManagerInterface $entityManager */\n\n$entityManager->setAutoDispatchDomainEvents(false);\n\n// ...\n\n$entityManager->dispatchPreFlushDomainEvents();\n$entityManager->flush();\n$entityManager->dispatchPostFlushDomainEvents();\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Immediate dispatching is dispatched outside ",(0,i.kt)("inlineCode",{parentName:"p"},"DomainEventManager")," and\n",(0,i.kt)("inlineCode",{parentName:"p"},"DomainEventAwareEntityManager"),", and therefore unaffected by\n",(0,i.kt)("inlineCode",{parentName:"p"},"setAutoDispatchDomainEvents()"),".")),(0,i.kt)("h2",{id:"clearing-events"},"Clearing Events"),(0,i.kt)("p",null,"If the domain event queues are not empty at the end of the request,\n",(0,i.kt)("inlineCode",{parentName:"p"},"DomainEventManager")," will throw ",(0,i.kt)("inlineCode",{parentName:"p"},"UndispatchedEventsException"),". To prevent that\nfrom happening, if you disable auto-dispatch, you need to make sure that you\ndispatch both pre-flush and post-flush events as above. Alternatively, you can\nclear the events if you don't want them dispatched:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"/** @var DomainEventAwareEntityManagerInterface $entityManager */\n\n$entityManager->setAutoDispatchDomainEvents(false);\n\n// ...\n\n$entityManager->flush();\n$entityManager->clearDomainEvents();\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"In the event of an uncaught error, the framework will automatically\nclear undispatched events using the ",(0,i.kt)("inlineCode",{parentName:"p"},"kernel.exception")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"console.error"),"\nevents, so in such cases, you don't have to handle that manually. But if you\ncatch an exception that previously caused pending events not to be dispatched,\nyou need to manually clear the events.")),(0,i.kt)("h2",{id:"getting-the-events-in-the-queue"},"Getting the Events in the Queue"),(0,i.kt)("p",null,"You can get the undispatched events in the queue by calling ",(0,i.kt)("inlineCode",{parentName:"p"},"popDomainEvents()"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"/** @var DomainEventAwareEntityManagerInterface $entityManager */\n\n$events = $entityManager->popDomainEvents();\n")),(0,i.kt)("p",null,"This can be useful if you want to dispatch the events in another process, or\nstore them in a database, etc."),(0,i.kt)("h2",{id:"immediate-dispatcher-installation"},"Immediate Dispatcher Installation"),(0,i.kt)("p",null,"Immediate event dispatcher works by installing the event dispatcher to a static\nvariable. This installation happens on several opportunities:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"In these events: ",(0,i.kt)("inlineCode",{parentName:"li"},"kernel.request")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"console.command"),"."),(0,i.kt)("li",{parentName:"ul"},"During the initialization of ",(0,i.kt)("inlineCode",{parentName:"li"},"ManagerRegistry"),"."),(0,i.kt)("li",{parentName:"ul"},"During the initialization of an ",(0,i.kt)("inlineCode",{parentName:"li"},"EntityManagerInterface"),".")),(0,i.kt)("p",null,"When none of these occurs, there is no opportunity to install the event\ndispatcher. This usually happens only in isolated unit tests. To fix the\nproblem, you can install a stub event dispatcher manually like the following."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use PHPUnit\\Framework\\TestCase;\nuse Rekalogika\\DomainEvent\\ImmediateDomainEventDispatcherInstaller;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcher;\n\nclass SomeTest extends TestCase\n{\n public function setUp(): void\n {\n $installer = new ImmediateDomainEventDispatcherInstaller(new EventDispatcher);\n $installer->install();\n\n }\n\n // ...\n}\n")),(0,i.kt)("p",null,"In integration tests where you have access to the service container, but the\ntests don't involve ",(0,i.kt)("inlineCode",{parentName:"p"},"EntityManager")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"ManagerRegistry"),", you can pull the\ninstaller from the container to install the immediate dispatcher:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\DomainEvent\\ImmediateDomainEventDispatcherInstaller;\nuse Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase;\n\nclass SomeTest extends KernelTestCase\n{\n public function setUp(): void\n {\n self::bootKernel();\n static::getContainer()\n ->get(ImmediateDomainEventDispatcherInstaller::class)->install();\n }\n\n // ...\n}\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/08447aa8.1593ff7d.js b/assets/js/08447aa8.798509ca.js similarity index 58% rename from assets/js/08447aa8.1593ff7d.js rename to assets/js/08447aa8.798509ca.js index 5c156d46..990fe516 100644 --- a/assets/js/08447aa8.1593ff7d.js +++ b/assets/js/08447aa8.798509ca.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[947],{3905:(e,t,n)=>{n.d(t,{Zo:()=>m,kt:()=>u});var i=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function s(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var o=i.createContext({}),p=function(e){var t=i.useContext(o),n=t;return e&&(n="function"==typeof e?e(t):s(s({},t),e)),n},m=function(e){var t=p(e.components);return i.createElement(o.Provider,{value:t},e.children)},c="mdxType",f={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},y=i.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,o=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),c=p(n),y=r,u=c["".concat(o,".").concat(y)]||c[y]||f[y]||a;return n?i.createElement(u,s(s({ref:t},m),{},{components:n})):i.createElement(u,s({ref:t},m))}));function u(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,s=new Array(a);s[0]=y;var l={};for(var o in t)hasOwnProperty.call(t,o)&&(l[o]=t[o]);l.originalType=e,l[c]="string"==typeof e?e:r,s[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>m,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>p,toc:()=>c});var i=n(7462),r=(n(7294),n(3905)),a=n(4996),s=n(941);const l={title:"Concepts & Terms"},o=void 0,p={unversionedId:"file/concepts",id:"file/concepts",title:"Concepts & Terms",description:"Terms",source:"@site/docs/file/02-concepts.md",sourceDirName:"file",slug:"/file/concepts",permalink:"/file/concepts",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/02-concepts.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{title:"Concepts & Terms"},sidebar:"docs",previous:{title:"Installation & Configuration",permalink:"/file/installation"},next:{title:"Using File & FileRepository",permalink:"/file/file"}},m={},c=[{value:"Terms",id:"terms",level:2},{value:"Class Diagram",id:"class-diagram",level:2},{value:"Keys vs Paths",id:"keys-vs-paths",level:2}],f={toc:c},y="wrapper";function u(e){let{components:t,...n}=e;return(0,r.kt)(y,(0,i.Z)({},f,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h2",{id:"terms"},"Terms"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"FileRepository"),": manages files in framework, implements\n",(0,r.kt)("inlineCode",{parentName:"li"},"FileRepositoryInterface"),"."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"File"),": a file in a Flysystem filesystem, implements ",(0,r.kt)("inlineCode",{parentName:"li"},"FileInterface"),". Each\nfile is identified by a filesystem identifier and a key. Null filesystem\nidentifier denotes that the file is in the local filesystem."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"FilePointer"),": a pointer to a file, implements ",(0,r.kt)("inlineCode",{parentName:"li"},"FilePointerInterface"),". Like\na file, a file pointer has a filesystem identifier and a key, but nothing\nelse."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"Filesystem"),": a Flysystem filesystem, implements Flysystem's\n",(0,r.kt)("inlineCode",{parentName:"li"},"FilesystemOperator"),". The caller should not use it directly, but use the\n",(0,r.kt)("inlineCode",{parentName:"li"},"FileRepository")," instead."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"Local filesystem"),": a special Flysystem filesystem initialized by the\nframework that points to unscoped local filesystem, using '/' as its root\nlocation.")),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"A Flysystem filesystem using ",(0,r.kt)("inlineCode",{parentName:"p"},"LocalFilesystemAdapter")," that is setup by\nthe user is not considered a local filesystem in this document.")),(0,r.kt)("h2",{id:"class-diagram"},"Class Diagram"),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"'Interface' in the names are stripped for brevity. Simple getters are represented by properties.")),(0,r.kt)(s.Z,{alt:"File classes",sources:{light:(0,a.Z)("/diagrams/light/file.svg"),dark:(0,a.Z)("/diagrams/dark/file.svg")},width:"100%",mdxType:"ThemedImage"}),(0,r.kt)("h2",{id:"keys-vs-paths"},"Keys vs Paths"),(0,r.kt)("p",null,"The library encourages using the concept of 'keys', not 'paths'. Although the\nkey can appear similar to a path, the main difference is that the filename is\nnot part of the key, but part of the file's metadata. The key is similar to the\nprimary key of a database table. You can change the 'name' field, but the ID\nusually stays the same."))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[947],{3905:(e,t,n)=>{n.d(t,{Zo:()=>m,kt:()=>u});var i=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function s(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var o=i.createContext({}),p=function(e){var t=i.useContext(o),n=t;return e&&(n="function"==typeof e?e(t):s(s({},t),e)),n},m=function(e){var t=p(e.components);return i.createElement(o.Provider,{value:t},e.children)},c="mdxType",f={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},y=i.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,o=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),c=p(n),y=r,u=c["".concat(o,".").concat(y)]||c[y]||f[y]||a;return n?i.createElement(u,s(s({ref:t},m),{},{components:n})):i.createElement(u,s({ref:t},m))}));function u(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,s=new Array(a);s[0]=y;var l={};for(var o in t)hasOwnProperty.call(t,o)&&(l[o]=t[o]);l.originalType=e,l[c]="string"==typeof e?e:r,s[1]=l;for(var p=2;p{n.r(t),n.d(t,{assets:()=>m,contentTitle:()=>o,default:()=>u,frontMatter:()=>l,metadata:()=>p,toc:()=>c});var i=n(7462),r=(n(7294),n(3905)),a=n(4996),s=n(941);const l={title:"Concepts & Terms"},o=void 0,p={unversionedId:"file/concepts",id:"file/concepts",title:"Concepts & Terms",description:"Terms",source:"@site/docs/file/02-concepts.md",sourceDirName:"file",slug:"/file/concepts",permalink:"/file/concepts",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/02-concepts.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{title:"Concepts & Terms"},sidebar:"docs",previous:{title:"Installation & Configuration",permalink:"/file/installation"},next:{title:"Using File & FileRepository",permalink:"/file/file"}},m={},c=[{value:"Terms",id:"terms",level:2},{value:"Class Diagram",id:"class-diagram",level:2},{value:"Keys vs Paths",id:"keys-vs-paths",level:2}],f={toc:c},y="wrapper";function u(e){let{components:t,...n}=e;return(0,r.kt)(y,(0,i.Z)({},f,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h2",{id:"terms"},"Terms"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"FileRepository"),": Manages files in the framework. Implements\n",(0,r.kt)("inlineCode",{parentName:"li"},"FileRepositoryInterface"),"."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"File"),": A file in a Flysystem filesystem. Implements ",(0,r.kt)("inlineCode",{parentName:"li"},"FileInterface"),". Each\nfile is identified by a filesystem identifier and a key. A null filesystem\nidentifier denotes that the file is in the local filesystem."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"FilePointer"),": A pointer to a file. Implements ",(0,r.kt)("inlineCode",{parentName:"li"},"FilePointerInterface"),". Like\na file, a file pointer has a filesystem identifier and a key, but nothing\nelse."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"Filesystem"),": A Flysystem filesystem. Implements Flysystem's\n",(0,r.kt)("inlineCode",{parentName:"li"},"FilesystemOperator"),". The caller should not use it directly but use the\n",(0,r.kt)("inlineCode",{parentName:"li"},"FileRepository")," instead."),(0,r.kt)("li",{parentName:"ul"},(0,r.kt)("strong",{parentName:"li"},"Local filesystem"),": A special Flysystem filesystem initialized by the\nframework that points to an unscoped local filesystem, using '/' as its root\nlocation.")),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"A Flysystem filesystem using ",(0,r.kt)("inlineCode",{parentName:"p"},"LocalFilesystemAdapter")," that is set up by the user\nis not considered a local filesystem in this document.")),(0,r.kt)("h2",{id:"class-diagram"},"Class Diagram"),(0,r.kt)("admonition",{type:"note"},(0,r.kt)("p",{parentName:"admonition"},"'Interface' in the names are stripped for brevity. Simple getters are\nrepresented by properties.")),(0,r.kt)(s.Z,{alt:"File classes",sources:{light:(0,a.Z)("/diagrams/light/file.svg"),dark:(0,a.Z)("/diagrams/dark/file.svg")},width:"100%",mdxType:"ThemedImage"}),(0,r.kt)("h2",{id:"keys-vs-paths"},"Keys vs Paths"),(0,r.kt)("p",null,"The library encourages using the concept of 'keys', not 'paths'. Although the\nkey can appear similar to a path, the main difference is that the filename is\nnot part of the key, but part of the file's metadata. The key is similar to the\nprimary key of a database table. You can change the 'name' field, but the ID\nusually stays the same."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/39dae2a3.37e7c2f3.js b/assets/js/39dae2a3.37e7c2f3.js deleted file mode 100644 index 6e2410d8..00000000 --- a/assets/js/39dae2a3.37e7c2f3.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[769],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>u});var i=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=i.createContext({}),c=function(e){var t=i.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return i.createElement(s.Provider,{value:t},e.children)},f="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},d=i.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),f=c(n),d=r,u=f["".concat(s,".").concat(d)]||f[d]||h[d]||a;return n?i.createElement(u,o(o({ref:t},p),{},{components:n})):i.createElement(u,o({ref:t},p))}));function u(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,o=new Array(a);o[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[f]="string"==typeof e?e:r,o[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>l,toc:()=>c});var i=n(7462),r=(n(7294),n(3905));const a={title:"File Association Internal Details"},o=void 0,l={unversionedId:"file-bundle/entity-association-internal",id:"file-bundle/entity-association-internal",title:"File Association Internal Details",description:"Where The Files Are Stored",source:"@site/docs/file-bundle/21-entity-association-internal.md",sourceDirName:"file-bundle",slug:"/file-bundle/entity-association-internal",permalink:"/file-bundle/entity-association-internal",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/21-entity-association-internal.md",tags:[],version:"current",sidebarPosition:21,frontMatter:{title:"File Association Internal Details"},sidebar:"docs",previous:{title:"Filtering",permalink:"/file-bundle/filtering"},next:{title:"Creating Filters",permalink:"/file-bundle/creating-filters"}},s={},c=[{value:"Where The Files Are Stored",id:"where-the-files-are-stored",level:2},{value:"About File Names",id:"about-file-names",level:2},{value:"How It Works",id:"how-it-works",level:2},{value:"Architecture",id:"architecture",level:2}],p={toc:c},f="wrapper";function h(e){let{components:t,...n}=e;return(0,r.kt)(f,(0,i.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h2",{id:"where-the-files-are-stored"},"Where The Files Are Stored"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"FileLocationResolverInterface")," decides where to store the file. It takes the\nentity instance and the name of the property holding the file, and outputs a\n",(0,r.kt)("inlineCode",{parentName:"p"},"FilePointer")," describing where the file in that property will be stored. The\ndefault implementation ",(0,r.kt)("inlineCode",{parentName:"p"},"DefaultFileLocationResolver")," stores files into the\nfilesystem with the identifier 'default' and the key similar to the following:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337\n\u2570----\u256f \u2570--------------------------------------\u256f \u2570---\u256f \u2570---------\u256f \u2570---\u256f\n A B C D E\n")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"A: Prefix, defaults to 'entity'."),(0,r.kt)("li",{parentName:"ul"},"B: SHA-1 hash of the entity's fully-qualified class name."),(0,r.kt)("li",{parentName:"ul"},"C: Property name."),(0,r.kt)("li",{parentName:"ul"},"D: Hashed directories of the entity's ID. The ID is hashed using SHA-1, then\nsplitted by 2 characters each. Then, the first four of them are taken to form\nthe directory structure."),(0,r.kt)("li",{parentName:"ul"},"E: The entity ID.")),(0,r.kt)("p",null,"This default should be sufficient in most cases, for all entities, and all\nfilesystems. It masks internal details (entity class names). It does not pile\ntoo many files in one directory (some filesystems struggle with huge amount of\nfiles in a directory). The ordering is chosen to make it easier for manual\nadministration tasks."),(0,r.kt)("p",null,"To obtain the entity's ID, ",(0,r.kt)("inlineCode",{parentName:"p"},"DefaultFileLocationResolver")," calls\n",(0,r.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),". By default, it is ",(0,r.kt)("inlineCode",{parentName:"p"},"DefaultObjectIdResolver")," which\ncalls ",(0,r.kt)("inlineCode",{parentName:"p"},"getId()")," of the entity."),(0,r.kt)("p",null,"To override this default behavior, you can create your own implementation of\neither ",(0,r.kt)("inlineCode",{parentName:"p"},"FileLocationResolverInterface")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),". If you\nare using autoconfiguration, then you are good to go. Otherwise, you need to\ntag them in the service container:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-yaml"},"services:\n App\\MyFileLocationResolver:\n tags:\n - { name: 'rekalogika.file.association.file_location_resolver' }\n App\\MyObjectIdResolver:\n tags:\n - { name: 'rekalogika.file.association.object_id_resolver' }\n")),(0,r.kt)("h2",{id:"about-file-names"},"About File Names"),(0,r.kt)("p",null,"Like modern key-value cloud storage services, this framework uses the concept of\n'keys', not 'paths'. The file name is not part of the key, but stored in the\nmetadata, along with other properties of the file. The original file name is\nnever taken into consideration when determining where to store the file."),(0,r.kt)("p",null,"The metadata itself is stored in a sidecar file. Using the example above, the\nmetadata will be stored in this location:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337.metadata\n")),(0,r.kt)("p",null,"The caller can obtain the file name using the appropriate methods:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"$imageFilename = $entity->getImage()?->getName();\n")),(0,r.kt)("p",null,"When possible, the framework should have copied the file name of the original\nfile to the destination metadata when the file was first associated with the\nentity."),(0,r.kt)("h2",{id:"how-it-works"},"How It Works"),(0,r.kt)("p",null,"The storage key of the file is deterministic. It is determined only by the\nobject's class name, the object's ID and the name of the property containing the\nfile. As long as those don't change, the key will remain the same."),(0,r.kt)("p",null,"When persisting an entity, the framework will calculate the destination storage\nkey of every applicable property of the entity, and compare it to the current\nfile residing on each property:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"If both are the same, the framework leaves it alone."),(0,r.kt)("li",{parentName:"ul"},"If they are different, the framework will copy the file from the entity to the\nstorage destination."),(0,r.kt)("li",{parentName:"ul"},"If null, the framework will attempt to remove the file from the storage,\nirrespective of whether the file exists or not.")),(0,r.kt)("h2",{id:"architecture"},"Architecture"),(0,r.kt)("p",null,"In a nutshell: Doctrine Unit Of Work \u27a1\ufe0f Doctrine Events \u27a1\ufe0f\nrekalogika/reconstitutor \u27a1\ufe0f ",(0,r.kt)("inlineCode",{parentName:"p"},"InterfaceReconstitutor")," & ",(0,r.kt)("inlineCode",{parentName:"p"},"AttributeReconstitutor"),"\n\u27a1\ufe0f ",(0,r.kt)("inlineCode",{parentName:"p"},"FileAssociationManager")," \u27a1\ufe0f ",(0,r.kt)("inlineCode",{parentName:"p"},"FileRepository")," (from rekalogika/file)."),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"InterfaceReconstitutor")," & ",(0,r.kt)("inlineCode",{parentName:"p"},"AttributeReconstitutor")," are the entry points of this\npackage. They execute methods of ",(0,r.kt)("inlineCode",{parentName:"p"},"FileAssociationManager")," which work with the\nentities and ",(0,r.kt)("inlineCode",{parentName:"p"},"FileRepository")," to manage the association between the entities and\nfiles."),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"InterfaceReconstitutor")," & ",(0,r.kt)("inlineCode",{parentName:"p"},"AttributeReconstitutor")," are registered to the\nservice container so that they are called by our ",(0,r.kt)("inlineCode",{parentName:"p"},"rekalogika/reconstitutor")," when\nthe relevant events are being emitted by Doctrine. The service configuration is\ndone by the package ",(0,r.kt)("inlineCode",{parentName:"p"},"rekalogika/file-bundle"),"."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/39dae2a3.55733114.js b/assets/js/39dae2a3.55733114.js new file mode 100644 index 00000000..aca44c49 --- /dev/null +++ b/assets/js/39dae2a3.55733114.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[769],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>d});var i=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=i.createContext({}),c=function(e){var t=i.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return i.createElement(s.Provider,{value:t},e.children)},f="mdxType",h={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},u=i.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),f=c(n),u=r,d=f["".concat(s,".").concat(u)]||f[u]||h[u]||a;return n?i.createElement(d,o(o({ref:t},p),{},{components:n})):i.createElement(d,o({ref:t},p))}));function d(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,o=new Array(a);o[0]=u;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[f]="string"==typeof e?e:r,o[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>l,toc:()=>c});var i=n(7462),r=(n(7294),n(3905));const a={title:"File Association Internal Details"},o=void 0,l={unversionedId:"file-bundle/entity-association-internal",id:"file-bundle/entity-association-internal",title:"File Association Internal Details",description:"Where The Files Are Stored",source:"@site/docs/file-bundle/21-entity-association-internal.md",sourceDirName:"file-bundle",slug:"/file-bundle/entity-association-internal",permalink:"/file-bundle/entity-association-internal",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/21-entity-association-internal.md",tags:[],version:"current",sidebarPosition:21,frontMatter:{title:"File Association Internal Details"},sidebar:"docs",previous:{title:"Filtering",permalink:"/file-bundle/filtering"},next:{title:"Creating Filters",permalink:"/file-bundle/creating-filters"}},s={},c=[{value:"Where The Files Are Stored",id:"where-the-files-are-stored",level:2},{value:"About File Names",id:"about-file-names",level:2},{value:"How It Works",id:"how-it-works",level:2},{value:"Architecture",id:"architecture",level:2}],p={toc:c},f="wrapper";function h(e){let{components:t,...n}=e;return(0,r.kt)(f,(0,i.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h2",{id:"where-the-files-are-stored"},"Where The Files Are Stored"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"FileLocationResolverInterface")," decides where to store the file. It takes the\nentity instance and the name of the property holding the file and outputs a\n",(0,r.kt)("inlineCode",{parentName:"p"},"FilePointer")," describing where the file in that property will be stored. The\ndefault implementation ",(0,r.kt)("inlineCode",{parentName:"p"},"DefaultFileLocationResolver")," stores files into the\nfilesystem with the identifier 'default' and the key similar to the following:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337\n\u2570----\u256f \u2570--------------------------------------\u256f \u2570---\u256f \u2570---------\u256f \u2570---\u256f\n A B C D E\n")),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"A: Prefix, defaults to 'entity'."),(0,r.kt)("li",{parentName:"ul"},"B: SHA-1 hash of the entity's fully-qualified class name."),(0,r.kt)("li",{parentName:"ul"},"C: Property name."),(0,r.kt)("li",{parentName:"ul"},"D: Hashed directories of the entity's ID. The ID is hashed using SHA-1, then\nsplit by 2 characters each. Then, the first four of them are taken to form\nthe directory structure."),(0,r.kt)("li",{parentName:"ul"},"E: The entity ID.")),(0,r.kt)("p",null,"This default should be sufficient in most cases, for all entities, and all\nfilesystems. It masks internal details (entity class names). It does not pile\ntoo many files in one directory (some filesystems struggle with a huge amount of\nfiles in a directory). The ordering is chosen to make it easier for manual\nadministration tasks."),(0,r.kt)("p",null,"To obtain the entity's ID, ",(0,r.kt)("inlineCode",{parentName:"p"},"DefaultFileLocationResolver")," calls\n",(0,r.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),". By default, it is ",(0,r.kt)("inlineCode",{parentName:"p"},"DefaultObjectIdResolver")," which\ncalls ",(0,r.kt)("inlineCode",{parentName:"p"},"getId()")," of the entity."),(0,r.kt)("p",null,"To override this default behavior, you can create your own implementation of\neither ",(0,r.kt)("inlineCode",{parentName:"p"},"FileLocationResolverInterface")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),". If you\nare using autoconfiguration, then you are good to go. Otherwise, you need to\ntag them in the service container:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-yaml"},"services:\n App\\MyFileLocationResolver:\n tags:\n - { name: 'rekalogika.file.association.file_location_resolver' }\n App\\MyObjectIdResolver:\n tags:\n - { name: 'rekalogika.file.association.object_id_resolver' }\n")),(0,r.kt)("h2",{id:"about-file-names"},"About File Names"),(0,r.kt)("p",null,"Like modern key-value cloud storage services, this framework uses the concept of\n'keys', not 'paths'. The file name is not part of the key but is stored in the\nmetadata, along with other properties of the file. The original file name is\nnever taken into consideration when determining where to store the file."),(0,r.kt)("p",null,"The metadata itself is stored in a sidecar file. Using the example above, the\nmetadata will be stored in this location:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337.metadata\n")),(0,r.kt)("p",null,"The caller can obtain the file name using the appropriate methods:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"$imageFilename = $entity->getImage()?->getName();\n")),(0,r.kt)("p",null,"When possible, the framework should have copied the file name of the original\nfile to the destination metadata when the file was first associated with the\nentity."),(0,r.kt)("h2",{id:"how-it-works"},"How It Works"),(0,r.kt)("p",null,"The storage key of the file is deterministic. It is determined only by the\nobject's class name, the object's ID, and the name of the property containing the\nfile. As long as those don't change, the key will remain the same."),(0,r.kt)("p",null,"When persisting an entity, the framework will calculate the destination storage\nkey of every applicable property of the entity, and compare it to the current\nfile residing on each property:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"If both are the same, the framework leaves it alone."),(0,r.kt)("li",{parentName:"ul"},"If they are different, the framework will copy the file from the entity to the\nstorage destination."),(0,r.kt)("li",{parentName:"ul"},"If null, the framework will attempt to remove the file from the storage,\nirrespective of whether the file exists or not.")),(0,r.kt)("h2",{id:"architecture"},"Architecture"),(0,r.kt)("p",null,"In a nutshell: Doctrine Unit Of Work \u27a1\ufe0f Doctrine Events \u27a1\ufe0f\nrekalogika/reconstitutor \u27a1\ufe0f ",(0,r.kt)("inlineCode",{parentName:"p"},"InterfaceReconstitutor")," & ",(0,r.kt)("inlineCode",{parentName:"p"},"AttributeReconstitutor"),"\n\u27a1\ufe0f ",(0,r.kt)("inlineCode",{parentName:"p"},"FileAssociationManager")," \u27a1\ufe0f ",(0,r.kt)("inlineCode",{parentName:"p"},"FileRepository")," (from rekalogika/file)."),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"InterfaceReconstitutor")," & ",(0,r.kt)("inlineCode",{parentName:"p"},"AttributeReconstitutor")," are the entry points of this\npackage. They execute methods of ",(0,r.kt)("inlineCode",{parentName:"p"},"FileAssociationManager")," which works with the\nentities and ",(0,r.kt)("inlineCode",{parentName:"p"},"FileRepository")," to manage the association between the entities and\nfiles."),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"InterfaceReconstitutor")," & ",(0,r.kt)("inlineCode",{parentName:"p"},"AttributeReconstitutor")," are registered to the\nservice container so that they are called by our ",(0,r.kt)("inlineCode",{parentName:"p"},"rekalogika/reconstitutor")," when\nthe relevant events are being emitted by Doctrine. The service configuration is\ndone by the package ",(0,r.kt)("inlineCode",{parentName:"p"},"rekalogika/file-bundle"),"."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4215e4a8.9836e004.js b/assets/js/4215e4a8.49f2616c.js similarity index 66% rename from assets/js/4215e4a8.9836e004.js rename to assets/js/4215e4a8.49f2616c.js index 7b127212..23e97cf2 100644 --- a/assets/js/4215e4a8.9836e004.js +++ b/assets/js/4215e4a8.49f2616c.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[441],{3905:(e,t,r)=>{r.d(t,{Zo:()=>u,kt:()=>d});var a=r(7294);function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,a)}return r}function o(e){for(var t=1;t=0||(n[r]=e[r]);return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}var s=a.createContext({}),p=function(e){var t=a.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):o(o({},t),e)),r},u=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},c="mdxType",f={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var r=e.components,n=e.mdxType,i=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),c=p(r),m=n,d=c["".concat(s,".").concat(m)]||c[m]||f[m]||i;return r?a.createElement(d,o(o({ref:t},u),{},{components:r})):a.createElement(d,o({ref:t},u))}));function d(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var i=r.length,o=new Array(i);o[0]=m;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[c]="string"==typeof e?e:n,o[1]=l;for(var p=2;p{r.r(t),r.d(t,{assets:()=>u,contentTitle:()=>s,default:()=>d,frontMatter:()=>l,metadata:()=>p,toc:()=>c});var a=r(7462),n=(r(7294),r(3905)),i=r(4996),o=r(941);const l={title:"Introduction"},s=void 0,p={unversionedId:"file/intro",id:"file/intro",title:"Introduction",description:"High-level file abstraction library built on top of Flysystem. It lets you work",source:"@site/docs/file/00-intro.md",sourceDirName:"file",slug:"/file/intro",permalink:"/file/intro",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/00-intro.md",tags:[],version:"current",sidebarPosition:0,frontMatter:{title:"Introduction"},sidebar:"docs",previous:{title:"rekalogika/file",permalink:"/file/"},next:{title:"Installation & Configuration",permalink:"/file/installation"}},u={},c=[{value:"Features",id:"features",level:2},{value:"General Features",id:"general-features",level:3},{value:"Interoperability Features",id:"interoperability-features",level:3},{value:"Components",id:"components",level:2},{value:"License",id:"license",level:2},{value:"Contributing",id:"contributing",level:2}],f={toc:c},m="wrapper";function d(e){let{components:t,...r}=e;return(0,n.kt)(m,(0,a.Z)({},f,r,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("p",null,"High-level file abstraction library built on top of Flysystem. It lets you work\nwith file objects in an object-oriented manner. A file object represents a file\nin a Flysystem filesystem. It can be a local file or a file in a cloud storage,\nthe library lets you work with them in the same way."),(0,n.kt)("h2",{id:"features"},"Features"),(0,n.kt)("h3",{id:"general-features"},"General Features"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Rich, high-level abstraction of files built on top of Flysystem."),(0,n.kt)("li",{parentName:"ul"},"Abstractions for file name and media type (MIME type)."),(0,n.kt)("li",{parentName:"ul"},"Caches and stores metadata in a sidecar file. Uniform metadata support across\nall filesystems."),(0,n.kt)("li",{parentName:"ul"},"Uses the repository pattern for files."),(0,n.kt)("li",{parentName:"ul"},"Remote fa\xe7ade pattern in accessing metadata, improves performance with remote\nfilesystems. Two metadata queries require only one round trip."),(0,n.kt)("li",{parentName:"ul"},"Rich metadata support."),(0,n.kt)("li",{parentName:"ul"},"Option to use lazy-loading proxy for files."),(0,n.kt)("li",{parentName:"ul"},"Support for file derivations."),(0,n.kt)("li",{parentName:"ul"},"Separated contracts and implementation. Useful for enforcing architectural\nboundaries. Your domain models doesn't have to depend on the framework.")),(0,n.kt)("h3",{id:"interoperability-features"},"Interoperability Features"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Adapters for Symfony HttpFoundation, Form, and Validator."),(0,n.kt)("li",{parentName:"ul"},"Adapter for OneupUploaderBundle.")),(0,n.kt)("h2",{id:"components"},"Components"),(0,n.kt)("p",null,"The File framework consists of several components."),(0,n.kt)(o.Z,{alt:"File classes",sources:{light:(0,i.Z)("/diagrams/light/file-components.svg"),dark:(0,i.Z)("/diagrams/dark/file-components.svg")},width:"100%",mdxType:"ThemedImage"}),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file"),": The core library. It provides the file abstraction and\nmetadata support."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-bundle"),": Integrates the library with Symfony."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-association"),": Provides support for associating files with\nDoctrine entities."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-contracts"),": Contains the interfaces and contracts used by\nthe library."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-derivation"),": Library for creating derived files."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-image"),": Provides image resizing filter."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-metadata-contracts"),": Contains additional interfaces\ndescribing file metadata."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-oneup-uploader-bridge"),": Adapter for OneupUploaderBundle."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-server"),": Temporary URL server for files."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-symfony-bridge"),": Adapter for Symfony HttpFoundation, Form, and\nValidator.")),(0,n.kt)("h2",{id:"license"},"License"),(0,n.kt)("p",null,"MIT"),(0,n.kt)("h2",{id:"contributing"},"Contributing"),(0,n.kt)("p",null,"This framework consists of multiple repositories splitted from a monorepo. Be\nsure to submit issues and pull request to the\n",(0,n.kt)("a",{parentName:"p",href:"https://github.com/rekalogika/file-src"},"rekalogika/file-src")," monorepo."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[441],{3905:(e,t,r)=>{r.d(t,{Zo:()=>u,kt:()=>d});var a=r(7294);function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,a)}return r}function o(e){for(var t=1;t=0||(n[r]=e[r]);return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}var s=a.createContext({}),p=function(e){var t=a.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):o(o({},t),e)),r},u=function(e){var t=p(e.components);return a.createElement(s.Provider,{value:t},e.children)},c="mdxType",f={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var r=e.components,n=e.mdxType,i=e.originalType,s=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),c=p(r),m=n,d=c["".concat(s,".").concat(m)]||c[m]||f[m]||i;return r?a.createElement(d,o(o({ref:t},u),{},{components:r})):a.createElement(d,o({ref:t},u))}));function d(e,t){var r=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var i=r.length,o=new Array(i);o[0]=m;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[c]="string"==typeof e?e:n,o[1]=l;for(var p=2;p{r.r(t),r.d(t,{assets:()=>u,contentTitle:()=>s,default:()=>d,frontMatter:()=>l,metadata:()=>p,toc:()=>c});var a=r(7462),n=(r(7294),r(3905)),i=r(4996),o=r(941);const l={title:"Introduction"},s=void 0,p={unversionedId:"file/intro",id:"file/intro",title:"Introduction",description:"High-level file abstraction library built on top of Flysystem. It lets you work",source:"@site/docs/file/00-intro.md",sourceDirName:"file",slug:"/file/intro",permalink:"/file/intro",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/00-intro.md",tags:[],version:"current",sidebarPosition:0,frontMatter:{title:"Introduction"},sidebar:"docs",previous:{title:"rekalogika/file",permalink:"/file/"},next:{title:"Installation & Configuration",permalink:"/file/installation"}},u={},c=[{value:"Features",id:"features",level:2},{value:"General Features",id:"general-features",level:3},{value:"Interoperability Features",id:"interoperability-features",level:3},{value:"Components",id:"components",level:2},{value:"License",id:"license",level:2},{value:"Contributing",id:"contributing",level:2}],f={toc:c},m="wrapper";function d(e){let{components:t,...r}=e;return(0,n.kt)(m,(0,a.Z)({},f,r,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("p",null,"High-level file abstraction library built on top of Flysystem. It lets you work\nwith file objects in an object-oriented manner. A file object represents a file\nin a Flysystem filesystem. It can be a local file or a file in a cloud storage,\nthe library lets you work with them in the same way."),(0,n.kt)("h2",{id:"features"},"Features"),(0,n.kt)("h3",{id:"general-features"},"General Features"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Rich, high-level abstraction of files built on top of Flysystem."),(0,n.kt)("li",{parentName:"ul"},"Abstractions for file name and media type (MIME type)."),(0,n.kt)("li",{parentName:"ul"},"Caches and stores metadata in a sidecar file. Uniform metadata support across\nall filesystems."),(0,n.kt)("li",{parentName:"ul"},"Uses the repository pattern for files."),(0,n.kt)("li",{parentName:"ul"},"Remote fa\xe7ade pattern in accessing metadata. Improves performance with remote\nfilesystems. Two metadata queries require only one round trip."),(0,n.kt)("li",{parentName:"ul"},"Rich metadata support."),(0,n.kt)("li",{parentName:"ul"},"Option to use lazy-loading proxy for files."),(0,n.kt)("li",{parentName:"ul"},"Support for file derivations."),(0,n.kt)("li",{parentName:"ul"},"Separated contracts and implementation. Useful for enforcing architectural\nboundaries. Your domain models don't have to depend on the framework.")),(0,n.kt)("h3",{id:"interoperability-features"},"Interoperability Features"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Adapters for Symfony HttpFoundation, Form, and Validator."),(0,n.kt)("li",{parentName:"ul"},"Adapter for OneupUploaderBundle.")),(0,n.kt)("h2",{id:"components"},"Components"),(0,n.kt)("p",null,"The File framework consists of several components."),(0,n.kt)(o.Z,{alt:"File classes",sources:{light:(0,i.Z)("/diagrams/light/file-components.svg"),dark:(0,i.Z)("/diagrams/dark/file-components.svg")},width:"100%",mdxType:"ThemedImage"}),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file"),": The core library. It provides the file abstraction and\nmetadata support."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-bundle"),": Integrates the library with Symfony."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-association"),": Provides support for associating files with\nDoctrine entities."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-contracts"),": Contains the interfaces and contracts used by\nthe library."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-derivation"),": Library for creating derived files."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-image"),": Provides image resizing filter."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-metadata-contracts"),": Contains additional interfaces\ndescribing file metadata."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-oneup-uploader-bridge"),": Adapter for OneupUploaderBundle."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-server"),": Temporary URL server for files."),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("strong",{parentName:"li"},"rekalogika/file-symfony-bridge"),": Adapter for Symfony HttpFoundation, Form, and\nValidator.")),(0,n.kt)("h2",{id:"license"},"License"),(0,n.kt)("p",null,"MIT"),(0,n.kt)("h2",{id:"contributing"},"Contributing"),(0,n.kt)("p",null,"This framework consists of multiple repositories split from a monorepo. Be\nsure to submit issues and pull requests to the\n",(0,n.kt)("a",{parentName:"p",href:"https://github.com/rekalogika/file-src"},"rekalogika/file-src")," monorepo."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/498f3971.9a9dfb0e.js b/assets/js/498f3971.6c11ea81.js similarity index 88% rename from assets/js/498f3971.9a9dfb0e.js rename to assets/js/498f3971.6c11ea81.js index b2a59297..af1ad0ba 100644 --- a/assets/js/498f3971.9a9dfb0e.js +++ b/assets/js/498f3971.6c11ea81.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[569],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>m});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},p="mdxType",f={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,l=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),p=u(n),d=r,m=p["".concat(s,".").concat(d)]||p[d]||f[d]||l;return n?a.createElement(m,o(o({ref:t},c),{},{components:n})):a.createElement(m,o({ref:t},c))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=n.length,o=new Array(l);o[0]=d;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[p]="string"==typeof e?e:r,o[1]=i;for(var u=2;u{n.d(t,{Z:()=>o});var a=n(7294),r=n(6010);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.Z)(l.tabItem,o),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>w});var a=n(7462),r=n(7294),l=n(6010),o=n(2466),i=n(6550),s=n(1980),u=n(7392),c=n(12);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function f(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.k6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function y(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=f(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=m({queryString:n,groupId:a}),[p,y]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Nk)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),b=(()=>{const e=s??p;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&i(b)}),[b]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var b=n(2389);const h={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.o5)(),f=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==i&&(p(t),s(a))},d=e=>{let t=null;switch(e.key){case"Enter":f(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:f},o,{className:(0,l.Z)("tabs__item",h.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function k(e){const t=y(e);return r.createElement("div",{className:(0,l.Z)("tabs-container",h.tabList)},r.createElement(g,(0,a.Z)({},e,t)),r.createElement(v,(0,a.Z)({},e,t)))}function w(e){const t=(0,b.Z)();return r.createElement(k,(0,a.Z)({key:String(t)},e))}},497:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>f,frontMatter:()=>l,metadata:()=>i,toc:()=>u});var a=n(7462),r=(n(7294),n(3905));n(4866),n(5162);const l={title:"Installation & Configuration"},o=void 0,i={unversionedId:"file/installation",id:"file/installation",title:"Installation & Configuration",description:"This section explains how to install and configure the rekalogika/file",source:"@site/docs/file/01-installation.md",sourceDirName:"file",slug:"/file/installation",permalink:"/file/installation",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/01-installation.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{title:"Installation & Configuration"},sidebar:"docs",previous:{title:"Introduction",permalink:"/file/intro"},next:{title:"Concepts & Terms",permalink:"/file/concepts"}},s={},u=[{value:"Installation",id:"installation",level:2},{value:"Initialization",id:"initialization",level:2}],c={toc:u},p="wrapper";function f(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"This section explains how to install and configure the ",(0,r.kt)("inlineCode",{parentName:"p"},"rekalogika/file"),"\npackage in a PHP project."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"If you are developing a Symfony application, you might want to install the\nbundle instead. See\n",(0,r.kt)("a",{parentName:"p",href:"/file-bundle/installation"},(0,r.kt)("inlineCode",{parentName:"a"},"rekalogika/file-bundle"))," for more\ninformation.")),(0,r.kt)("h2",{id:"installation"},"Installation"),(0,r.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,r.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,r.kt)("p",null,"Open a command console, enter your project directory and execute:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-bundle\n")),(0,r.kt)("h2",{id:"initialization"},"Initialization"),(0,r.kt)("p",null,"In your application, initialize the file repository like the following example:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\FileFactory;\nuse League\\Flysystem\\Filesystem;\nuse League\\Flysystem\\Local\\LocalFilesystemAdapter;\n\n$fileFactory = new FileFactory(\n filesystems: [\n 'default' => new Filesystem(new LocalFilesystemAdapter('/var/storage')),\n ]\n);\n\n/** @var FileRepositoryInterface */\n$fileRepository = $fileFactory->getFileRepository();\n")),(0,r.kt)("p",null,"Read Flysystem documentation on how to initialize the filesystem. Once you have\na Flysystem filesystem, you can pass it to our ",(0,r.kt)("inlineCode",{parentName:"p"},"FileFactory"),". Then, use the\n",(0,r.kt)("inlineCode",{parentName:"p"},"FileFactory")," to create a ",(0,r.kt)("inlineCode",{parentName:"p"},"FileRepositoryInterface")," instance."))}f.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[569],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>m});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},p="mdxType",f={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,l=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),p=u(n),d=r,m=p["".concat(s,".").concat(d)]||p[d]||f[d]||l;return n?a.createElement(m,o(o({ref:t},c),{},{components:n})):a.createElement(m,o({ref:t},c))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=n.length,o=new Array(l);o[0]=d;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[p]="string"==typeof e?e:r,o[1]=i;for(var u=2;u{n.d(t,{Z:()=>o});var a=n(7294),r=n(6010);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.Z)(l.tabItem,o),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>w});var a=n(7462),r=n(7294),l=n(6010),o=n(2466),i=n(6550),s=n(1980),u=n(7392),c=n(12);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function f(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function d(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.k6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function y(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=f(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[s,u]=m({queryString:n,groupId:a}),[p,y]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Nk)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),b=(()=>{const e=s??p;return d({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{b&&i(b)}),[b]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!d({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),y(e)}),[u,y,l]),tabValues:l}}var b=n(2389);const h={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.o5)(),f=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==i&&(p(t),s(a))},d=e=>{let t=null;switch(e.key){case"Enter":f(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:f},o,{className:(0,l.Z)("tabs__item",h.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function k(e){const t=y(e);return r.createElement("div",{className:(0,l.Z)("tabs-container",h.tabList)},r.createElement(g,(0,a.Z)({},e,t)),r.createElement(v,(0,a.Z)({},e,t)))}function w(e){const t=(0,b.Z)();return r.createElement(k,(0,a.Z)({key:String(t)},e))}},497:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>f,frontMatter:()=>l,metadata:()=>i,toc:()=>u});var a=n(7462),r=(n(7294),n(3905));n(4866),n(5162);const l={title:"Installation & Configuration"},o=void 0,i={unversionedId:"file/installation",id:"file/installation",title:"Installation & Configuration",description:"This section explains how to install and configure the rekalogika/file",source:"@site/docs/file/01-installation.md",sourceDirName:"file",slug:"/file/installation",permalink:"/file/installation",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/01-installation.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{title:"Installation & Configuration"},sidebar:"docs",previous:{title:"Introduction",permalink:"/file/intro"},next:{title:"Concepts & Terms",permalink:"/file/concepts"}},s={},u=[{value:"Installation",id:"installation",level:2},{value:"Initialization",id:"initialization",level:2}],c={toc:u},p="wrapper";function f(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"This section explains how to install and configure the ",(0,r.kt)("inlineCode",{parentName:"p"},"rekalogika/file"),"\npackage in a PHP project."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"If you are developing a Symfony application, you might want to install the\nbundle instead. See\n",(0,r.kt)("a",{parentName:"p",href:"/file-bundle/installation"},(0,r.kt)("inlineCode",{parentName:"a"},"rekalogika/file-bundle"))," for more\ninformation.")),(0,r.kt)("h2",{id:"installation"},"Installation"),(0,r.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,r.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,r.kt)("p",null,"Open a command console, enter your project directory, and execute:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-bundle\n")),(0,r.kt)("h2",{id:"initialization"},"Initialization"),(0,r.kt)("p",null,"In your application, initialize the file repository like the following example:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\FileFactory;\nuse League\\Flysystem\\Filesystem;\nuse League\\Flysystem\\Local\\LocalFilesystemAdapter;\n\n$fileFactory = new FileFactory(\n filesystems: [\n 'default' => new Filesystem(new LocalFilesystemAdapter('/var/storage')),\n ]\n);\n\n/** @var FileRepositoryInterface */\n$fileRepository = $fileFactory->getFileRepository();\n")),(0,r.kt)("p",null,"Read Flysystem documentation on how to initialize the filesystem. Once you have\na Flysystem filesystem, you can pass it to our ",(0,r.kt)("inlineCode",{parentName:"p"},"FileFactory"),". Then, use the\n",(0,r.kt)("inlineCode",{parentName:"p"},"FileFactory")," to create a ",(0,r.kt)("inlineCode",{parentName:"p"},"FileRepositoryInterface")," instance."))}f.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4ef8bca5.028366d3.js b/assets/js/4ef8bca5.527fde67.js similarity index 54% rename from assets/js/4ef8bca5.028366d3.js rename to assets/js/4ef8bca5.527fde67.js index 83b87eb1..fce240cc 100644 --- a/assets/js/4ef8bca5.028366d3.js +++ b/assets/js/4ef8bca5.527fde67.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[903],{3905:(e,t,i)=>{i.d(t,{Zo:()=>f,kt:()=>g});var n=i(7294);function a(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function l(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function o(e){for(var t=1;t=0||(a[i]=e[i]);return a}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(a[i]=e[i])}return a}var s=n.createContext({}),p=function(e){var t=n.useContext(s),i=t;return e&&(i="function"==typeof e?e(t):o(o({},t),e)),i},f=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},c="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var i=e.components,a=e.mdxType,l=e.originalType,s=e.parentName,f=r(e,["components","mdxType","originalType","parentName"]),c=p(i),d=a,g=c["".concat(s,".").concat(d)]||c[d]||m[d]||l;return i?n.createElement(g,o(o({ref:t},f),{},{components:i})):n.createElement(g,o({ref:t},f))}));function g(e,t){var i=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var l=i.length,o=new Array(l);o[0]=d;var r={};for(var s in t)hasOwnProperty.call(t,s)&&(r[s]=t[s]);r.originalType=e,r[c]="string"==typeof e?e:a,o[1]=r;for(var p=2;p{i.r(t),i.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>l,metadata:()=>r,toc:()=>p});var n=i(7462),a=(i(7294),i(3905));const l={title:"Using File & FileRepository"},o=void 0,r={unversionedId:"file/file",id:"file/file",title:"Using File & FileRepository",description:"When using this framework, the user will primarily work with the",source:"@site/docs/file/03-file.md",sourceDirName:"file",slug:"/file/file",permalink:"/file/file",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/03-file.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{title:"Using File & FileRepository"},sidebar:"docs",previous:{title:"Concepts & Terms",permalink:"/file/concepts"},next:{title:"Adapters",permalink:"/file/adapters"}},s={},p=[{value:"Working With the File Repository",id:"working-with-the-file-repository",level:2},{value:"Create a file",id:"create-a-file",level:3},{value:"Get a file",id:"get-a-file",level:3},{value:"Delete a file",id:"delete-a-file",level:3},{value:"Copy and move a file",id:"copy-and-move-a-file",level:3},{value:"Create a temporary file",id:"create-a-temporary-file",level:3},{value:"Working With a File",id:"working-with-a-file",level:2},{value:"Reading the file's content",id:"reading-the-files-content",level:3},{value:"Writing to the file, replacing its content",id:"writing-to-the-file-replacing-its-content",level:3},{value:"Renaming the file",id:"renaming-the-file",level:3},{value:"Saving to a local file",id:"saving-to-a-local-file",level:3},{value:"Media type (MIME type) handling",id:"media-type-mime-type-handling",level:3},{value:"File size & last modified time",id:"file-size--last-modified-time",level:3},{value:"Image metadata",id:"image-metadata",level:3},{value:"HTTP metadata",id:"http-metadata",level:3},{value:"Flushing metadata",id:"flushing-metadata",level:3},{value:"File Pointer & comparison",id:"file-pointer--comparison",level:3}],f={toc:p},c="wrapper";function m(e){let{components:t,...i}=e;return(0,a.kt)(c,(0,n.Z)({},f,i,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"When using this framework, the user will primarily work with the\n",(0,a.kt)("inlineCode",{parentName:"p"},"FileRepositoryInterface")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"FileInterface")," objects."),(0,a.kt)("h2",{id:"working-with-the-file-repository"},"Working With the File Repository"),(0,a.kt)("h3",{id:"create-a-file"},"Create a file"),(0,a.kt)("admonition",{type:"caution"},(0,a.kt)("p",{parentName:"admonition"},"These methods overwrite the existing file if it already exists.")),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileRepositoryInterface;\nuse Rekalogika\\File\\FilePointer;\n\n/** @var FileRepositoryInterface $fileRepository */\n\n// Create a file from a string\n$file = $fileRepository->createFromString(\n new FilePointer('default', 'key'),\n 'Hello World!'\n);\n\n// Create a file from a stream (resource or PSR-7 StreamInterface)\n$file = $fileRepository->createFromStream(\n new FilePointer('default', 'key'),\n $stream\n);\n\n// Create a file from a local file\n$file = $fileRepository->createFromLocalFile(\n new FilePointer('default', 'key'),\n '/tmp/foo.txt'\n);\n\n")),(0,a.kt)("h3",{id:"get-a-file"},"Get a file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileRepositoryInterface;\nuse Rekalogika\\Contracts\\File\\Exception\\File\\FileNotFoundException;\nuse Rekalogika\\File\\FilePointer;\n\n/** @var FileRepositoryInterface $fileRepository */\n\n// get() will throw an exception if the file is not found\ntry {\n $file = $fileRepository->get(new FilePointer('default', 'key'));\n} catch (FileNotFoundException $e) {\n // File not found\n}\n\n// tryGet() will return null if the file is not found\n$file = $fileRepository->tryGet(new FilePointer('default', 'key'));\n\n// With a local file, you can also do it without using file repository:\ntry {\n $file = new File('/path/to/file');\n} catch (FileNotFoundException $e) {\n // File not found\n}\n")),(0,a.kt)("h3",{id:"delete-a-file"},"Delete a file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileRepositoryInterface;\nuse Rekalogika\\File\\FilePointer;\n\n/** @var FileRepositoryInterface $fileRepository */\n\n$fileRepository->delete(new FilePointer('default', 'key'));\n")),(0,a.kt)("h3",{id:"copy-and-move-a-file"},"Copy and move a file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileRepositoryInterface;\nuse Rekalogika\\File\\FilePointer;\n\n/** @var FileRepositoryInterface $fileRepository */\n\n$newFile = $fileRepository->copy(\n new FilePointer('default', 'key'),\n new FilePointer('otherfilesystem', 'destinationkey')\n);\n\n$newFile = $fileRepository->move(\n new FilePointer('default', 'key'),\n new FilePointer('otherfilesystem', 'destinationkey')\n);\n")),(0,a.kt)("admonition",{type:"tip"},(0,a.kt)("p",{parentName:"admonition"},"You can also use a ",(0,a.kt)("inlineCode",{parentName:"p"},"FileInterface")," as the origin or the destination\nof the move or copy operation.")),(0,a.kt)("h3",{id:"create-a-temporary-file"},"Create a temporary file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileRepositoryInterface;\n\n/** @var FileRepositoryInterface $fileRepository */\n\n$file = $fileRepository->createTemporaryFile();\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"The temporary file is represented by special ",(0,a.kt)("inlineCode",{parentName:"p"},"TemporaryFile")," that will be\nautomatically deleted if it is unset or falls out of scope.")),(0,a.kt)("h2",{id:"working-with-a-file"},"Working With a File"),(0,a.kt)("h3",{id:"reading-the-files-content"},"Reading the file's content"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// As a string\n$string = $file->getContent();\n\n// As a stream\n$stream = $file->getContentAsStream();\n\n// getContentAsStream() returns a PSR-7 StreamInterface, to get a plain PHP\n// resource, call detach() on it\n$resource = $stream->detach();\n")),(0,a.kt)("h3",{id:"writing-to-the-file-replacing-its-content"},"Writing to the file, replacing its content"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// From a string\n$file->setContent('Hello World!');\n\n// From a stream or resource\n$file->setContentFromStream($resource);\n")),(0,a.kt)("h3",{id:"renaming-the-file"},"Renaming the file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n$file->setName('my-photo.jpg');\n\n// If you omit the extension, the library will automatically choose the correct\n// extension based on the file's MIME type\n\n$file->setName('my-photo');\n$name = (string) $file->getName(); // my-photo.jpg\n\n// If you absolutely don't want an extension, you can set it directly to the\n// metadata\n\n$file->get(FileMetadataInterface::class)->setFileName('my-photo');\n$file->flush();\n\n// getName() returns FileNameInterface that provides several convenient methods\n// to get information about the filename\n\n$file->setName('foo.png');\n\n$name = (string) $file->getName(); // foo.png\n$fullName = $file->getName()->getFull(); // foo.png\n$baseName = $file->getName()->getBase(); // foo\n$extension = $file->getName()->getExtension(); // png\n$hasExtension = $file->getName()->hasExtension(); // true\n")),(0,a.kt)("h3",{id:"saving-to-a-local-file"},"Saving to a local file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// Saves the file to /tmp/foo.txt\n$localFile = $file->saveToLocalFile('/tmp/foo.txt'); \n\n// Saves the file to a temporary file\n$temporaryFile = $file->createLocalTemporaryFile();\n")),(0,a.kt)("h3",{id:"media-type-mime-type-handling"},"Media type (MIME type) handling"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// Setting the MIME type is usally not necessary as the framework will\n// automatically detect media type\n$file->setType('image/jpeg'); // sets the media type to image/jpeg\n\n$type = (string) $file->getType(); // image/jpeg\n$type = $file->getType()->getName(); // image/jpeg\n$type = $file->getType()->getType(); // image\n$type = $file->getType()->getSubType(); // jpeg\n$type = $file->getType()->getCommonExtensions(); // ['jpg', 'jpeg', 'jpe']\n$type = $file->getType()->getExtension(); // jpg\n$type = (string) $file->getType()->getDescription(); // JPEG image\n")),(0,a.kt)("h3",{id:"file-size--last-modified-time"},"File size & last modified time"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// Main metadata\n$size = $file->getSize(); // file size in bytes\n$lastModified = $file->getLastModified(); // last modified time\n")),(0,a.kt)("h3",{id:"image-metadata"},"Image metadata"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\Contracts\\File\\Metadata\\ImageMetadataInterface;\n\n/** @var FileInterface $file */\n\n$width = $file->get(ImageMetadataInterface::class)?->getWidth(); \n$height = $file->get(ImageMetadataInterface::class)?->getHeight(); \n\n// You can also use string identifiers, useful when specifying FQCNs is\n// unwieldy, like in Twig templates\n\n$width = $file->get('imageMetadata')?->getWidth(); \n$height = $file->get('imageMetadata')?->getHeight(); \n")),(0,a.kt)("h3",{id:"http-metadata"},"HTTP metadata"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\Contracts\\File\\Metadata\\HttpMetadataInterface;\n\n/** @var FileInterface $file */\n\n// Setting the disposition value, will be used in the Content-Disposition header\n// when the file is downloaded\n$file->get(HttpMetadataInterface::class)?->setDisposition('attachment'); \n$file->flush();\n\n// Getting all the HTTP headers that will be used when the file is downloaded\n$httpHeaders = $file->get(HttpMetadataInterface::class)?->getHeaders(); \n")),(0,a.kt)("h3",{id:"flushing-metadata"},"Flushing metadata"),(0,a.kt)("p",null,"Updating metadata using a high-level method (those on ",(0,a.kt)("inlineCode",{parentName:"p"},"FileInterface"),") will be\nsaved automatically. But using a low-level method (under\n",(0,a.kt)("inlineCode",{parentName:"p"},"FileInterface::get()"),"), you have to call ",(0,a.kt)("inlineCode",{parentName:"p"},"flush()")," manually. You can take\nadvantage of this so that multiple metadata updates are saved in a single round\ntrip."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\Contracts\\File\\Metadata\\HttpMetadataInterface;\n\n/** @var FileInterface $file */\n\n// Each of the following will be flush automatically individually, and will\n// require two roundtrips to the storage backend\n$file->setType('image/jpeg');\n$file->setName('foo.jpg');\n\n// The following needs an explicit flush(). It will only require one roundtrip\n// to the storage backend.\n$file->get(FileMetadataInterface::class)?->setType('image/jpeg'); \n$file->get(FileMetadataInterface::class)?->setName('foo.jpg'); \n$file->flush();\n")),(0,a.kt)("h3",{id:"file-pointer--comparison"},"File Pointer & comparison"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// get pointer from a FileInterface\n$filePointer = $file->getPointer();\n\n// determine if two File/FilePointer objects point to the same file\n$isEqual = $filePointer->isEqualTo($file);\n$isEqual = $file->isEqualTo($filePointer);\n$isEqual = $file1->isEqualTo($file2);\n$isEqual = $filePointer1->isEqualTo($filePointer2);\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[903],{3905:(e,t,i)=>{i.d(t,{Zo:()=>f,kt:()=>g});var n=i(7294);function a(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function l(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function o(e){for(var t=1;t=0||(a[i]=e[i]);return a}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(a[i]=e[i])}return a}var s=n.createContext({}),p=function(e){var t=n.useContext(s),i=t;return e&&(i="function"==typeof e?e(t):o(o({},t),e)),i},f=function(e){var t=p(e.components);return n.createElement(s.Provider,{value:t},e.children)},c="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var i=e.components,a=e.mdxType,l=e.originalType,s=e.parentName,f=r(e,["components","mdxType","originalType","parentName"]),c=p(i),d=a,g=c["".concat(s,".").concat(d)]||c[d]||m[d]||l;return i?n.createElement(g,o(o({ref:t},f),{},{components:i})):n.createElement(g,o({ref:t},f))}));function g(e,t){var i=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var l=i.length,o=new Array(l);o[0]=d;var r={};for(var s in t)hasOwnProperty.call(t,s)&&(r[s]=t[s]);r.originalType=e,r[c]="string"==typeof e?e:a,o[1]=r;for(var p=2;p{i.r(t),i.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>l,metadata:()=>r,toc:()=>p});var n=i(7462),a=(i(7294),i(3905));const l={title:"Using File & FileRepository"},o=void 0,r={unversionedId:"file/file",id:"file/file",title:"Using File & FileRepository",description:"When using this framework, the user will primarily work with the",source:"@site/docs/file/03-file.md",sourceDirName:"file",slug:"/file/file",permalink:"/file/file",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/03-file.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{title:"Using File & FileRepository"},sidebar:"docs",previous:{title:"Concepts & Terms",permalink:"/file/concepts"},next:{title:"Adapters",permalink:"/file/adapters"}},s={},p=[{value:"Working With the File Repository",id:"working-with-the-file-repository",level:2},{value:"Create a file",id:"create-a-file",level:3},{value:"Get a file",id:"get-a-file",level:3},{value:"Delete a file",id:"delete-a-file",level:3},{value:"Copy and move a file",id:"copy-and-move-a-file",level:3},{value:"Create a temporary file",id:"create-a-temporary-file",level:3},{value:"Working With a File",id:"working-with-a-file",level:2},{value:"Reading the file's content",id:"reading-the-files-content",level:3},{value:"Writing to the file, replacing its content",id:"writing-to-the-file-replacing-its-content",level:3},{value:"Renaming the file",id:"renaming-the-file",level:3},{value:"Saving to a local file",id:"saving-to-a-local-file",level:3},{value:"Media type (MIME type) handling",id:"media-type-mime-type-handling",level:3},{value:"File size & last modified time",id:"file-size--last-modified-time",level:3},{value:"Image metadata",id:"image-metadata",level:3},{value:"HTTP metadata",id:"http-metadata",level:3},{value:"Flushing metadata",id:"flushing-metadata",level:3},{value:"File Pointer & comparison",id:"file-pointer--comparison",level:3}],f={toc:p},c="wrapper";function m(e){let{components:t,...i}=e;return(0,a.kt)(c,(0,n.Z)({},f,i,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"When using this framework, the user will primarily work with the\n",(0,a.kt)("inlineCode",{parentName:"p"},"FileRepositoryInterface")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"FileInterface")," objects."),(0,a.kt)("h2",{id:"working-with-the-file-repository"},"Working With the File Repository"),(0,a.kt)("h3",{id:"create-a-file"},"Create a file"),(0,a.kt)("admonition",{type:"caution"},(0,a.kt)("p",{parentName:"admonition"},"These methods overwrite the existing file if it already exists.")),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileRepositoryInterface;\nuse Rekalogika\\File\\FilePointer;\n\n/** @var FileRepositoryInterface $fileRepository */\n\n// Create a file from a string\n$file = $fileRepository->createFromString(\n new FilePointer('default', 'key'),\n 'Hello World!'\n);\n\n// Create a file from a stream (resource or PSR-7 StreamInterface)\n$file = $fileRepository->createFromStream(\n new FilePointer('default', 'key'),\n $stream\n);\n\n// Create a file from a local file\n$file = $fileRepository->createFromLocalFile(\n new FilePointer('default', 'key'),\n '/tmp/foo.txt'\n);\n\n")),(0,a.kt)("h3",{id:"get-a-file"},"Get a file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileRepositoryInterface;\nuse Rekalogika\\Contracts\\File\\Exception\\File\\FileNotFoundException;\nuse Rekalogika\\File\\FilePointer;\n\n/** @var FileRepositoryInterface $fileRepository */\n\n// get() will throw an exception if the file is not found\ntry {\n $file = $fileRepository->get(new FilePointer('default', 'key'));\n} catch (FileNotFoundException $e) {\n // File not found\n}\n\n// tryGet() will return null if the file is not found\n$file = $fileRepository->tryGet(new FilePointer('default', 'key'));\n\n// With a local file, you can also do it without using file repository:\ntry {\n $file = new File('/path/to/file');\n} catch (FileNotFoundException $e) {\n // File not found\n}\n")),(0,a.kt)("h3",{id:"delete-a-file"},"Delete a file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileRepositoryInterface;\nuse Rekalogika\\File\\FilePointer;\n\n/** @var FileRepositoryInterface $fileRepository */\n\n$fileRepository->delete(new FilePointer('default', 'key'));\n")),(0,a.kt)("h3",{id:"copy-and-move-a-file"},"Copy and move a file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileRepositoryInterface;\nuse Rekalogika\\File\\FilePointer;\n\n/** @var FileRepositoryInterface $fileRepository */\n\n$newFile = $fileRepository->copy(\n new FilePointer('default', 'key'),\n new FilePointer('otherfilesystem', 'destinationkey')\n);\n\n$newFile = $fileRepository->move(\n new FilePointer('default', 'key'),\n new FilePointer('otherfilesystem', 'destinationkey')\n);\n")),(0,a.kt)("admonition",{type:"tip"},(0,a.kt)("p",{parentName:"admonition"},"You can also use a ",(0,a.kt)("inlineCode",{parentName:"p"},"FileInterface")," as the origin or the destination\nof the move or copy operation.")),(0,a.kt)("h3",{id:"create-a-temporary-file"},"Create a temporary file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileRepositoryInterface;\n\n/** @var FileRepositoryInterface $fileRepository */\n\n$file = $fileRepository->createTemporaryFile();\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"The temporary file is represented by a special ",(0,a.kt)("inlineCode",{parentName:"p"},"TemporaryFile")," that will be\nautomatically deleted if it is unset or falls out of scope.")),(0,a.kt)("h2",{id:"working-with-a-file"},"Working With a File"),(0,a.kt)("h3",{id:"reading-the-files-content"},"Reading the file's content"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// As a string\n$string = $file->getContent();\n\n// As a stream\n$stream = $file->getContentAsStream();\n\n// getContentAsStream() returns a PSR-7 StreamInterface, to get a plain PHP\n// resource, call detach() on it\n$resource = $stream->detach();\n")),(0,a.kt)("h3",{id:"writing-to-the-file-replacing-its-content"},"Writing to the file, replacing its content"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// From a string\n$file->setContent('Hello World!');\n\n// From a stream or resource\n$file->setContentFromStream($resource);\n")),(0,a.kt)("h3",{id:"renaming-the-file"},"Renaming the file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n$file->setName('my-photo.jpg');\n\n// If you omit the extension, the library will automatically choose the correct\n// extension based on the file's MIME type\n\n$file->setName('my-photo');\n$name = (string) $file->getName(); // my-photo.jpg\n\n// If you absolutely don't want an extension, you can set it directly to the\n// metadata\n\n$file->get(FileMetadataInterface::class)->setFileName('my-photo');\n$file->flush();\n\n// getName() returns FileNameInterface that provides several convenient methods\n// to get information about the filename\n\n$file->setName('foo.png');\n\n$name = (string) $file->getName(); // foo.png\n$fullName = $file->getName()->getFull(); // foo.png\n$baseName = $file->getName()->getBase(); // foo\n$extension = $file->getName()->getExtension(); // png\n$hasExtension = $file->getName()->hasExtension(); // true\n")),(0,a.kt)("h3",{id:"saving-to-a-local-file"},"Saving to a local file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// Saves the file to /tmp/foo.txt\n$localFile = $file->saveToLocalFile('/tmp/foo.txt'); \n\n// Saves the file to a temporary file\n$temporaryFile = $file->createLocalTemporaryFile();\n")),(0,a.kt)("h3",{id:"media-type-mime-type-handling"},"Media type (MIME type) handling"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// Setting the MIME type is usally not necessary as the framework will\n// automatically detect media type\n$file->setType('image/jpeg'); // sets the media type to image/jpeg\n\n$type = (string) $file->getType(); // image/jpeg\n$type = $file->getType()->getName(); // image/jpeg\n$type = $file->getType()->getType(); // image\n$type = $file->getType()->getSubType(); // jpeg\n$type = $file->getType()->getCommonExtensions(); // ['jpg', 'jpeg', 'jpe']\n$type = $file->getType()->getExtension(); // jpg\n$type = (string) $file->getType()->getDescription(); // JPEG image\n")),(0,a.kt)("h3",{id:"file-size--last-modified-time"},"File size & last modified time"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// Main metadata\n$size = $file->getSize(); // file size in bytes\n$lastModified = $file->getLastModified(); // last modified time\n")),(0,a.kt)("h3",{id:"image-metadata"},"Image metadata"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\Contracts\\File\\Metadata\\ImageMetadataInterface;\n\n/** @var FileInterface $file */\n\n$width = $file->get(ImageMetadataInterface::class)?->getWidth(); \n$height = $file->get(ImageMetadataInterface::class)?->getHeight(); \n\n// You can also use string identifiers, useful when specifying FQCNs is\n// unwieldy, like in Twig templates\n\n$width = $file->get('imageMetadata')?->getWidth(); \n$height = $file->get('imageMetadata')?->getHeight(); \n")),(0,a.kt)("h3",{id:"http-metadata"},"HTTP metadata"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\Contracts\\File\\Metadata\\HttpMetadataInterface;\n\n/** @var FileInterface $file */\n\n// Setting the disposition value, will be used in the Content-Disposition header\n// when the file is downloaded\n$file->get(HttpMetadataInterface::class)?->setDisposition('attachment'); \n$file->flush();\n\n// Getting all the HTTP headers that will be used when the file is downloaded\n$httpHeaders = $file->get(HttpMetadataInterface::class)?->getHeaders(); \n")),(0,a.kt)("h3",{id:"flushing-metadata"},"Flushing metadata"),(0,a.kt)("p",null,"Updating metadata using a high-level method (those on ",(0,a.kt)("inlineCode",{parentName:"p"},"FileInterface"),") will be\nsaved automatically. But using a low-level method (under\n",(0,a.kt)("inlineCode",{parentName:"p"},"FileInterface::get()"),"), you have to call ",(0,a.kt)("inlineCode",{parentName:"p"},"flush()")," manually. You can take\nadvantage of this so that multiple metadata updates are saved in a single round\ntrip."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\Contracts\\File\\Metadata\\HttpMetadataInterface;\n\n/** @var FileInterface $file */\n\n// Each of the following will be flush automatically individually, and will\n// require two roundtrips to the storage backend\n$file->setType('image/jpeg');\n$file->setName('foo.jpg');\n\n// The following needs an explicit flush(). It will only require one roundtrip\n// to the storage backend.\n$file->get(FileMetadataInterface::class)?->setType('image/jpeg'); \n$file->get(FileMetadataInterface::class)?->setName('foo.jpg'); \n$file->flush();\n")),(0,a.kt)("h3",{id:"file-pointer--comparison"},"File Pointer & comparison"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// get pointer from a FileInterface\n$filePointer = $file->getPointer();\n\n// determine if two File/FilePointer objects point to the same file\n$isEqual = $filePointer->isEqualTo($file);\n$isEqual = $file->isEqualTo($filePointer);\n$isEqual = $file1->isEqualTo($file2);\n$isEqual = $filePointer1->isEqualTo($filePointer2);\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5060314a.05bc9e36.js b/assets/js/5060314a.d158853b.js similarity index 79% rename from assets/js/5060314a.05bc9e36.js rename to assets/js/5060314a.d158853b.js index 2b905e65..09c291bf 100644 --- a/assets/js/5060314a.05bc9e36.js +++ b/assets/js/5060314a.d158853b.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[944],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>h});var r=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=r.createContext({}),c=function(e){var t=r.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return r.createElement(l.Provider,{value:t},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),u=c(n),f=i,h=u["".concat(l,".").concat(f)]||u[f]||d[f]||a;return n?r.createElement(h,o(o({ref:t},p),{},{components:n})):r.createElement(h,o({ref:t},p))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=f;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[u]="string"==typeof e?e:i,o[1]=s;for(var c=2;c{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var r=n(7462),i=(n(7294),n(3905));const a={title:"Tips and Best Practices"},o=void 0,s={unversionedId:"domain-event/tips",id:"domain-event/tips",title:"Tips and Best Practices",description:"This chapter explains the tips and our best practices that others might find",source:"@site/docs/domain-event/04-tips.md",sourceDirName:"domain-event",slug:"/domain-event/tips",permalink:"/domain-event/tips",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/domain-event/04-tips.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{title:"Tips and Best Practices"},sidebar:"docs",previous:{title:"Immediate Dispatcher Handling & Troubleshooting",permalink:"/domain-event/immediate-dispatcher"},next:{title:"rekalogika/file",permalink:"/file/"}},l={},c=[{value:"Use UUIDs as Identifiers",id:"use-uuids-as-identifiers",level:2},{value:"Choosing Dispatching Strategy",id:"choosing-dispatching-strategy",level:2}],p={toc:c},u="wrapper";function d(e){let{components:t,...n}=e;return(0,i.kt)(u,(0,r.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"This chapter explains the tips and our best practices that others might find\nuseful, but not strictly required."),(0,i.kt)("h2",{id:"use-uuids-as-identifiers"},"Use UUIDs as Identifiers"),(0,i.kt)("p",null,"Use UUIDs as entity identifiers & have the entities generate one for themselves\non instantiation. That means new entities already have an ID before ",(0,i.kt)("inlineCode",{parentName:"p"},"flush()"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Uid\\UuidV7;\n\nclass Post\n{\n private string $id;\n\n public function __construct(string $title)\n {\n $this->id = new UuidV7();\n }\n\n // ...\n}\n")),(0,i.kt)("p",null,"Therefore, you can reliably store the ID in your event objects, instead of the\nobject itself. Using the ID in the events mean your events can be realiably\nserialized, and you can pass them anywhere without alteration."),(0,i.kt)("h2",{id:"choosing-dispatching-strategy"},"Choosing Dispatching Strategy"),(0,i.kt)("p",null,"If you want to something similar that you are used to do with application\nevents, you probably want post-flush strategy."),(0,i.kt)("p",null,"Use post-flush for things that should occur only if the change is successful,\nlike notifications, etc."),(0,i.kt)("p",null,"Use pre-flush events to make alterations to your domain that will be\n",(0,i.kt)("inlineCode",{parentName:"p"},"flush()"),"-ed together along with the other changes."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[944],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>f});var r=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=r.createContext({}),c=function(e){var t=r.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return r.createElement(l.Provider,{value:t},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},h=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),u=c(n),h=i,f=u["".concat(l,".").concat(h)]||u[h]||d[h]||a;return n?r.createElement(f,o(o({ref:t},p),{},{components:n})):r.createElement(f,o({ref:t},p))}));function f(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=h;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[u]="string"==typeof e?e:i,o[1]=s;for(var c=2;c{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var r=n(7462),i=(n(7294),n(3905));const a={title:"Tips and Best Practices"},o=void 0,s={unversionedId:"domain-event/tips",id:"domain-event/tips",title:"Tips and Best Practices",description:"This chapter explains the tips and our best practices that others might find",source:"@site/docs/domain-event/04-tips.md",sourceDirName:"domain-event",slug:"/domain-event/tips",permalink:"/domain-event/tips",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/domain-event/04-tips.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{title:"Tips and Best Practices"},sidebar:"docs",previous:{title:"Immediate Dispatcher Handling & Troubleshooting",permalink:"/domain-event/immediate-dispatcher"},next:{title:"rekalogika/file",permalink:"/file/"}},l={},c=[{value:"Use UUIDs as Identifiers",id:"use-uuids-as-identifiers",level:2},{value:"Choosing Dispatching Strategy",id:"choosing-dispatching-strategy",level:2}],p={toc:c},u="wrapper";function d(e){let{components:t,...n}=e;return(0,i.kt)(u,(0,r.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"This chapter explains the tips and our best practices that others might find\nuseful, but not strictly required."),(0,i.kt)("h2",{id:"use-uuids-as-identifiers"},"Use UUIDs as Identifiers"),(0,i.kt)("p",null,"Use UUIDs as entity identifiers & have the entities generate one for themselves\non instantiation. That means new entities already have an ID before ",(0,i.kt)("inlineCode",{parentName:"p"},"flush()"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\Uid\\UuidV7;\n\nclass Post\n{\n private string $id;\n\n public function __construct(string $title)\n {\n $this->id = new UuidV7();\n }\n\n // ...\n}\n")),(0,i.kt)("p",null,"Therefore, you can reliably store the ID in your event objects, instead of the\nobject itself. Using the ID in the events means your events can be reliably\nserialized, and you can pass them anywhere without alteration."),(0,i.kt)("h2",{id:"choosing-dispatching-strategy"},"Choosing Dispatching Strategy"),(0,i.kt)("p",null,"If you want to do something similar to what you are used to doing with\napplication events, you probably want the post-flush strategy."),(0,i.kt)("p",null,"Use post-flush for things that should occur only if the change is successful,\nlike notifications, etc."),(0,i.kt)("p",null,"Use pre-flush events to make alterations to your domain that will be\n",(0,i.kt)("inlineCode",{parentName:"p"},"flush()"),"-ed together along with the other changes."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5498dd6e.f363b7f3.js b/assets/js/5498dd6e.b6935c04.js similarity index 74% rename from assets/js/5498dd6e.f363b7f3.js rename to assets/js/5498dd6e.b6935c04.js index 08f77587..5ab06134 100644 --- a/assets/js/5498dd6e.f363b7f3.js +++ b/assets/js/5498dd6e.b6935c04.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[425],{3905:(e,t,r)=>{r.d(t,{Zo:()=>u,kt:()=>f});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function l(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var c=n.createContext({}),i=function(e){var t=n.useContext(c),r=t;return e&&(r="function"==typeof e?e(t):l(l({},t),e)),r},u=function(e){var t=i(e.components);return n.createElement(c.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,o=e.originalType,c=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),p=i(r),m=a,f=p["".concat(c,".").concat(m)]||p[m]||d[m]||o;return r?n.createElement(f,l(l({ref:t},u),{},{components:r})):n.createElement(f,l({ref:t},u))}));function f(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=r.length,l=new Array(o);l[0]=m;var s={};for(var c in t)hasOwnProperty.call(t,c)&&(s[c]=t[c]);s.originalType=e,s[p]="string"==typeof e?e:a,l[1]=s;for(var i=2;i{r.d(t,{Z:()=>l});var n=r(7294),a=r(6010);const o={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:r,className:l}=e;return n.createElement("div",{role:"tabpanel",className:(0,a.Z)(o.tabItem,l),hidden:r},t)}},4866:(e,t,r)=>{r.d(t,{Z:()=>w});var n=r(7462),a=r(7294),o=r(6010),l=r(2466),s=r(6550),c=r(1980),i=r(7392),u=r(12);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:r,attributes:n,default:a}}=e;return{value:t,label:r,attributes:n,default:a}}))}function d(e){const{values:t,children:r}=e;return(0,a.useMemo)((()=>{const e=t??p(r);return function(e){const t=(0,i.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,r])}function m(e){let{value:t,tabValues:r}=e;return r.some((e=>e.value===t))}function f(e){let{queryString:t=!1,groupId:r}=e;const n=(0,s.k6)(),o=function(e){let{queryString:t=!1,groupId:r}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return r??null}({queryString:t,groupId:r});return[(0,c._X)(o),(0,a.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function y(e){const{defaultValue:t,queryString:r=!1,groupId:n}=e,o=d(e),[l,s]=(0,a.useState)((()=>function(e){let{defaultValue:t,tabValues:r}=e;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:r}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${r.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=r.find((e=>e.default))??r[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[c,i]=f({queryString:r,groupId:n}),[p,y]=function(e){let{groupId:t}=e;const r=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,u.Nk)(r);return[n,(0,a.useCallback)((e=>{r&&o.set(e)}),[r,o])]}({groupId:n}),b=(()=>{const e=c??p;return m({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{b&&s(b)}),[b]);return{selectedValue:l,selectValue:(0,a.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);s(e),i(e),y(e)}),[i,y,o]),tabValues:o}}var b=r(2389);const h={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function k(e){let{className:t,block:r,selectedValue:s,selectValue:c,tabValues:i}=e;const u=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.o5)(),d=e=>{const t=e.currentTarget,r=u.indexOf(t),n=i[r].value;n!==s&&(p(t),c(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const r=u.indexOf(e.currentTarget)+1;t=u[r]??u[0];break}case"ArrowLeft":{const r=u.indexOf(e.currentTarget)-1;t=u[r]??u[u.length-1];break}}t?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.Z)("tabs",{"tabs--block":r},t)},i.map((e=>{let{value:t,label:r,attributes:l}=e;return a.createElement("li",(0,n.Z)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>u.push(e),onKeyDown:m,onClick:d},l,{className:(0,o.Z)("tabs__item",h.tabItem,l?.className,{"tabs__item--active":s===t})}),r??t)})))}function g(e){let{lazy:t,children:r,selectedValue:n}=e;const o=(Array.isArray(r)?r:[r]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function v(e){const t=y(e);return a.createElement("div",{className:(0,o.Z)("tabs-container",h.tabList)},a.createElement(k,(0,n.Z)({},e,t)),a.createElement(g,(0,n.Z)({},e,t)))}function w(e){const t=(0,b.Z)();return a.createElement(v,(0,n.Z)({key:String(t)},e))}},1878:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>u,contentTitle:()=>c,default:()=>f,frontMatter:()=>s,metadata:()=>i,toc:()=>p});var n=r(7462),a=(r(7294),r(3905)),o=r(4866),l=r(5162);const s={title:"rekalogika/direct-property-access"},c=void 0,i={unversionedId:"direct-property-access/index",id:"direct-property-access/index",title:"rekalogika/direct-property-access",description:"Implementation of Symfony's PropertyAccessorInterface that reads and writes",source:"@site/docs/direct-property-access/index.md",sourceDirName:"direct-property-access",slug:"/direct-property-access/",permalink:"/direct-property-access/",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/direct-property-access/index.md",tags:[],version:"current",frontMatter:{title:"rekalogika/direct-property-access"},sidebar:"docs",next:{title:"rekalogika/domain-event",permalink:"/domain-event/"}},u={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2},{value:"Caveats",id:"caveats",level:2},{value:"Credits",id:"credits",level:2}],d={toc:p},m="wrapper";function f(e){let{components:t,...r}=e;return(0,a.kt)(m,(0,n.Z)({},d,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Implementation of Symfony's ",(0,a.kt)("inlineCode",{parentName:"p"},"PropertyAccessorInterface")," that reads and writes\ndirectly to the object's properties, bypassing getters and setters."),(0,a.kt)("h2",{id:"installation"},"Installation"),(0,a.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,a.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,a.kt)(o.Z,{mdxType:"Tabs"},(0,a.kt)(l.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Open a command console, enter your project directory and execute:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/direct-property-access\n"))),(0,a.kt)(l.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Step 1: Download the Bundle"),(0,a.kt)("p",null,"Open a command console, enter your project directory and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/direct-property-access\n")),(0,a.kt)("p",null,"Step 2: Enable the Bundle"),(0,a.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,a.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\DirectPropertyAccess\\RekalogikaDirectPropertyAccessBundle::class => ['all' => true],\n];\n"))),(0,a.kt)(l.Z,{value:"nonsymfony",label:"Non-Symfony Projects",mdxType:"TabItem"},(0,a.kt)("p",null,"Open a command console, enter your project directory and execute:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/direct-property-access\n")))),(0,a.kt)("h2",{id:"usage"},"Usage"),(0,a.kt)("p",null,"In Symfony projects, you can autowire ",(0,a.kt)("inlineCode",{parentName:"p"},"DirectPropertyAccessor"),". In other\nprojects, you can simply instantiate it."),(0,a.kt)("p",null,"Read ",(0,a.kt)("a",{parentName:"p",href:"https://symfony.com/doc/current/components/property_access.html"},"Symfony's PropertyAccess\ndocumentation"),"\nfor more information on how to use it. The difference is that\n",(0,a.kt)("inlineCode",{parentName:"p"},"DirectPropertyAccessor")," does not call any of the object's methods, but reads\nand writes directly to the object's properties, even if they are private."),(0,a.kt)("h2",{id:"caveats"},"Caveats"),(0,a.kt)("p",null,"Currently does not support arrays and paths beyond one level deep."),(0,a.kt)("h2",{id:"credits"},"Credits"),(0,a.kt)("p",null,"This project took inspiration from the following projects."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/symfony/property-access"},"Symfony Property Access")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/kwn/reflection-property-access"},"kwn/reflection-property-access")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/nelmio/alice/blob/master/src/PropertyAccess/ReflectionPropertyAccessor.php"},"nelmio/alice"))))}f.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[425],{3905:(e,t,r)=>{r.d(t,{Zo:()=>u,kt:()=>f});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function l(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var c=n.createContext({}),i=function(e){var t=n.useContext(c),r=t;return e&&(r="function"==typeof e?e(t):l(l({},t),e)),r},u=function(e){var t=i(e.components);return n.createElement(c.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,o=e.originalType,c=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),p=i(r),m=a,f=p["".concat(c,".").concat(m)]||p[m]||d[m]||o;return r?n.createElement(f,l(l({ref:t},u),{},{components:r})):n.createElement(f,l({ref:t},u))}));function f(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=r.length,l=new Array(o);l[0]=m;var s={};for(var c in t)hasOwnProperty.call(t,c)&&(s[c]=t[c]);s.originalType=e,s[p]="string"==typeof e?e:a,l[1]=s;for(var i=2;i{r.d(t,{Z:()=>l});var n=r(7294),a=r(6010);const o={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:r,className:l}=e;return n.createElement("div",{role:"tabpanel",className:(0,a.Z)(o.tabItem,l),hidden:r},t)}},4866:(e,t,r)=>{r.d(t,{Z:()=>w});var n=r(7462),a=r(7294),o=r(6010),l=r(2466),s=r(6550),c=r(1980),i=r(7392),u=r(12);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:r,attributes:n,default:a}}=e;return{value:t,label:r,attributes:n,default:a}}))}function d(e){const{values:t,children:r}=e;return(0,a.useMemo)((()=>{const e=t??p(r);return function(e){const t=(0,i.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,r])}function m(e){let{value:t,tabValues:r}=e;return r.some((e=>e.value===t))}function f(e){let{queryString:t=!1,groupId:r}=e;const n=(0,s.k6)(),o=function(e){let{queryString:t=!1,groupId:r}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return r??null}({queryString:t,groupId:r});return[(0,c._X)(o),(0,a.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function y(e){const{defaultValue:t,queryString:r=!1,groupId:n}=e,o=d(e),[l,s]=(0,a.useState)((()=>function(e){let{defaultValue:t,tabValues:r}=e;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:r}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${r.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=r.find((e=>e.default))??r[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[c,i]=f({queryString:r,groupId:n}),[p,y]=function(e){let{groupId:t}=e;const r=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,u.Nk)(r);return[n,(0,a.useCallback)((e=>{r&&o.set(e)}),[r,o])]}({groupId:n}),b=(()=>{const e=c??p;return m({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{b&&s(b)}),[b]);return{selectedValue:l,selectValue:(0,a.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);s(e),i(e),y(e)}),[i,y,o]),tabValues:o}}var b=r(2389);const h={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function k(e){let{className:t,block:r,selectedValue:s,selectValue:c,tabValues:i}=e;const u=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.o5)(),d=e=>{const t=e.currentTarget,r=u.indexOf(t),n=i[r].value;n!==s&&(p(t),c(n))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const r=u.indexOf(e.currentTarget)+1;t=u[r]??u[0];break}case"ArrowLeft":{const r=u.indexOf(e.currentTarget)-1;t=u[r]??u[u.length-1];break}}t?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.Z)("tabs",{"tabs--block":r},t)},i.map((e=>{let{value:t,label:r,attributes:l}=e;return a.createElement("li",(0,n.Z)({role:"tab",tabIndex:s===t?0:-1,"aria-selected":s===t,key:t,ref:e=>u.push(e),onKeyDown:m,onClick:d},l,{className:(0,o.Z)("tabs__item",h.tabItem,l?.className,{"tabs__item--active":s===t})}),r??t)})))}function g(e){let{lazy:t,children:r,selectedValue:n}=e;const o=(Array.isArray(r)?r:[r]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function v(e){const t=y(e);return a.createElement("div",{className:(0,o.Z)("tabs-container",h.tabList)},a.createElement(k,(0,n.Z)({},e,t)),a.createElement(g,(0,n.Z)({},e,t)))}function w(e){const t=(0,b.Z)();return a.createElement(v,(0,n.Z)({key:String(t)},e))}},1878:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>u,contentTitle:()=>c,default:()=>f,frontMatter:()=>s,metadata:()=>i,toc:()=>p});var n=r(7462),a=(r(7294),r(3905)),o=r(4866),l=r(5162);const s={title:"rekalogika/direct-property-access"},c=void 0,i={unversionedId:"direct-property-access/index",id:"direct-property-access/index",title:"rekalogika/direct-property-access",description:"Implementation of Symfony's PropertyAccessorInterface that reads and writes",source:"@site/docs/direct-property-access/index.md",sourceDirName:"direct-property-access",slug:"/direct-property-access/",permalink:"/direct-property-access/",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/direct-property-access/index.md",tags:[],version:"current",frontMatter:{title:"rekalogika/direct-property-access"},sidebar:"docs",next:{title:"rekalogika/domain-event",permalink:"/domain-event/"}},u={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2},{value:"Caveats",id:"caveats",level:2},{value:"Credits",id:"credits",level:2}],d={toc:p},m="wrapper";function f(e){let{components:t,...r}=e;return(0,a.kt)(m,(0,n.Z)({},d,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Implementation of Symfony's ",(0,a.kt)("inlineCode",{parentName:"p"},"PropertyAccessorInterface")," that reads and writes\ndirectly to the object's properties, bypassing getters and setters."),(0,a.kt)("h2",{id:"installation"},"Installation"),(0,a.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,a.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,a.kt)(o.Z,{mdxType:"Tabs"},(0,a.kt)(l.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Open a command console, enter your project directory, and execute:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/direct-property-access\n"))),(0,a.kt)(l.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Step 1: Download the Bundle"),(0,a.kt)("p",null,"Open a command console, enter your project directory, and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/direct-property-access\n")),(0,a.kt)("p",null,"Step 2: Enable the Bundle"),(0,a.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,a.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\DirectPropertyAccess\\RekalogikaDirectPropertyAccessBundle::class => ['all' => true],\n];\n"))),(0,a.kt)(l.Z,{value:"nonsymfony",label:"Non-Symfony Projects",mdxType:"TabItem"},(0,a.kt)("p",null,"Open a command console, enter your project directory, and execute:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/direct-property-access\n")))),(0,a.kt)("h2",{id:"usage"},"Usage"),(0,a.kt)("p",null,"In Symfony projects, you can autowire ",(0,a.kt)("inlineCode",{parentName:"p"},"DirectPropertyAccessor"),". In other\nprojects, you can simply instantiate it."),(0,a.kt)("p",null,"Read ",(0,a.kt)("a",{parentName:"p",href:"https://symfony.com/doc/current/components/property_access.html"},"Symfony's PropertyAccess\ndocumentation"),"\nfor more information on how to use it. The difference is that\n",(0,a.kt)("inlineCode",{parentName:"p"},"DirectPropertyAccessor")," does not call any of the object's methods, but reads\nand writes directly to the object's properties, even if they are private."),(0,a.kt)("h2",{id:"caveats"},"Caveats"),(0,a.kt)("p",null,"Currently does not support arrays and paths beyond one level deep."),(0,a.kt)("h2",{id:"credits"},"Credits"),(0,a.kt)("p",null,"This project took inspiration from the following projects."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/symfony/property-access"},"Symfony Property Access")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/kwn/reflection-property-access"},"kwn/reflection-property-access")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/nelmio/alice/blob/master/src/PropertyAccess/ReflectionPropertyAccessor.php"},"nelmio/alice"))))}f.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5e8cf002.36ef2699.js b/assets/js/5e8cf002.36ef2699.js new file mode 100644 index 00000000..751c71b1 --- /dev/null +++ b/assets/js/5e8cf002.36ef2699.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[6],{3905:(e,n,t)=>{t.d(n,{Zo:()=>m,kt:()=>f});var a=t(7294);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function r(e){for(var n=1;n=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var p=a.createContext({}),s=function(e){var n=a.useContext(p),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},m=function(e){var n=s(e.components);return a.createElement(p.Provider,{value:n},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},c=a.forwardRef((function(e,n){var t=e.components,i=e.mdxType,o=e.originalType,p=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),d=s(t),c=i,f=d["".concat(p,".").concat(c)]||d[c]||u[c]||o;return t?a.createElement(f,r(r({ref:n},m),{},{components:t})):a.createElement(f,r({ref:n},m))}));function f(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var o=t.length,r=new Array(o);r[0]=c;var l={};for(var p in n)hasOwnProperty.call(n,p)&&(l[p]=n[p]);l.originalType=e,l[d]="string"==typeof e?e:i,r[1]=l;for(var s=2;s{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>s});var a=t(7462),i=(t(7294),t(3905));const o={title:"Symfony Integration"},r=void 0,l={unversionedId:"file-bundle/symfony",id:"file-bundle/symfony",title:"Symfony Integration",description:"This chapter describes how to integrate this framework with the typical Symfony",source:"@site/docs/file-bundle/04-symfony.md",sourceDirName:"file-bundle",slug:"/file-bundle/symfony",permalink:"/file-bundle/symfony",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/04-symfony.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{title:"Symfony Integration"},sidebar:"docs",previous:{title:"Associating Files with Doctrine Entities",permalink:"/file-bundle/doctrine-entity"},next:{title:"Serving Files",permalink:"/file-bundle/serving-files"}},p={},s=[{value:"Components Summary",id:"components-summary",level:2},{value:"Adapters",id:"adapters",level:2},{value:"Streaming a FileInterface",id:"streaming-a-fileinterface",level:2},{value:"Forms",id:"forms",level:2},{value:"Validators",id:"validators",level:2}],m={toc:s},d="wrapper";function u(e){let{components:n,...t}=e;return(0,i.kt)(d,(0,a.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"This chapter describes how to integrate this framework with the typical Symfony\ncomponents used to work with files."),(0,i.kt)("admonition",{title:"Preparation",type:"info"},(0,i.kt)("p",{parentName:"admonition"},"To enable this feature, you need to install the package\n",(0,i.kt)("inlineCode",{parentName:"p"},"rekalogika/file-symfony-bridge"),":"),(0,i.kt)("pre",{parentName:"admonition"},(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-symfony-bridge\n"))),(0,i.kt)("h2",{id:"components-summary"},"Components Summary"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Adapters to convert HttpFoundation ",(0,i.kt)("inlineCode",{parentName:"li"},"File")," objects to a ",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface")," and\nvice versa, with special handling for ",(0,i.kt)("inlineCode",{parentName:"li"},"UploadedFile"),"."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"FileResponse")," for streaming a ",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface")," to the client web browser."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"FileType")," form that works with ",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface")," objects."),(0,i.kt)("li",{parentName:"ul"},"A form transformer ",(0,i.kt)("inlineCode",{parentName:"li"},"FileTransformer")," that you can add to an existing Symfony\n",(0,i.kt)("inlineCode",{parentName:"li"},"FileType")," fields so that it gives us a ",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface")," instead of a\n",(0,i.kt)("inlineCode",{parentName:"li"},"UploadedFile")," object."),(0,i.kt)("li",{parentName:"ul"},"A form extension ",(0,i.kt)("inlineCode",{parentName:"li"},"FileTypeExtension")," that you can optionally register to\nautomatically convert all the existing Symfony ",(0,i.kt)("inlineCode",{parentName:"li"},"FileType")," so they all give us\na ",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface"),"."),(0,i.kt)("li",{parentName:"ul"},"Subclassed ",(0,i.kt)("inlineCode",{parentName:"li"},"FileValidator")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"ImageValidator")," that works with\n",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface")," objects.")),(0,i.kt)("h2",{id:"adapters"},"Adapters"),(0,i.kt)("p",null,"Converts a HttpFoundation ",(0,i.kt)("inlineCode",{parentName:"p"},"File")," (and child classes, including ",(0,i.kt)("inlineCode",{parentName:"p"},"UploadedFile"),")\nto a ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\HttpFoundation\\FromHttpFoundationFileAdapter;\nuse Symfony\\Component\\HttpFoundation\\File\\File;\n\n/** @var File $httpFoundationFile */\n\n$file = FromHttpFoundationFileAdapter::adapt($httpFoundationFile);\n")),(0,i.kt)("p",null,"However, it is more convenient to use the universal adapter instead, although\nthe universal adapter still needs this package to be installed."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\File\\File;\nuse Rekalogika\\File\\FileAdapter;\n\n/** @var File $httpFoundationFile */\n\n$file = FileAdapter::adapt($httpFoundationFile);\n")),(0,i.kt)("p",null,"Converts a ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," to a HttpFoundation ",(0,i.kt)("inlineCode",{parentName:"p"},"File"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\HttpFoundation\\ToHttpFoundationFileAdapter;\nuse Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n$httpFoundationFile = ToHttpFoundationFileAdapter::adapt($file);\n")),(0,i.kt)("h2",{id:"streaming-a-fileinterface"},"Streaming a FileInterface"),(0,i.kt)("p",null,"To stream a ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," to the client's web browser, you can use\n",(0,i.kt)("inlineCode",{parentName:"p"},"FileResponse"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\HttpFoundation\\FileResponse;\nuse Rekalogika\\Contracts\\File\\FileInterface;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass SomeController\n{\n public function download(): Response\n {\n /** @var FileInterface $file */\n $file = ...;\n\n return new FileResponse($file);\n }\n}\n")),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"FileResponse")," accepts additional optional parameters:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"$status"),": HTTP status code. Default: ",(0,i.kt)("inlineCode",{parentName:"li"},"200"),"."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"$headers"),": Array of additional headers. Default: ",(0,i.kt)("inlineCode",{parentName:"li"},"[]"),"."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"$disposition"),": Force the first parameter of the ",(0,i.kt)("inlineCode",{parentName:"li"},"Content-Disposition")," header\nto the specified value. It can be ",(0,i.kt)("inlineCode",{parentName:"li"},"attachment")," or ",(0,i.kt)("inlineCode",{parentName:"li"},"inline"),". The filename is\nautomatically taken from the metadata.")),(0,i.kt)("h2",{id:"forms"},"Forms"),(0,i.kt)("p",null,"We provide a ",(0,i.kt)("inlineCode",{parentName:"p"},"FileType")," that works with ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," objects. This is\nbasically the same as Symfony's ",(0,i.kt)("inlineCode",{parentName:"p"},"FileType")," with a transformer built-in:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\Form\\FileType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass SomeFormType extends AbstractType\n{\n public function buildForm(FormBuilderInterface $builder, array $options): void\n {\n $builder\n // ...\n ->add('file', FileType::class, [\n // ...\n ])\n ;\n }\n}\n")),(0,i.kt)("p",null,"If for some reason you cannot change the form type, you can use\n",(0,i.kt)("inlineCode",{parentName:"p"},"FileTransformer")," to transform existing fields. It should work with Symfony's\n",(0,i.kt)("inlineCode",{parentName:"p"},"FileType")," and any third-party form types with a compatible behavior:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\Form\\FileTransformer;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass SomeFormType extends AbstractType\n{\n public function buildForm(FormBuilderInterface $builder, array $options): void\n {\n $builder\n ->add('file', FileType::class, [\n // ...\n ]);\n\n $builder->get('file')->addModelTransformer(new FileTransformer());\n }\n}\n")),(0,i.kt)("p",null,"You can also modify all the existing Symfony's ",(0,i.kt)("inlineCode",{parentName:"p"},"FileType")," fields en masse by\nregistering the ",(0,i.kt)("inlineCode",{parentName:"p"},"FileTypeExtension"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/services.yaml",title:"config/services.yaml"},"services:\n Rekalogika\\File\\Bridge\\Symfony\\Form\\FileTypeExtension:\n tags:\n - { name: form.type_extension }\n")),(0,i.kt)("h2",{id:"validators"},"Validators"),(0,i.kt)("p",null,"We provide ",(0,i.kt)("inlineCode",{parentName:"p"},"File")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"Image")," validators. They are the same as Symfony's\n",(0,i.kt)("inlineCode",{parentName:"p"},"File")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"Image")," validators, except that they work with ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface"),"\nobjects instead of HttpFoundation ",(0,i.kt)("inlineCode",{parentName:"p"},"File")," objects:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\File\\Bridge\\Symfony\\Constraints\\File as FileConstraint;\nuse Rekalogika\\File\\Bridge\\Symfony\\Constraints\\Image as ImageConstraint;\n\nclass Product\n{\n #[ImageConstraint(minWidth: '1000']\n private ?FileInterface $photo = null;\n\n #[ImageConstraint(maxSize: '10000k']\n private ?FileInterface $manual = null;\n\n // ...\n}\n")),(0,i.kt)("admonition",{type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"Due to how the adapters work, some validator functions might not work\ncorrectly, like those that check file names.")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/5e8cf002.aad28784.js b/assets/js/5e8cf002.aad28784.js deleted file mode 100644 index 59ba4427..00000000 --- a/assets/js/5e8cf002.aad28784.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[6],{3905:(e,n,t)=>{t.d(n,{Zo:()=>m,kt:()=>f});var a=t(7294);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function r(e){for(var n=1;n=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var p=a.createContext({}),s=function(e){var n=a.useContext(p),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},m=function(e){var n=s(e.components);return a.createElement(p.Provider,{value:n},e.children)},d="mdxType",u={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},c=a.forwardRef((function(e,n){var t=e.components,i=e.mdxType,o=e.originalType,p=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),d=s(t),c=i,f=d["".concat(p,".").concat(c)]||d[c]||u[c]||o;return t?a.createElement(f,r(r({ref:n},m),{},{components:t})):a.createElement(f,r({ref:n},m))}));function f(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var o=t.length,r=new Array(o);r[0]=c;var l={};for(var p in n)hasOwnProperty.call(n,p)&&(l[p]=n[p]);l.originalType=e,l[d]="string"==typeof e?e:i,r[1]=l;for(var s=2;s{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>s});var a=t(7462),i=(t(7294),t(3905));const o={title:"Symfony Integration"},r=void 0,l={unversionedId:"file-bundle/symfony",id:"file-bundle/symfony",title:"Symfony Integration",description:"This chapter describes how to integrate this framework with the typical Symfony",source:"@site/docs/file-bundle/04-symfony.md",sourceDirName:"file-bundle",slug:"/file-bundle/symfony",permalink:"/file-bundle/symfony",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/04-symfony.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{title:"Symfony Integration"},sidebar:"docs",previous:{title:"Associating Files with Doctrine Entities",permalink:"/file-bundle/doctrine-entity"},next:{title:"Serving Files",permalink:"/file-bundle/serving-files"}},p={},s=[{value:"Components Summary",id:"components-summary",level:2},{value:"Adapters",id:"adapters",level:2},{value:"Streaming a FileInterface",id:"streaming-a-fileinterface",level:2},{value:"Forms",id:"forms",level:2},{value:"Validators",id:"validators",level:2}],m={toc:s},d="wrapper";function u(e){let{components:n,...t}=e;return(0,i.kt)(d,(0,a.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"This chapter describes how to integrate this framework with the typical Symfony\ncomponents used to work with files."),(0,i.kt)("admonition",{title:"Preparation",type:"info"},(0,i.kt)("p",{parentName:"admonition"},"To enable this feature, you need to install the package\n",(0,i.kt)("inlineCode",{parentName:"p"},"rekalogika/file-symfony-bridge"),":"),(0,i.kt)("pre",{parentName:"admonition"},(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-symfony-bridge\n"))),(0,i.kt)("h2",{id:"components-summary"},"Components Summary"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Adapters to convert HttpFoundation ",(0,i.kt)("inlineCode",{parentName:"li"},"File")," objects to a ",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface")," and\nvice versa, with special handling for ",(0,i.kt)("inlineCode",{parentName:"li"},"UploadedFile"),"."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"FileResponse")," for streaming a ",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface")," to client web browser."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"FileType")," form that works with ",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface")," objects."),(0,i.kt)("li",{parentName:"ul"},"A form transformer ",(0,i.kt)("inlineCode",{parentName:"li"},"FileTransformer")," that you can add to an existing Symfony\n",(0,i.kt)("inlineCode",{parentName:"li"},"FileType")," fields so that it gives us a ",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface")," instead of an\n",(0,i.kt)("inlineCode",{parentName:"li"},"UploadedFile")," object."),(0,i.kt)("li",{parentName:"ul"},"A form extension ",(0,i.kt)("inlineCode",{parentName:"li"},"FileTypeExtension")," that you can optionally register to\nautomatically convert all the existing Symfony ",(0,i.kt)("inlineCode",{parentName:"li"},"FileType")," so they all give us\na ",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface"),"."),(0,i.kt)("li",{parentName:"ul"},"Subclassed ",(0,i.kt)("inlineCode",{parentName:"li"},"FileValidator")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"ImageValidator")," that works with\n",(0,i.kt)("inlineCode",{parentName:"li"},"FileInterface")," objects.")),(0,i.kt)("h2",{id:"adapters"},"Adapters"),(0,i.kt)("p",null,"Converts a HttpFoundation ",(0,i.kt)("inlineCode",{parentName:"p"},"File")," (and child classes, including ",(0,i.kt)("inlineCode",{parentName:"p"},"UploadedFile"),")\nto a ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\HttpFoundation\\FromHttpFoundationFileAdapter;\nuse Symfony\\Component\\HttpFoundation\\File\\File;\n\n/** @var File $httpFoundationFile */\n\n$file = FromHttpFoundationFileAdapter::adapt($httpFoundationFile);\n")),(0,i.kt)("p",null,"However, it is more convenient to use the universal adapter instead, although\nthe universal adapter still needs this package to be installed."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\File\\File;\nuse Rekalogika\\File\\FileAdapter;\n\n/** @var File $httpFoundationFile */\n\n$file = FileAdapter::adapt($httpFoundationFile);\n")),(0,i.kt)("p",null,"Converts a ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," to a HttpFoundation ",(0,i.kt)("inlineCode",{parentName:"p"},"File"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\HttpFoundation\\ToHttpFoundationFileAdapter;\nuse Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n$httpFoundationFile = ToHttpFoundationFileAdapter::adapt($file);\n")),(0,i.kt)("h2",{id:"streaming-a-fileinterface"},"Streaming a FileInterface"),(0,i.kt)("p",null,"To stream a ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," to the client's web browser, you can use\n",(0,i.kt)("inlineCode",{parentName:"p"},"FileResponse"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\HttpFoundation\\FileResponse;\nuse Rekalogika\\Contracts\\File\\FileInterface;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass SomeController\n{\n public function download(): Response\n {\n /** @var FileInterface $file */\n $file = ...;\n\n return new FileResponse($file);\n }\n}\n")),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"FileResponse")," accepts additional optional parameters:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"$status"),": HTTP status code. Default: ",(0,i.kt)("inlineCode",{parentName:"li"},"200"),"."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"$headers"),": Array of additional headers. Default: ",(0,i.kt)("inlineCode",{parentName:"li"},"[]"),"."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"$disposition"),": Force the first parameter of the ",(0,i.kt)("inlineCode",{parentName:"li"},"Content-Disposition")," header\nto the specified value. It can be ",(0,i.kt)("inlineCode",{parentName:"li"},"attachment")," or ",(0,i.kt)("inlineCode",{parentName:"li"},"inline"),". The filename is\nautomatically taken from the metadata.")),(0,i.kt)("h2",{id:"forms"},"Forms"),(0,i.kt)("p",null,"We provide a ",(0,i.kt)("inlineCode",{parentName:"p"},"FileType")," that works with ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," objects. This is\nbasically the same as Symfony's ",(0,i.kt)("inlineCode",{parentName:"p"},"FileType")," with a transformer built-in:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\Form\\FileType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass SomeFormType extends AbstractType\n{\n public function buildForm(FormBuilderInterface $builder, array $options): void\n {\n $builder\n // ...\n ->add('file', FileType::class, [\n // ...\n ])\n ;\n }\n}\n")),(0,i.kt)("p",null,"If for some reason you cannot change the form type, you can use\n",(0,i.kt)("inlineCode",{parentName:"p"},"FileTransformer")," to transform existing fields. It should work with Symfony's\n",(0,i.kt)("inlineCode",{parentName:"p"},"FileType")," and any third-party form types with a compatible behavior:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\Form\\FileTransformer;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType;\nuse Symfony\\Component\\Form\\AbstractType;\nuse Symfony\\Component\\Form\\FormBuilderInterface;\n\nclass SomeFormType extends AbstractType\n{\n public function buildForm(FormBuilderInterface $builder, array $options): void\n {\n $builder\n ->add('file', FileType::class, [\n // ...\n ]);\n\n $builder->get('file')->addModelTransformer(new FileTransformer());\n }\n}\n")),(0,i.kt)("p",null,"You can also modify all the existing Symfony's ",(0,i.kt)("inlineCode",{parentName:"p"},"FileType")," fields en masse by\nregistering the ",(0,i.kt)("inlineCode",{parentName:"p"},"FileTypeExtension"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/services.yaml",title:"config/services.yaml"},"services:\n Rekalogika\\File\\Bridge\\Symfony\\Form\\FileTypeExtension:\n tags:\n - { name: form.type_extension }\n")),(0,i.kt)("h2",{id:"validators"},"Validators"),(0,i.kt)("p",null,"We provide ",(0,i.kt)("inlineCode",{parentName:"p"},"File")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"Image")," validators. They are the same as Symfony's\n",(0,i.kt)("inlineCode",{parentName:"p"},"File")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"Image")," validators, except that they work with ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface"),"\nobjects instead of HttpFoundation ",(0,i.kt)("inlineCode",{parentName:"p"},"File")," objects:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\File\\Bridge\\Symfony\\Constraints\\File as FileConstraint;\nuse Rekalogika\\File\\Bridge\\Symfony\\Constraints\\Image as ImageConstraint;\n\nclass Product\n{\n #[ImageConstraint(minWidth: '1000']\n private ?FileInterface $photo = null;\n\n #[ImageConstraint(maxSize: '10000k']\n private ?FileInterface $manual = null;\n\n // ...\n}\n")),(0,i.kt)("admonition",{type:"caution"},(0,i.kt)("p",{parentName:"admonition"},"Due to how the adapters work, some validator functions might not work\ncorrectly, like those that check file names.")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6cacf3ff.481b774d.js b/assets/js/6cacf3ff.481b774d.js deleted file mode 100644 index 057596ff..00000000 --- a/assets/js/6cacf3ff.481b774d.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[160],{3905:(e,t,i)=>{i.d(t,{Zo:()=>c,kt:()=>u});var n=i(7294);function r(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function a(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function o(e){for(var t=1;t=0||(r[i]=e[i]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(r[i]=e[i])}return r}var d=n.createContext({}),p=function(e){var t=n.useContext(d),i=t;return e&&(i="function"==typeof e?e(t):o(o({},t),e)),i},c=function(e){var t=p(e.components);return n.createElement(d.Provider,{value:t},e.children)},f="mdxType",s={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var i=e.components,r=e.mdxType,a=e.originalType,d=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),f=p(i),m=r,u=f["".concat(d,".").concat(m)]||f[m]||s[m]||a;return i?n.createElement(u,o(o({ref:t},c),{},{components:i})):n.createElement(u,o({ref:t},c))}));function u(e,t){var i=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=i.length,o=new Array(a);o[0]=m;var l={};for(var d in t)hasOwnProperty.call(t,d)&&(l[d]=t[d]);l.originalType=e,l[f]="string"==typeof e?e:r,o[1]=l;for(var p=2;p{i.r(t),i.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>s,frontMatter:()=>a,metadata:()=>l,toc:()=>p});var n=i(7462),r=(i(7294),i(3905));const a={title:"Derivation"},o=void 0,l={unversionedId:"file/derivation",id:"file/derivation",title:"Derivation",description:"This chapter describes the concept of file derivation and the pipe & filters",source:"@site/docs/file/06-derivation.md",sourceDirName:"file",slug:"/file/derivation",permalink:"/file/derivation",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/06-derivation.md",tags:[],version:"current",sidebarPosition:6,frontMatter:{title:"Derivation"},sidebar:"docs",previous:{title:"Metadata",permalink:"/file/metadata"},next:{title:"Lazy-Loading Proxy",permalink:"/file/proxy"}},d={},p=[{value:"Derivation",id:"derivation",level:2},{value:"Low-Level Implementation",id:"low-level-implementation",level:2},{value:"Pipes & Filters Pattern",id:"pipes--filters-pattern",level:2}],c={toc:p},f="wrapper";function s(e){let{components:t,...i}=e;return(0,r.kt)(f,(0,n.Z)({},c,i,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"This chapter describes the concept of file derivation and the pipe & filters\npattern applied to ",(0,r.kt)("inlineCode",{parentName:"p"},"FileInterface"),"."),(0,r.kt)("h2",{id:"derivation"},"Derivation"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"FileInterface")," supports what we call 'derivation'. A file can have one or more\nderivation of itself. For example, an image file can have a thumbnail, medium,\nand large derivation. A derived file can also be derived further. For example, a\nthumbnail can be in the original aspect ratio, or square-cropped."),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"FileInterface")," provides the method ",(0,r.kt)("inlineCode",{parentName:"p"},"getDerivation()")," that returns a\n",(0,r.kt)("inlineCode",{parentName:"p"},"FilePointer")," to the derived file. Our ",(0,r.kt)("inlineCode",{parentName:"p"},"File")," objects ensure that a derivation\ncannot be made if the file is in the local filesystem, or in an ad-hoc\nfilesystem, to avoid cluttering the local filesystem with unwanted files."),(0,r.kt)("h2",{id:"low-level-implementation"},"Low-Level Implementation"),(0,r.kt)("p",null,"At the low level, a derivation is created simply by appending the derivation ID\nto the original file's key. For example, if the original file's key is:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337\n")),(0,r.kt)("p",null,"then, with the derivation ID '100px', the derived file's key becomes:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337.d/100px\n")),(0,r.kt)("p",null,"Deleting the original file will also result in the deletion of all of its\nderivations."),(0,r.kt)("p",null,"Derivation can be nested. Suppose the derived file above will be derived further\nwith the derivation ID of 'square', then the derived file's key becomes:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337.d/100px.d/square\n")),(0,r.kt)("admonition",{type:"caution"},(0,r.kt)("p",{parentName:"admonition"},"Because each derivation step requires a round trip to the storage backend, it is\nnot recommended to nest derivations too deep.")),(0,r.kt)("h2",{id:"pipes--filters-pattern"},"Pipes & Filters Pattern"),(0,r.kt)("p",null,"Derivation can be used as the building block of filters. A filter is a service\nthat perform opportunistic creation and caching of a derived file from a source\nfile."),(0,r.kt)("p",null,"A filter can be applied to a ",(0,r.kt)("inlineCode",{parentName:"p"},"FileInterface")," and does the following:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Obtain the original file."),(0,r.kt)("li",{parentName:"ol"},"Determine the derivation ID from the parameters provided by the caller. For\nexample, if the caller wants to get a square thumbnail of an image, the\nfilter can use the derivation ID like 'thumbnail-square'."),(0,r.kt)("li",{parentName:"ol"},"Call ",(0,r.kt)("inlineCode",{parentName:"li"},"FileInterface::getDerivation()")," to get a pointer to the derived file."),(0,r.kt)("li",{parentName:"ol"},"Call ",(0,r.kt)("inlineCode",{parentName:"li"},"FileRepository::get()")," to get the derived file.",(0,r.kt)("ol",{parentName:"li"},(0,r.kt)("li",{parentName:"ol"},"If the derived file does not exist, produce the derived file, and write to\nthe pointer."),(0,r.kt)("li",{parentName:"ol"},"If the derived file exists and newer than the original file, return it."),(0,r.kt)("li",{parentName:"ol"},"If the derived file exists and older than the original file, produce the\nderived file, then overwrite the old derived file.")))),(0,r.kt)("p",null,"The caller can then use the filter to create a modified version of the original\nfile without having to worry about the details."),(0,r.kt)("p",null,"We provide the package\n",(0,r.kt)("a",{parentName:"p",href:"/file-bundle/creating-filters"},(0,r.kt)("inlineCode",{parentName:"a"},"rekalogika/file-derivation"))," to\nstreamline the creation of filters within the Symfony framework."))}s.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/6cacf3ff.ef2782ff.js b/assets/js/6cacf3ff.ef2782ff.js new file mode 100644 index 00000000..ee37047f --- /dev/null +++ b/assets/js/6cacf3ff.ef2782ff.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[160],{3905:(e,t,i)=>{i.d(t,{Zo:()=>c,kt:()=>u});var n=i(7294);function r(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function a(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,n)}return i}function o(e){for(var t=1;t=0||(r[i]=e[i]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(r[i]=e[i])}return r}var d=n.createContext({}),p=function(e){var t=n.useContext(d),i=t;return e&&(i="function"==typeof e?e(t):o(o({},t),e)),i},c=function(e){var t=p(e.components);return n.createElement(d.Provider,{value:t},e.children)},f="mdxType",s={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var i=e.components,r=e.mdxType,a=e.originalType,d=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),f=p(i),m=r,u=f["".concat(d,".").concat(m)]||f[m]||s[m]||a;return i?n.createElement(u,o(o({ref:t},c),{},{components:i})):n.createElement(u,o({ref:t},c))}));function u(e,t){var i=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=i.length,o=new Array(a);o[0]=m;var l={};for(var d in t)hasOwnProperty.call(t,d)&&(l[d]=t[d]);l.originalType=e,l[f]="string"==typeof e?e:r,o[1]=l;for(var p=2;p{i.r(t),i.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>s,frontMatter:()=>a,metadata:()=>l,toc:()=>p});var n=i(7462),r=(i(7294),i(3905));const a={title:"Derivation"},o=void 0,l={unversionedId:"file/derivation",id:"file/derivation",title:"Derivation",description:"This chapter describes the concept of file derivation and the pipe & filter",source:"@site/docs/file/06-derivation.md",sourceDirName:"file",slug:"/file/derivation",permalink:"/file/derivation",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/06-derivation.md",tags:[],version:"current",sidebarPosition:6,frontMatter:{title:"Derivation"},sidebar:"docs",previous:{title:"Metadata",permalink:"/file/metadata"},next:{title:"Lazy-Loading Proxy",permalink:"/file/proxy"}},d={},p=[{value:"Derivation",id:"derivation",level:2},{value:"Low-Level Implementation",id:"low-level-implementation",level:2},{value:"Pipes & Filters Pattern",id:"pipes--filters-pattern",level:2}],c={toc:p},f="wrapper";function s(e){let{components:t,...i}=e;return(0,r.kt)(f,(0,n.Z)({},c,i,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"This chapter describes the concept of file derivation and the pipe & filter\npattern applied to ",(0,r.kt)("inlineCode",{parentName:"p"},"FileInterface"),"."),(0,r.kt)("h2",{id:"derivation"},"Derivation"),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"FileInterface")," supports what we call 'derivation'. A file can have one or more\nderivations of itself. For example, an image file can have a thumbnail, medium,\nand large derivation. A derived file can also be derived further. For example, a\nthumbnail can be in the original aspect ratio, or square-cropped."),(0,r.kt)("p",null,(0,r.kt)("inlineCode",{parentName:"p"},"FileInterface")," provides the method ",(0,r.kt)("inlineCode",{parentName:"p"},"getDerivation()")," that returns a\n",(0,r.kt)("inlineCode",{parentName:"p"},"FilePointer")," to the derived file. Our ",(0,r.kt)("inlineCode",{parentName:"p"},"File")," objects ensure that a derivation\ncannot be made if the file is in the local filesystem, or in an ad-hoc\nfilesystem, to avoid cluttering the local filesystem with unwanted files."),(0,r.kt)("h2",{id:"low-level-implementation"},"Low-Level Implementation"),(0,r.kt)("p",null,"At the low level, a derivation is created simply by appending the derivation ID\nto the original file's key. For example, if the original file's key is:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337\n")),(0,r.kt)("p",null,"then, with the derivation ID '100px', the derived file's key becomes:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337.d/100px\n")),(0,r.kt)("p",null,"Deleting the original file will also result in the deletion of all of its\nderivations."),(0,r.kt)("p",null,"Derivation can be nested. Suppose the derived file above will be derived further\nwith the derivation ID of 'square', then the derived file's key becomes:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337.d/100px.d/square\n")),(0,r.kt)("admonition",{type:"caution"},(0,r.kt)("p",{parentName:"admonition"},"Because each derivation step requires a round trip to the storage backend, it is\nnot recommended to nest derivations too deep.")),(0,r.kt)("h2",{id:"pipes--filters-pattern"},"Pipes & Filters Pattern"),(0,r.kt)("p",null,"Derivation can be used as the building block of filters. A filter is a service\nthat performs opportunistic creation and caching of a derived file from a source\nfile."),(0,r.kt)("p",null,"A filter can be applied to a ",(0,r.kt)("inlineCode",{parentName:"p"},"FileInterface")," and does the following:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Obtain the original file."),(0,r.kt)("li",{parentName:"ol"},"Determine the derivation ID from the parameters provided by the caller. For\nexample, if the caller wants to get a square thumbnail of an image, the\nfilter can use the derivation ID like 'thumbnail-square'."),(0,r.kt)("li",{parentName:"ol"},"Call ",(0,r.kt)("inlineCode",{parentName:"li"},"FileInterface::getDerivation()")," to get a pointer to the derived file."),(0,r.kt)("li",{parentName:"ol"},"Call ",(0,r.kt)("inlineCode",{parentName:"li"},"FileRepository::get()")," to get the derived file.",(0,r.kt)("ol",{parentName:"li"},(0,r.kt)("li",{parentName:"ol"},"If the derived file does not exist, produce the derived file, and write to\nthe pointer."),(0,r.kt)("li",{parentName:"ol"},"If the derived file exists and is newer than the original file, return it."),(0,r.kt)("li",{parentName:"ol"},"If the derived file exists and is older than the original file, produce\nthe derived file, then overwrite the old derived file.")))),(0,r.kt)("p",null,"The caller can then use the filter to create a modified version of the original\nfile without having to worry about the details."),(0,r.kt)("p",null,"We provide the package\n",(0,r.kt)("a",{parentName:"p",href:"/file-bundle/creating-filters"},(0,r.kt)("inlineCode",{parentName:"a"},"rekalogika/file-derivation"))," to\nstreamline the creation of filters within the Symfony framework."))}s.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/77356f5a.6abaf47c.js b/assets/js/77356f5a.84518d85.js similarity index 83% rename from assets/js/77356f5a.6abaf47c.js rename to assets/js/77356f5a.84518d85.js index f11fd500..daa496aa 100644 --- a/assets/js/77356f5a.6abaf47c.js +++ b/assets/js/77356f5a.84518d85.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[881],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>h});var a=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),c=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return a.createElement(l.Provider,{value:t},e.children)},m="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),m=c(n),u=i,h=m["".concat(l,".").concat(u)]||m[u]||d[u]||r;return n?a.createElement(h,o(o({ref:t},p),{},{components:n})):a.createElement(h,o({ref:t},p))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=u;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[m]="string"==typeof e?e:i,o[1]=s;for(var c=2;c{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var a=n(7462),i=(n(7294),n(3905));const r={title:"Immediate Dispatcher Handling & Troubleshooting"},o=void 0,s={unversionedId:"domain-event/immediate-dispatcher",id:"domain-event/immediate-dispatcher",title:"Immediate Dispatcher Handling & Troubleshooting",description:"Immediate Dispatcher in Unit Tests",source:"@site/docs/domain-event/03-immediate-dispatcher.md",sourceDirName:"domain-event",slug:"/domain-event/immediate-dispatcher",permalink:"/domain-event/immediate-dispatcher",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/domain-event/03-immediate-dispatcher.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{title:"Immediate Dispatcher Handling & Troubleshooting"},sidebar:"docs",previous:{title:"Manual Control",permalink:"/domain-event/manual-control"},next:{title:"Tips and Best Practices",permalink:"/domain-event/tips"}},l={},c=[{value:"Immediate Dispatcher in Unit Tests",id:"immediate-dispatcher-in-unit-tests",level:3}],p={toc:c},m="wrapper";function d(e){let{components:t,...n}=e;return(0,i.kt)(m,(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h3",{id:"immediate-dispatcher-in-unit-tests"},"Immediate Dispatcher in Unit Tests"),(0,i.kt)("p",null,"Immediate event dispatcher works by installing the event dispatcher to a static\nvariable. This installation happens on several opportunities:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"In these events: ",(0,i.kt)("inlineCode",{parentName:"li"},"kernel.request")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"console.command"),"."),(0,i.kt)("li",{parentName:"ul"},"During the initialization of ",(0,i.kt)("inlineCode",{parentName:"li"},"ManagerRegistry"),"."),(0,i.kt)("li",{parentName:"ul"},"During the initialization of an ",(0,i.kt)("inlineCode",{parentName:"li"},"EntityManagerInterface"),".")),(0,i.kt)("p",null,"When any of these don't occur, there is no opportunity to install the event\ndispatcher. This usually happens only in isolated unit tests. To fix the\nproblem, you can install a stub event dispatcher manually like the following."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use PHPUnit\\Framework\\TestCase;\nuse Rekalogika\\DomainEvent\\ImmediateDomainEventDispatcherInstaller;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcher;\n\nclass SomeTest extends TestCase\n{\n public function setUp(): void\n {\n $installer = new ImmediateDomainEventDispatcherInstaller(new EventDispatcher);\n $installer->install();\n\n }\n\n // ...\n}\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Stub dispatcher doesn't do anything. If you want to test the dispatching, you\nneed to get the real dispatcher from the container.")),(0,i.kt)("p",null,"In integration tests where you have access to the service container, but the\ntests don't involve ",(0,i.kt)("inlineCode",{parentName:"p"},"EntityManager")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"ManagerRegistry"),", you can manually pull\nthe installer from the container to install the immediate dispatcher:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\DomainEvent\\ImmediateDomainEventDispatcherInstaller;\nuse Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase;\n\nclass SomeTest extends KernelTestCase\n{\n public function setUp(): void\n {\n self::bootKernel();\n static::getContainer()\n ->get(ImmediateDomainEventDispatcherInstaller::class)->install();\n }\n\n // ...\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[881],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>h});var a=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=a.createContext({}),c=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return a.createElement(l.Provider,{value:t},e.children)},m="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},u=a.forwardRef((function(e,t){var n=e.components,i=e.mdxType,r=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),m=c(n),u=i,h=m["".concat(l,".").concat(u)]||m[u]||d[u]||r;return n?a.createElement(h,o(o({ref:t},p),{},{components:n})):a.createElement(h,o({ref:t},p))}));function h(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=n.length,o=new Array(r);o[0]=u;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s[m]="string"==typeof e?e:i,o[1]=s;for(var c=2;c{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var a=n(7462),i=(n(7294),n(3905));const r={title:"Immediate Dispatcher Handling & Troubleshooting"},o=void 0,s={unversionedId:"domain-event/immediate-dispatcher",id:"domain-event/immediate-dispatcher",title:"Immediate Dispatcher Handling & Troubleshooting",description:"Immediate Dispatcher in Unit Tests",source:"@site/docs/domain-event/03-immediate-dispatcher.md",sourceDirName:"domain-event",slug:"/domain-event/immediate-dispatcher",permalink:"/domain-event/immediate-dispatcher",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/domain-event/03-immediate-dispatcher.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{title:"Immediate Dispatcher Handling & Troubleshooting"},sidebar:"docs",previous:{title:"Manual Control",permalink:"/domain-event/manual-control"},next:{title:"Tips and Best Practices",permalink:"/domain-event/tips"}},l={},c=[{value:"Immediate Dispatcher in Unit Tests",id:"immediate-dispatcher-in-unit-tests",level:3}],p={toc:c},m="wrapper";function d(e){let{components:t,...n}=e;return(0,i.kt)(m,(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h3",{id:"immediate-dispatcher-in-unit-tests"},"Immediate Dispatcher in Unit Tests"),(0,i.kt)("p",null,"Immediate event dispatcher works by installing the event dispatcher to a static\nvariable. This installation happens on several opportunities:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"In these events: ",(0,i.kt)("inlineCode",{parentName:"li"},"kernel.request")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"console.command"),"."),(0,i.kt)("li",{parentName:"ul"},"During the initialization of ",(0,i.kt)("inlineCode",{parentName:"li"},"ManagerRegistry"),"."),(0,i.kt)("li",{parentName:"ul"},"During the initialization of an ",(0,i.kt)("inlineCode",{parentName:"li"},"EntityManagerInterface"),".")),(0,i.kt)("p",null,"When any of these don't occur, there is no opportunity to install the event\ndispatcher. This usually happens only in isolated unit tests. To fix the\nproblem, you can install a stub event dispatcher manually like the following."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use PHPUnit\\Framework\\TestCase;\nuse Rekalogika\\DomainEvent\\ImmediateDomainEventDispatcherInstaller;\nuse Symfony\\Component\\EventDispatcher\\EventDispatcher;\n\nclass SomeTest extends TestCase\n{\n public function setUp(): void\n {\n $installer = new ImmediateDomainEventDispatcherInstaller(new EventDispatcher);\n $installer->install();\n\n }\n\n // ...\n}\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"The stub dispatcher doesn't do anything. If you want to test the dispatching,\nyou need to get the real dispatcher from the container.")),(0,i.kt)("p",null,"In integration tests where you have access to the service container, but the\ntests don't involve ",(0,i.kt)("inlineCode",{parentName:"p"},"EntityManager")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"ManagerRegistry"),", you can manually pull\nthe installer from the container to install the immediate dispatcher:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\DomainEvent\\ImmediateDomainEventDispatcherInstaller;\nuse Symfony\\Bundle\\FrameworkBundle\\Test\\KernelTestCase;\n\nclass SomeTest extends KernelTestCase\n{\n public function setUp(): void\n {\n self::bootKernel();\n static::getContainer()\n ->get(ImmediateDomainEventDispatcherInstaller::class)->install();\n }\n\n // ...\n}\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.6812ad13.js b/assets/js/935f2afb.45e5fd75.js similarity index 72% rename from assets/js/935f2afb.6812ad13.js rename to assets/js/935f2afb.45e5fd75.js index 933643bb..f495e26f 100644 --- a/assets/js/935f2afb.6812ad13.js +++ b/assets/js/935f2afb.45e5fd75.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"docs":[{"type":"link","label":"rekalogika/direct-property-access","href":"/direct-property-access/","docId":"direct-property-access/index"},{"type":"category","label":"rekalogika/domain-event","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Introduction & Installation","href":"/domain-event/intro","docId":"domain-event/intro"},{"type":"link","label":"Basic Usage","href":"/domain-event/basic-usage","docId":"domain-event/basic-usage"},{"type":"link","label":"Manual Control","href":"/domain-event/manual-control","docId":"domain-event/manual-control"},{"type":"link","label":"Immediate Dispatcher Handling & Troubleshooting","href":"/domain-event/immediate-dispatcher","docId":"domain-event/immediate-dispatcher"},{"type":"link","label":"Tips and Best Practices","href":"/domain-event/tips","docId":"domain-event/tips"}],"href":"/domain-event/"},{"type":"category","label":"rekalogika/file","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Introduction","href":"/file/intro","docId":"file/intro"},{"type":"link","label":"Installation & Configuration","href":"/file/installation","docId":"file/installation"},{"type":"link","label":"Concepts & Terms","href":"/file/concepts","docId":"file/concepts"},{"type":"link","label":"Using File & FileRepository","href":"/file/file","docId":"file/file"},{"type":"link","label":"Adapters","href":"/file/adapters","docId":"file/adapters"},{"type":"link","label":"Metadata","href":"/file/metadata","docId":"file/metadata"},{"type":"link","label":"Derivation","href":"/file/derivation","docId":"file/derivation"},{"type":"link","label":"Lazy-Loading Proxy","href":"/file/proxy","docId":"file/proxy"}],"href":"/file/"},{"type":"category","label":"rekalogika/file-bundle","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Introduction","href":"/file-bundle/intro","docId":"file-bundle/intro"},{"type":"link","label":"Installation & Configuration","href":"/file-bundle/installation","docId":"file-bundle/installation"},{"type":"link","label":"Associating Files with Doctrine Entities","href":"/file-bundle/doctrine-entity","docId":"file-bundle/doctrine-entity"},{"type":"link","label":"Symfony Integration","href":"/file-bundle/symfony","docId":"file-bundle/symfony"},{"type":"link","label":"Serving Files","href":"/file-bundle/serving-files","docId":"file-bundle/serving-files"},{"type":"link","label":"Filtering","href":"/file-bundle/filtering","docId":"file-bundle/filtering"},{"type":"link","label":"File Association Internal Details","href":"/file-bundle/entity-association-internal","docId":"file-bundle/entity-association-internal"},{"type":"link","label":"Creating Filters","href":"/file-bundle/creating-filters","docId":"file-bundle/creating-filters"},{"type":"link","label":"Creating an Object ID Resolver","href":"/file-bundle/object-id-resolver","docId":"file-bundle/object-id-resolver"}],"href":"/file-bundle/"},{"type":"link","label":"rekalogika/psr-16-simple-cache-bundle","href":"/psr-16-simple-cache-bundle/","docId":"psr-16-simple-cache-bundle/index"},{"type":"link","label":"rekalogika/reconstitutor","href":"/reconstitutor/","docId":"reconstitutor/index"},{"type":"link","label":"rekalogika/temporary-url-bundle","href":"/temporary-url-bundle/","docId":"temporary-url-bundle/index"}]},"docs":{"direct-property-access/index":{"id":"direct-property-access/index","title":"rekalogika/direct-property-access","description":"Implementation of Symfony\'s PropertyAccessorInterface that reads and writes","sidebar":"docs"},"domain-event/basic-usage":{"id":"domain-event/basic-usage","title":"Basic Usage","description":"Creating Domain Events","sidebar":"docs"},"domain-event/immediate-dispatcher":{"id":"domain-event/immediate-dispatcher","title":"Immediate Dispatcher Handling & Troubleshooting","description":"Immediate Dispatcher in Unit Tests","sidebar":"docs"},"domain-event/index":{"id":"domain-event/index","title":"rekalogika/domain-event","description":"A domain event pattern","sidebar":"docs"},"domain-event/intro":{"id":"domain-event/intro","title":"Introduction & Installation","description":"An implementation of domain event pattern","sidebar":"docs"},"domain-event/manual-control":{"id":"domain-event/manual-control","title":"Manual Control","description":"To manually manage domain events, you can use","sidebar":"docs"},"domain-event/tips":{"id":"domain-event/tips","title":"Tips and Best Practices","description":"This chapter explains the tips and our best practices that others might find","sidebar":"docs"},"file-bundle/creating-filters":{"id":"file-bundle/creating-filters","title":"Creating Filters","description":"This chapter explains how to create your own file filters using","sidebar":"docs"},"file-bundle/doctrine-entity":{"id":"file-bundle/doctrine-entity","title":"Associating Files with Doctrine Entities","description":"This chapter describes how to create a file property in a Doctrine entity that","sidebar":"docs"},"file-bundle/entity-association-internal":{"id":"file-bundle/entity-association-internal","title":"File Association Internal Details","description":"Where The Files Are Stored","sidebar":"docs"},"file-bundle/filtering":{"id":"file-bundle/filtering","title":"Filtering","description":"In this framework, \'filtering\' is the opportunistic creation & caching of","sidebar":"docs"},"file-bundle/index":{"id":"file-bundle/index","title":"rekalogika/file-bundle","description":"Integrates rekalogika/file library into Symfony framework.","sidebar":"docs"},"file-bundle/installation":{"id":"file-bundle/installation","title":"Installation & Configuration","description":"Installation within a Symfony application.","sidebar":"docs"},"file-bundle/intro":{"id":"file-bundle/intro","title":"Introduction","description":"Symfony bundle to easily integrate the rekalogika/file framework and related","sidebar":"docs"},"file-bundle/object-id-resolver":{"id":"file-bundle/object-id-resolver","title":"Creating an Object ID Resolver","description":"The framework assumes that the ID of your entity is returned by the method","sidebar":"docs"},"file-bundle/serving-files":{"id":"file-bundle/serving-files","title":"Serving Files","description":"This chapter describes how to serve files to client web browser.","sidebar":"docs"},"file-bundle/symfony":{"id":"file-bundle/symfony","title":"Symfony Integration","description":"This chapter describes how to integrate this framework with the typical Symfony","sidebar":"docs"},"file/adapters":{"id":"file/adapters","title":"Adapters","description":"The library provides a FileAdapter class that can be used to adapt or convert","sidebar":"docs"},"file/concepts":{"id":"file/concepts","title":"Concepts & Terms","description":"Terms","sidebar":"docs"},"file/derivation":{"id":"file/derivation","title":"Derivation","description":"This chapter describes the concept of file derivation and the pipe & filters","sidebar":"docs"},"file/file":{"id":"file/file","title":"Using File & FileRepository","description":"When using this framework, the user will primarily work with the","sidebar":"docs"},"file/index":{"id":"file/index","title":"rekalogika/file","description":"High-level file abstraction library built on top of Flysystem. It lets you work","sidebar":"docs"},"file/installation":{"id":"file/installation","title":"Installation & Configuration","description":"This section explains how to install and configure the rekalogika/file","sidebar":"docs"},"file/intro":{"id":"file/intro","title":"Introduction","description":"High-level file abstraction library built on top of Flysystem. It lets you work","sidebar":"docs"},"file/metadata":{"id":"file/metadata","title":"Metadata","description":"This chapter describes how file metadata is handled by this library.","sidebar":"docs"},"file/proxy":{"id":"file/proxy","title":"Lazy-Loading Proxy","description":"This chapter describes how to lazy-load a file.","sidebar":"docs"},"psr-16-simple-cache-bundle/index":{"id":"psr-16-simple-cache-bundle/index","title":"rekalogika/psr-16-simple-cache-bundle","description":"Enables PSR-16 Simple Cache services in Symfony projects. These were previously","sidebar":"docs"},"reconstitutor/index":{"id":"reconstitutor/index","title":"rekalogika/reconstitutor","description":"This library provides a thin layer that sits above Doctrine events to help you","sidebar":"docs"},"temporary-url-bundle/index":{"id":"temporary-url-bundle/index","title":"rekalogika/temporary-url-bundle","description":"Symfony bundle for creating temporary URLs to your resources. You provide the","sidebar":"docs"}}}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"docs":[{"type":"link","label":"rekalogika/direct-property-access","href":"/direct-property-access/","docId":"direct-property-access/index"},{"type":"category","label":"rekalogika/domain-event","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Introduction & Installation","href":"/domain-event/intro","docId":"domain-event/intro"},{"type":"link","label":"Basic Usage","href":"/domain-event/basic-usage","docId":"domain-event/basic-usage"},{"type":"link","label":"Manual Control","href":"/domain-event/manual-control","docId":"domain-event/manual-control"},{"type":"link","label":"Immediate Dispatcher Handling & Troubleshooting","href":"/domain-event/immediate-dispatcher","docId":"domain-event/immediate-dispatcher"},{"type":"link","label":"Tips and Best Practices","href":"/domain-event/tips","docId":"domain-event/tips"}],"href":"/domain-event/"},{"type":"category","label":"rekalogika/file","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Introduction","href":"/file/intro","docId":"file/intro"},{"type":"link","label":"Installation & Configuration","href":"/file/installation","docId":"file/installation"},{"type":"link","label":"Concepts & Terms","href":"/file/concepts","docId":"file/concepts"},{"type":"link","label":"Using File & FileRepository","href":"/file/file","docId":"file/file"},{"type":"link","label":"Adapters","href":"/file/adapters","docId":"file/adapters"},{"type":"link","label":"Metadata","href":"/file/metadata","docId":"file/metadata"},{"type":"link","label":"Derivation","href":"/file/derivation","docId":"file/derivation"},{"type":"link","label":"Lazy-Loading Proxy","href":"/file/proxy","docId":"file/proxy"}],"href":"/file/"},{"type":"category","label":"rekalogika/file-bundle","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Introduction","href":"/file-bundle/intro","docId":"file-bundle/intro"},{"type":"link","label":"Installation & Configuration","href":"/file-bundle/installation","docId":"file-bundle/installation"},{"type":"link","label":"Associating Files with Doctrine Entities","href":"/file-bundle/doctrine-entity","docId":"file-bundle/doctrine-entity"},{"type":"link","label":"Symfony Integration","href":"/file-bundle/symfony","docId":"file-bundle/symfony"},{"type":"link","label":"Serving Files","href":"/file-bundle/serving-files","docId":"file-bundle/serving-files"},{"type":"link","label":"Filtering","href":"/file-bundle/filtering","docId":"file-bundle/filtering"},{"type":"link","label":"File Association Internal Details","href":"/file-bundle/entity-association-internal","docId":"file-bundle/entity-association-internal"},{"type":"link","label":"Creating Filters","href":"/file-bundle/creating-filters","docId":"file-bundle/creating-filters"},{"type":"link","label":"Creating an Object ID Resolver","href":"/file-bundle/object-id-resolver","docId":"file-bundle/object-id-resolver"}],"href":"/file-bundle/"},{"type":"link","label":"rekalogika/psr-16-simple-cache-bundle","href":"/psr-16-simple-cache-bundle/","docId":"psr-16-simple-cache-bundle/index"},{"type":"link","label":"rekalogika/reconstitutor","href":"/reconstitutor/","docId":"reconstitutor/index"},{"type":"link","label":"rekalogika/temporary-url-bundle","href":"/temporary-url-bundle/","docId":"temporary-url-bundle/index"}]},"docs":{"direct-property-access/index":{"id":"direct-property-access/index","title":"rekalogika/direct-property-access","description":"Implementation of Symfony\'s PropertyAccessorInterface that reads and writes","sidebar":"docs"},"domain-event/basic-usage":{"id":"domain-event/basic-usage","title":"Basic Usage","description":"Creating Domain Events","sidebar":"docs"},"domain-event/immediate-dispatcher":{"id":"domain-event/immediate-dispatcher","title":"Immediate Dispatcher Handling & Troubleshooting","description":"Immediate Dispatcher in Unit Tests","sidebar":"docs"},"domain-event/index":{"id":"domain-event/index","title":"rekalogika/domain-event","description":"A domain event pattern","sidebar":"docs"},"domain-event/intro":{"id":"domain-event/intro","title":"Introduction & Installation","description":"An implementation of domain event pattern","sidebar":"docs"},"domain-event/manual-control":{"id":"domain-event/manual-control","title":"Manual Control","description":"To manually manage domain events, you can use","sidebar":"docs"},"domain-event/tips":{"id":"domain-event/tips","title":"Tips and Best Practices","description":"This chapter explains the tips and our best practices that others might find","sidebar":"docs"},"file-bundle/creating-filters":{"id":"file-bundle/creating-filters","title":"Creating Filters","description":"This chapter explains how to create your own file filters using","sidebar":"docs"},"file-bundle/doctrine-entity":{"id":"file-bundle/doctrine-entity","title":"Associating Files with Doctrine Entities","description":"This chapter describes how to create a file property in a Doctrine entity that","sidebar":"docs"},"file-bundle/entity-association-internal":{"id":"file-bundle/entity-association-internal","title":"File Association Internal Details","description":"Where The Files Are Stored","sidebar":"docs"},"file-bundle/filtering":{"id":"file-bundle/filtering","title":"Filtering","description":"In this framework, \'filtering\' is the opportunistic creation & caching of","sidebar":"docs"},"file-bundle/index":{"id":"file-bundle/index","title":"rekalogika/file-bundle","description":"Integrates rekalogika/file library into Symfony framework.","sidebar":"docs"},"file-bundle/installation":{"id":"file-bundle/installation","title":"Installation & Configuration","description":"Installation within a Symfony application.","sidebar":"docs"},"file-bundle/intro":{"id":"file-bundle/intro","title":"Introduction","description":"Symfony bundle to easily integrate the rekalogika/file framework and related","sidebar":"docs"},"file-bundle/object-id-resolver":{"id":"file-bundle/object-id-resolver","title":"Creating an Object ID Resolver","description":"The framework assumes that the ID of your entity is returned by the method","sidebar":"docs"},"file-bundle/serving-files":{"id":"file-bundle/serving-files","title":"Serving Files","description":"This chapter describes how to serve files to the client web browser.","sidebar":"docs"},"file-bundle/symfony":{"id":"file-bundle/symfony","title":"Symfony Integration","description":"This chapter describes how to integrate this framework with the typical Symfony","sidebar":"docs"},"file/adapters":{"id":"file/adapters","title":"Adapters","description":"The library provides a FileAdapter class that can be used to adapt or convert","sidebar":"docs"},"file/concepts":{"id":"file/concepts","title":"Concepts & Terms","description":"Terms","sidebar":"docs"},"file/derivation":{"id":"file/derivation","title":"Derivation","description":"This chapter describes the concept of file derivation and the pipe & filter","sidebar":"docs"},"file/file":{"id":"file/file","title":"Using File & FileRepository","description":"When using this framework, the user will primarily work with the","sidebar":"docs"},"file/index":{"id":"file/index","title":"rekalogika/file","description":"High-level file abstraction library built on top of Flysystem. It lets you work","sidebar":"docs"},"file/installation":{"id":"file/installation","title":"Installation & Configuration","description":"This section explains how to install and configure the rekalogika/file","sidebar":"docs"},"file/intro":{"id":"file/intro","title":"Introduction","description":"High-level file abstraction library built on top of Flysystem. It lets you work","sidebar":"docs"},"file/metadata":{"id":"file/metadata","title":"Metadata","description":"This chapter describes how file metadata is handled by this library.","sidebar":"docs"},"file/proxy":{"id":"file/proxy","title":"Lazy-Loading Proxy","description":"This chapter describes how to lazy-load a file.","sidebar":"docs"},"psr-16-simple-cache-bundle/index":{"id":"psr-16-simple-cache-bundle/index","title":"rekalogika/psr-16-simple-cache-bundle","description":"Enables PSR-16 Simple Cache services in Symfony projects. These were previously","sidebar":"docs"},"reconstitutor/index":{"id":"reconstitutor/index","title":"rekalogika/reconstitutor","description":"This library provides a thin layer that sits above Doctrine events to help you","sidebar":"docs"},"temporary-url-bundle/index":{"id":"temporary-url-bundle/index","title":"rekalogika/temporary-url-bundle","description":"Symfony bundle for creating temporary URLs to your resources. You provide the","sidebar":"docs"}}}')}}]); \ No newline at end of file diff --git a/assets/js/94d2cbb6.8cfcc62d.js b/assets/js/94d2cbb6.53dcaadc.js similarity index 78% rename from assets/js/94d2cbb6.8cfcc62d.js rename to assets/js/94d2cbb6.53dcaadc.js index 7a46dbb3..b0dc0cd4 100644 --- a/assets/js/94d2cbb6.8cfcc62d.js +++ b/assets/js/94d2cbb6.53dcaadc.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[138],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>m});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var u=a.createContext({}),s=function(e){var t=a.useContext(u),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=s(e.components);return a.createElement(u.Provider,{value:t},e.children)},d="mdxType",f={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},p=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,l=e.originalType,u=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),d=s(n),p=r,m=d["".concat(u,".").concat(p)]||d[p]||f[p]||l;return n?a.createElement(m,o(o({ref:t},c),{},{components:n})):a.createElement(m,o({ref:t},c))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=n.length,o=new Array(l);o[0]=p;var i={};for(var u in t)hasOwnProperty.call(t,u)&&(i[u]=t[u]);i.originalType=e,i[d]="string"==typeof e?e:r,o[1]=i;for(var s=2;s{n.d(t,{Z:()=>o});var a=n(7294),r=n(6010);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.Z)(l.tabItem,o),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>w});var a=n(7462),r=n(7294),l=n(6010),o=n(2466),i=n(6550),u=n(1980),s=n(7392),c=n(12);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function f(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,s.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function p(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.k6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u._X)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function b(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=f(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!p({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:n,groupId:a}),[d,b]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Nk)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=u??d;return p({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&i(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!p({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),s(e),b(e)}),[s,b,l]),tabValues:l}}var y=n(2389);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function h(e){let{className:t,block:n,selectedValue:i,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.o5)(),f=e=>{const t=e.currentTarget,n=c.indexOf(t),a=s[n].value;a!==i&&(d(t),u(a))},p=e=>{let t=null;switch(e.key){case"Enter":f(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.Z)("tabs",{"tabs--block":n},t)},s.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:p,onClick:f},o,{className:(0,l.Z)("tabs__item",g.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function k(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function v(e){const t=b(e);return r.createElement("div",{className:(0,l.Z)("tabs-container",g.tabList)},r.createElement(h,(0,a.Z)({},e,t)),r.createElement(k,(0,a.Z)({},e,t)))}function w(e){const t=(0,y.Z)();return r.createElement(v,(0,a.Z)({key:String(t)},e))}},9623:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>u,default:()=>m,frontMatter:()=>i,metadata:()=>s,toc:()=>d});var a=n(7462),r=(n(7294),n(3905)),l=n(4866),o=n(5162);const i={title:"Installation & Configuration"},u=void 0,s={unversionedId:"file-bundle/installation",id:"file-bundle/installation",title:"Installation & Configuration",description:"Installation within a Symfony application.",source:"@site/docs/file-bundle/02-installation.md",sourceDirName:"file-bundle",slug:"/file-bundle/installation",permalink:"/file-bundle/installation",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/02-installation.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{title:"Installation & Configuration"},sidebar:"docs",previous:{title:"Introduction",permalink:"/file-bundle/intro"},next:{title:"Associating Files with Doctrine Entities",permalink:"/file-bundle/doctrine-entity"}},c={},d=[{value:"Installation",id:"installation",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Integration With Flysystem Bundle",id:"integration-with-flysystem-bundle",level:2}],f={toc:d},p="wrapper";function m(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},f,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Installation within a Symfony application."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Most of the information under ",(0,r.kt)("inlineCode",{parentName:"p"},"rekalogika/file-bundle")," is applicable only to\nSymfony applications. If you are not using Symfony, please refer to the\n",(0,r.kt)("a",{parentName:"p",href:"/file/installation"},(0,r.kt)("inlineCode",{parentName:"a"},"rekalogika/file"))," documentation instead.")),(0,r.kt)("h2",{id:"installation"},"Installation"),(0,r.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,r.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,r.kt)(l.Z,{mdxType:"Tabs"},(0,r.kt)(o.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,r.kt)("p",null,"Open a command console, enter your project directory and execute:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-bundle\n"))),(0,r.kt)(o.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,r.kt)("p",null,"Step 1: Download the Bundle"),(0,r.kt)("p",null,"Open a command console, enter your project directory and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-bundle\n")),(0,r.kt)("p",null,"Step 2: Enable the Bundle"),(0,r.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,r.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\File\\Bundle\\RekalogikaFileBundle::class => ['all' => true],\n];\n")))),(0,r.kt)("h2",{id:"configuration"},"Configuration"),(0,r.kt)("p",null,"The bundle should work out of the box without configuration. By default, it will\ncreate a filesystem identified by 'default' that stores files in the directory\n",(0,r.kt)("inlineCode",{parentName:"p"},"%kernel.project_dir%/var/storage/default"),"."),(0,r.kt)("p",null,"The following is the default configuration:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/packages/rekalogika_file.yaml",title:"config/packages/rekalogika_file.yaml"},"rekalogika_file:\n filesystems:\n # our default filesystem service\n default: rekalogika.file.default_filesystem\n\n default_filesystem_directory: '%kernel.project_dir%/var/storage/default'\n")),(0,r.kt)("h2",{id:"integration-with-flysystem-bundle"},"Integration With Flysystem Bundle"),(0,r.kt)("p",null,"If you are using the Flysystem bundle, you can use the filesystems defined in\nthe Flysystem bundle:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/packages/rekalogika_file.yaml",title:"config/packages/rekalogika_file.yaml"},"rekalogika_file:\n filesystems:\n # 'default.storage' is the filesystem key under 'flysystem.storages'\n # in config/packages/flysystem.yaml\n default: 'default.storage'\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[138],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>m});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var u=a.createContext({}),s=function(e){var t=a.useContext(u),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=s(e.components);return a.createElement(u.Provider,{value:t},e.children)},d="mdxType",f={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},p=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,l=e.originalType,u=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),d=s(n),p=r,m=d["".concat(u,".").concat(p)]||d[p]||f[p]||l;return n?a.createElement(m,o(o({ref:t},c),{},{components:n})):a.createElement(m,o({ref:t},c))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var l=n.length,o=new Array(l);o[0]=p;var i={};for(var u in t)hasOwnProperty.call(t,u)&&(i[u]=t[u]);i.originalType=e,i[d]="string"==typeof e?e:r,o[1]=i;for(var s=2;s{n.d(t,{Z:()=>o});var a=n(7294),r=n(6010);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.Z)(l.tabItem,o),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>w});var a=n(7462),r=n(7294),l=n(6010),o=n(2466),i=n(6550),u=n(1980),s=n(7392),c=n(12);function d(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function f(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??d(n);return function(e){const t=(0,s.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function p(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.k6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,u._X)(l),(0,r.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(a.location.search);t.set(l,e),a.replace({...a.location,search:t.toString()})}),[l,a])]}function b(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,l=f(e),[o,i]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!p({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:l}))),[u,s]=m({queryString:n,groupId:a}),[d,b]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,l]=(0,c.Nk)(n);return[a,(0,r.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:a}),y=(()=>{const e=u??d;return p({value:e,tabValues:l})?e:null})();(0,r.useLayoutEffect)((()=>{y&&i(y)}),[y]);return{selectedValue:o,selectValue:(0,r.useCallback)((e=>{if(!p({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),s(e),b(e)}),[s,b,l]),tabValues:l}}var y=n(2389);const g={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function h(e){let{className:t,block:n,selectedValue:i,selectValue:u,tabValues:s}=e;const c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.o5)(),f=e=>{const t=e.currentTarget,n=c.indexOf(t),a=s[n].value;a!==i&&(d(t),u(a))},p=e=>{let t=null;switch(e.key){case"Enter":f(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.Z)("tabs",{"tabs--block":n},t)},s.map((e=>{let{value:t,label:n,attributes:o}=e;return r.createElement("li",(0,a.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:p,onClick:f},o,{className:(0,l.Z)("tabs__item",g.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function k(e){let{lazy:t,children:n,selectedValue:a}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function v(e){const t=b(e);return r.createElement("div",{className:(0,l.Z)("tabs-container",g.tabList)},r.createElement(h,(0,a.Z)({},e,t)),r.createElement(k,(0,a.Z)({},e,t)))}function w(e){const t=(0,y.Z)();return r.createElement(v,(0,a.Z)({key:String(t)},e))}},9623:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>u,default:()=>m,frontMatter:()=>i,metadata:()=>s,toc:()=>d});var a=n(7462),r=(n(7294),n(3905)),l=n(4866),o=n(5162);const i={title:"Installation & Configuration"},u=void 0,s={unversionedId:"file-bundle/installation",id:"file-bundle/installation",title:"Installation & Configuration",description:"Installation within a Symfony application.",source:"@site/docs/file-bundle/02-installation.md",sourceDirName:"file-bundle",slug:"/file-bundle/installation",permalink:"/file-bundle/installation",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/02-installation.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{title:"Installation & Configuration"},sidebar:"docs",previous:{title:"Introduction",permalink:"/file-bundle/intro"},next:{title:"Associating Files with Doctrine Entities",permalink:"/file-bundle/doctrine-entity"}},c={},d=[{value:"Installation",id:"installation",level:2},{value:"Configuration",id:"configuration",level:2},{value:"Integration With Flysystem Bundle",id:"integration-with-flysystem-bundle",level:2}],f={toc:d},p="wrapper";function m(e){let{components:t,...n}=e;return(0,r.kt)(p,(0,a.Z)({},f,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Installation within a Symfony application."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Most of the information under ",(0,r.kt)("inlineCode",{parentName:"p"},"rekalogika/file-bundle")," is applicable only to\nSymfony applications. If you are not using Symfony, please refer to the\n",(0,r.kt)("a",{parentName:"p",href:"/file/installation"},(0,r.kt)("inlineCode",{parentName:"a"},"rekalogika/file"))," documentation instead.")),(0,r.kt)("h2",{id:"installation"},"Installation"),(0,r.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,r.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,r.kt)(l.Z,{mdxType:"Tabs"},(0,r.kt)(o.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,r.kt)("p",null,"Open a command console, enter your project directory, and execute:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-bundle\n"))),(0,r.kt)(o.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,r.kt)("p",null,"Step 1: Download the Bundle"),(0,r.kt)("p",null,"Open a command console, enter your project directory, and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-bundle\n")),(0,r.kt)("p",null,"Step 2: Enable the Bundle"),(0,r.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,r.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\File\\Bundle\\RekalogikaFileBundle::class => ['all' => true],\n];\n")))),(0,r.kt)("h2",{id:"configuration"},"Configuration"),(0,r.kt)("p",null,"The bundle should work out of the box without configuration. By default, it will\ncreate a filesystem identified by 'default' that stores files in the directory\n",(0,r.kt)("inlineCode",{parentName:"p"},"%kernel.project_dir%/var/storage/default"),"."),(0,r.kt)("p",null,"The following is the default configuration:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/packages/rekalogika_file.yaml",title:"config/packages/rekalogika_file.yaml"},"rekalogika_file:\n filesystems:\n # our default filesystem service\n default: rekalogika.file.default_filesystem\n\n default_filesystem_directory: '%kernel.project_dir%/var/storage/default'\n")),(0,r.kt)("h2",{id:"integration-with-flysystem-bundle"},"Integration With Flysystem Bundle"),(0,r.kt)("p",null,"If you are using the Flysystem bundle, you can use the filesystems defined in\nthe Flysystem bundle:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/packages/rekalogika_file.yaml",title:"config/packages/rekalogika_file.yaml"},"rekalogika_file:\n filesystems:\n # 'default.storage' is the filesystem key under 'flysystem.storages'\n # in config/packages/flysystem.yaml\n default: 'default.storage'\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9e75f0a3.4d253da4.js b/assets/js/9e75f0a3.4d253da4.js deleted file mode 100644 index 86683f00..00000000 --- a/assets/js/9e75f0a3.4d253da4.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[748],{3905:(e,t,a)=>{a.d(t,{Zo:()=>c,kt:()=>h});var n=a(7294);function i(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function l(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function r(e){for(var t=1;t=0||(i[a]=e[a]);return i}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var s=n.createContext({}),d=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):r(r({},t),e)),a},c=function(e){var t=d(e.components);return n.createElement(s.Provider,{value:t},e.children)},m="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},f=n.forwardRef((function(e,t){var a=e.components,i=e.mdxType,l=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),m=d(a),f=i,h=m["".concat(s,".").concat(f)]||m[f]||p[f]||l;return a?n.createElement(h,r(r({ref:t},c),{},{components:a})):n.createElement(h,r({ref:t},c))}));function h(e,t){var a=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var l=a.length,r=new Array(l);r[0]=f;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[m]="string"==typeof e?e:i,r[1]=o;for(var d=2;d{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>p,frontMatter:()=>l,metadata:()=>o,toc:()=>d});var n=a(7462),i=(a(7294),a(3905));const l={title:"Metadata"},r=void 0,o={unversionedId:"file/metadata",id:"file/metadata",title:"Metadata",description:"This chapter describes how file metadata is handled by this library.",source:"@site/docs/file/05-metadata.md",sourceDirName:"file",slug:"/file/metadata",permalink:"/file/metadata",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/05-metadata.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{title:"Metadata"},sidebar:"docs",previous:{title:"Adapters",permalink:"/file/adapters"},next:{title:"Derivation",permalink:"/file/derivation"}},s={},d=[{value:"Primary Metadata",id:"primary-metadata",level:2},{value:"Metadata Objects",id:"metadata-objects",level:2},{value:"Getting Metadata Objects",id:"getting-metadata-objects",level:2},{value:"Setting Metadata",id:"setting-metadata",level:2},{value:"Low-Level Metadata Handling",id:"low-level-metadata-handling",level:2}],c={toc:d},m="wrapper";function p(e){let{components:t,...a}=e;return(0,i.kt)(m,(0,n.Z)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"This chapter describes how file metadata is handled by this library."),(0,i.kt)("h2",{id:"primary-metadata"},"Primary Metadata"),(0,i.kt)("p",null,"Firstly, ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," has several methods that returns or sets what can be\nconsidered metadata of the file:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// Returns the file's name\n$name = (string) $file->getName();\n\n// Returns the file's MIME type\n$mime = (string) $file->getType();\n\n// Returns the file's size in bytes\n$size = $file->getSize();\n\n// Returns the file's last modified time\n$lastModified = $file->getLastModified();\n")),(0,i.kt)("h2",{id:"metadata-objects"},"Metadata Objects"),(0,i.kt)("p",null,"A ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," can also have several metadata objects associated with it. A\nmetadata object is an object that represents a specific type of metadata of the\nfile."),(0,i.kt)("p",null,"These are the metadata objects that are currently implemented:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"RawMetadataInterface"),": Represents the raw metadata object. It is a simple\nkey-value object. The value can be a string, integer, boolean or null."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"FileMetadataInterface"),": Represents the metadata that every file has: name,\ntype, size and last modified time."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"HttpMetadataInterface"),": Represents metadata used in HTTP responses. It is\nused when streaming the file to the client over HTTP."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"ImageMetadataInterface"),": Contains metadata specific to images, including\nimage dimension and orientation.")),(0,i.kt)("h2",{id:"getting-metadata-objects"},"Getting Metadata Objects"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," has a ",(0,i.kt)("inlineCode",{parentName:"p"},"get()")," method that returns an associated object of\nthe file. The caller can use this method to get a specific metadata object of a\nfile."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// Returns the FileMetadataInterface object\n$fileMetadata = $file->get(FileMetadataInterface::class);\n\n// Returns the ImageMetadataInterface object\n$imageMetadata = $file->get(ImageMetadataInterface::class);\n\n// Getting image related metadata\n$width = $file->get(ImageMetadataInterface::class)->getWidth();\n$height = $file->get(ImageMetadataInterface::class)->getHeight();\n")),(0,i.kt)("p",null,"You can also use string aliases instead of FQCNs. This is useful when specifying\nFQCNs is inconvenient, like in Twig templates:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-twig"},'\n')),(0,i.kt)("h2",{id:"setting-metadata"},"Setting Metadata"),(0,i.kt)("p",null,"Metadata objects can provide methods that can be used to set the metadata value.\nFor example, the ",(0,i.kt)("inlineCode",{parentName:"p"},"FileMetadataInterface")," has ",(0,i.kt)("inlineCode",{parentName:"p"},"setName()")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"setType()"),", the\n",(0,i.kt)("inlineCode",{parentName:"p"},"HttpMetadataInterface")," has ",(0,i.kt)("inlineCode",{parentName:"p"},"setDisposition()"),", etc. After setting the metadata\nusing these methods, the caller must call ",(0,i.kt)("inlineCode",{parentName:"p"},"flush()")," to persist the changes."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\Contracts\\File\\Metadata\\HttpMetadataInterface;\n\n/** @var FileInterface $file */\n\n// Each of the following will be flush automatically individually, and will\n// require two roundtrips to the storage backend\n$file->setType('image/jpeg');\n$file->setName('foo.jpg');\n\n// The following needs an explicit flush(). It will only require one roundtrip\n// to the storage backend.\n$file->get(FileMetadataInterface::class)?->setType('image/jpeg'); \n$file->get(FileMetadataInterface::class)?->setName('foo.jpg'); \n$file->flush();\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Local files don't persist metadata. Changes in the metadata are only valid for\nthe duration of the request. However, if the file is copied or moved to a\nnon-local filesystem, the metadata will be copied and persisted by the\ndestination file.")),(0,i.kt)("h2",{id:"low-level-metadata-handling"},"Low-Level Metadata Handling"),(0,i.kt)("p",null,"In a non local filesystem, the library stores a file's metadata in a ",(0,i.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Sidecar_file"},"sidecar\nfile")," in the JSON format. If the\nfile key is ",(0,i.kt)("inlineCode",{parentName:"p"},"foo/bar.txt"),", the metadata file key will be\n",(0,i.kt)("inlineCode",{parentName:"p"},"foo/bar.txt.metadata"),"."),(0,i.kt)("p",null,"Rationale:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Supports all filesystem."),(0,i.kt)("li",{parentName:"ul"},"Uniform way of handling metadata with all filesystem."),(0,i.kt)("li",{parentName:"ul"},"Simpler administration. i.e. when copying between different filesystems."),(0,i.kt)("li",{parentName:"ul"},"Implements coarse-grained ",(0,i.kt)("a",{parentName:"li",href:"https://martinfowler.com/eaaCatalog/remoteFacade.html"},"remote fa\xe7ade pattern")," to improve performance with remote filesystems.")),(0,i.kt)("p",null,"With the local filesystem, the library provides the same interface as above, but\ndoes not save the metadata to a sidecar file. Instead, the metadata is\ndetermined from the file and stored in an in-memory cache. Any changes to the\nmetadata are not persisted and only valid in the current request, but will be\nconsidered if the caller copies or moves the file to a non-local filesystem."),(0,i.kt)("p",null,"The caller is expected to treat files in the local filesystem as transient\nobjects, and expected to copy or move the files to a non-local filesystem if\nthey wish to store the file."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9e75f0a3.e4463abd.js b/assets/js/9e75f0a3.e4463abd.js new file mode 100644 index 00000000..05c6d83a --- /dev/null +++ b/assets/js/9e75f0a3.e4463abd.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[748],{3905:(e,t,a)=>{a.d(t,{Zo:()=>c,kt:()=>h});var n=a(7294);function i(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function l(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function r(e){for(var t=1;t=0||(i[a]=e[a]);return i}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var s=n.createContext({}),d=function(e){var t=n.useContext(s),a=t;return e&&(a="function"==typeof e?e(t):r(r({},t),e)),a},c=function(e){var t=d(e.components);return n.createElement(s.Provider,{value:t},e.children)},m="mdxType",p={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},f=n.forwardRef((function(e,t){var a=e.components,i=e.mdxType,l=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),m=d(a),f=i,h=m["".concat(s,".").concat(f)]||m[f]||p[f]||l;return a?n.createElement(h,r(r({ref:t},c),{},{components:a})):n.createElement(h,r({ref:t},c))}));function h(e,t){var a=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var l=a.length,r=new Array(l);r[0]=f;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[m]="string"==typeof e?e:i,r[1]=o;for(var d=2;d{a.r(t),a.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>p,frontMatter:()=>l,metadata:()=>o,toc:()=>d});var n=a(7462),i=(a(7294),a(3905));const l={title:"Metadata"},r=void 0,o={unversionedId:"file/metadata",id:"file/metadata",title:"Metadata",description:"This chapter describes how file metadata is handled by this library.",source:"@site/docs/file/05-metadata.md",sourceDirName:"file",slug:"/file/metadata",permalink:"/file/metadata",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file/05-metadata.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{title:"Metadata"},sidebar:"docs",previous:{title:"Adapters",permalink:"/file/adapters"},next:{title:"Derivation",permalink:"/file/derivation"}},s={},d=[{value:"Primary Metadata",id:"primary-metadata",level:2},{value:"Metadata Objects",id:"metadata-objects",level:2},{value:"Getting Metadata Objects",id:"getting-metadata-objects",level:2},{value:"Setting Metadata",id:"setting-metadata",level:2},{value:"Low-Level Metadata Handling",id:"low-level-metadata-handling",level:2}],c={toc:d},m="wrapper";function p(e){let{components:t,...a}=e;return(0,i.kt)(m,(0,n.Z)({},c,a,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"This chapter describes how file metadata is handled by this library."),(0,i.kt)("h2",{id:"primary-metadata"},"Primary Metadata"),(0,i.kt)("p",null,"Firstly, ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," has several methods that return and set what can be\nconsidered metadata of the file:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// Returns the file's name\n$name = (string) $file->getName();\n\n// Returns the file's MIME type\n$mime = (string) $file->getType();\n\n// Returns the file's size in bytes\n$size = $file->getSize();\n\n// Returns the file's last modified time\n$lastModified = $file->getLastModified();\n")),(0,i.kt)("h2",{id:"metadata-objects"},"Metadata Objects"),(0,i.kt)("p",null,"A ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," can also have several metadata objects associated with it. A\nmetadata object is an object that represents a specific type of metadata of the\nfile."),(0,i.kt)("p",null,"These are the metadata objects that are currently implemented:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"RawMetadataInterface"),": Represents the raw metadata object. It is a simple\nkey-value object. The value can be a string, integer, boolean, or null."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"FileMetadataInterface"),": Represents the metadata that every file has: name,\ntype, size, and last modified time."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"HttpMetadataInterface"),": Represents metadata used in HTTP responses. It is\nused when streaming the file to the client over HTTP."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"ImageMetadataInterface"),": Contains metadata specific to images, including\nimage dimension and orientation.")),(0,i.kt)("h2",{id:"getting-metadata-objects"},"Getting Metadata Objects"),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"FileInterface")," has a ",(0,i.kt)("inlineCode",{parentName:"p"},"get()")," method that returns an associated object of\nthe file. The caller can use this method to get a specific metadata object of a\nfile."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var FileInterface $file */\n\n// Returns the FileMetadataInterface object\n$fileMetadata = $file->get(FileMetadataInterface::class);\n\n// Returns the ImageMetadataInterface object\n$imageMetadata = $file->get(ImageMetadataInterface::class);\n\n// Getting image related metadata\n$width = $file->get(ImageMetadataInterface::class)->getWidth();\n$height = $file->get(ImageMetadataInterface::class)->getHeight();\n")),(0,i.kt)("p",null,"You can also use string aliases instead of FQCNs. This is useful when specifying\nFQCNs is inconvenient, like in Twig templates:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-twig"},'\n')),(0,i.kt)("h2",{id:"setting-metadata"},"Setting Metadata"),(0,i.kt)("p",null,"Metadata objects can provide methods that can be used to set the metadata value.\nFor example, the ",(0,i.kt)("inlineCode",{parentName:"p"},"FileMetadataInterface")," has ",(0,i.kt)("inlineCode",{parentName:"p"},"setName()")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"setType()"),", the\n",(0,i.kt)("inlineCode",{parentName:"p"},"HttpMetadataInterface")," has ",(0,i.kt)("inlineCode",{parentName:"p"},"setDisposition()"),", etc. After setting the metadata\nusing these methods, the caller must call ",(0,i.kt)("inlineCode",{parentName:"p"},"flush()")," to persist the changes."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\Contracts\\File\\Metadata\\HttpMetadataInterface;\n\n/** @var FileInterface $file */\n\n// Each of the following will be flush automatically individually, and will\n// require two roundtrips to the storage backend\n$file->setType('image/jpeg');\n$file->setName('foo.jpg');\n\n// The following needs an explicit flush(). It will only require one roundtrip\n// to the storage backend.\n$file->get(FileMetadataInterface::class)?->setType('image/jpeg'); \n$file->get(FileMetadataInterface::class)?->setName('foo.jpg'); \n$file->flush();\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},"Local files don't persist metadata. Changes in the metadata are only valid for\nthe duration of the request. However, if the file is copied or moved to a\nnon-local filesystem, the metadata will be copied and persisted by the\ndestination file.")),(0,i.kt)("h2",{id:"low-level-metadata-handling"},"Low-Level Metadata Handling"),(0,i.kt)("p",null,"In a non-local filesystem, the library stores a file's metadata in a ",(0,i.kt)("a",{parentName:"p",href:"https://en.wikipedia.org/wiki/Sidecar_file"},"sidecar\nfile")," in the JSON format. If the\nfile key is ",(0,i.kt)("inlineCode",{parentName:"p"},"foo/bar.txt"),", the metadata file key will be\n",(0,i.kt)("inlineCode",{parentName:"p"},"foo/bar.txt.metadata"),"."),(0,i.kt)("p",null,"Rationale:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Supports all filesystems."),(0,i.kt)("li",{parentName:"ul"},"Uniform way of handling metadata with all filesystems."),(0,i.kt)("li",{parentName:"ul"},"Simpler administration. i.e. when copying between different filesystems."),(0,i.kt)("li",{parentName:"ul"},"Implements coarse-grained ",(0,i.kt)("a",{parentName:"li",href:"https://martinfowler.com/eaaCatalog/remoteFacade.html"},"remote fa\xe7ade pattern")," to improve performance with remote filesystems.")),(0,i.kt)("p",null,"With the local filesystem, the library provides the same interface as above but\ndoes not save the metadata to a sidecar file. Instead, the metadata is\ndetermined from the file and stored in an in-memory cache. Any changes to the\nmetadata are not persisted and are only valid in the current request but will be\nconsidered if the caller copies or moves the file to a non-local filesystem."),(0,i.kt)("p",null,"The caller is expected to treat files in the local filesystem as transient\nobjects and expected to copy or move the files to a non-local filesystem if they\nwish to store the file."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a1516275.7e3129e1.js b/assets/js/a1516275.a28c79aa.js similarity index 76% rename from assets/js/a1516275.7e3129e1.js rename to assets/js/a1516275.a28c79aa.js index 2fe0e923..b7fc7dd2 100644 --- a/assets/js/a1516275.7e3129e1.js +++ b/assets/js/a1516275.a28c79aa.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[178],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>f});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),u=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=u(e.components);return r.createElement(s.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,l=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),p=u(n),m=a,f=p["".concat(s,".").concat(m)]||p[m]||d[m]||l;return n?r.createElement(f,o(o({ref:t},c),{},{components:n})):r.createElement(f,o({ref:t},c))}));function f(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var l=n.length,o=new Array(l);o[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[p]="string"==typeof e?e:a,o[1]=i;for(var u=2;u{n.d(t,{Z:()=>o});var r=n(7294),a=n(6010);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.Z)(l.tabItem,o),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>w});var r=n(7462),a=n(7294),l=n(6010),o=n(2466),i=n(6550),s=n(1980),u=n(7392),c=n(12);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:r,default:a}}=e;return{value:t,label:n,attributes:r,default:a}}))}function d(e){const{values:t,children:n}=e;return(0,a.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function m(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function f(e){let{queryString:t=!1,groupId:n}=e;const r=(0,i.k6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(l),(0,a.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(r.location.search);t.set(l,e),r.replace({...r.location,search:t.toString()})}),[l,r])]}function b(e){const{defaultValue:t,queryString:n=!1,groupId:r}=e,l=d(e),[o,i]=(0,a.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const r=n.find((e=>e.default))??n[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:t,tabValues:l}))),[s,u]=f({queryString:n,groupId:r}),[p,b]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[r,l]=(0,c.Nk)(n);return[r,(0,a.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:r}),h=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,a.useLayoutEffect)((()=>{h&&i(h)}),[h]);return{selectedValue:o,selectValue:(0,a.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),b(e)}),[u,b,l]),tabValues:l}}var h=n(2389);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.o5)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),r=u[n].value;r!==i&&(p(t),s(r))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return a.createElement("li",(0,r.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},o,{className:(0,l.Z)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:r}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==r}))))}function k(e){const t=b(e);return a.createElement("div",{className:(0,l.Z)("tabs-container",y.tabList)},a.createElement(g,(0,r.Z)({},e,t)),a.createElement(v,(0,r.Z)({},e,t)))}function w(e){const t=(0,h.Z)();return a.createElement(k,(0,r.Z)({key:String(t)},e))}},2574:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>f,frontMatter:()=>i,metadata:()=>u,toc:()=>p});var r=n(7462),a=(n(7294),n(3905)),l=n(4866),o=n(5162);const i={title:"rekalogika/psr-16-simple-cache-bundle"},s=void 0,u={unversionedId:"psr-16-simple-cache-bundle/index",id:"psr-16-simple-cache-bundle/index",title:"rekalogika/psr-16-simple-cache-bundle",description:"Enables PSR-16 Simple Cache services in Symfony projects. These were previously",source:"@site/docs/psr-16-simple-cache-bundle/index.md",sourceDirName:"psr-16-simple-cache-bundle",slug:"/psr-16-simple-cache-bundle/",permalink:"/psr-16-simple-cache-bundle/",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/psr-16-simple-cache-bundle/index.md",tags:[],version:"current",frontMatter:{title:"rekalogika/psr-16-simple-cache-bundle"},sidebar:"docs",previous:{title:"Creating an Object ID Resolver",permalink:"/file-bundle/object-id-resolver"},next:{title:"rekalogika/reconstitutor",permalink:"/reconstitutor/"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2},{value:"Rationale",id:"rationale",level:2},{value:"Credits",id:"credits",level:2}],d={toc:p},m="wrapper";function f(e){let{components:t,...n}=e;return(0,a.kt)(m,(0,r.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Enables PSR-16 Simple Cache services in Symfony projects. These were previously\nenabled in the older Symfony version but were removed in 4.3."),(0,a.kt)("h2",{id:"installation"},"Installation"),(0,a.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,a.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,a.kt)(l.Z,{mdxType:"Tabs"},(0,a.kt)(o.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Open a command console, enter your project directory and execute:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/psr-16-simple-cache-bundle\n"))),(0,a.kt)(o.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Step 1: Download the Bundle"),(0,a.kt)("p",null,"Open a command console, enter your project directory and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/psr-16-simple-cache-bundle\n")),(0,a.kt)("p",null,"Step 2: Enable the Bundle"),(0,a.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,a.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\Psr16SimpleCacheBundle\\RekalogikaPsr16SimpleCacheBundle::class => ['all' => true],\n];\n")))),(0,a.kt)("h2",{id:"usage"},"Usage"),(0,a.kt)("p",null,"Caller can simply wire in ",(0,a.kt)("inlineCode",{parentName:"p"},"Psr\\SimpleCache\\CacheInterface"),". The service uses\nthe same underlying pool used by Symfony's ",(0,a.kt)("inlineCode",{parentName:"p"},"CacheInterface"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Psr\\SimpleCache\\CacheInterface;\n\nclass SomeService\n{\n public function __construct(private CacheInterface $cache)\n {\n }\n\n public function doSomething()\n {\n $this->cache->set('foo', 'bar');\n }\n}\n")),(0,a.kt)("h2",{id:"rationale"},"Rationale"),(0,a.kt)("p",null,"We are using PSR-16 mostly as an expiring key-value storage. While PSR-6 and\nSymfony's CacheInterface are more powerful and easier to use for caching things,\nwe don't feel their interfaces are suitable for a key-value storage."),(0,a.kt)("h2",{id:"credits"},"Credits"),(0,a.kt)("p",null,"This package is just a service definition. The actual implementation is done by\nthe Symfony project; they just don't make the service available by default."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://symfony.com/doc/current/components/cache/psr6_psr16_adapters.html"},"Adapters For Interoperability between PSR-6 and PSR-16 Cache")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/symfony/symfony/issues/28918#issuecomment-433489302"},"Service definition by Tobion"))))}f.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[178],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>f});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function l(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),u=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=u(e.components);return r.createElement(s.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,l=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),p=u(n),m=a,f=p["".concat(s,".").concat(m)]||p[m]||d[m]||l;return n?r.createElement(f,o(o({ref:t},c),{},{components:n})):r.createElement(f,o({ref:t},c))}));function f(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var l=n.length,o=new Array(l);o[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[p]="string"==typeof e?e:a,o[1]=i;for(var u=2;u{n.d(t,{Z:()=>o});var r=n(7294),a=n(6010);const l={tabItem:"tabItem_Ymn6"};function o(e){let{children:t,hidden:n,className:o}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.Z)(l.tabItem,o),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>w});var r=n(7462),a=n(7294),l=n(6010),o=n(2466),i=n(6550),s=n(1980),u=n(7392),c=n(12);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:r,default:a}}=e;return{value:t,label:n,attributes:r,default:a}}))}function d(e){const{values:t,children:n}=e;return(0,a.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function m(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function f(e){let{queryString:t=!1,groupId:n}=e;const r=(0,i.k6)(),l=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(l),(0,a.useCallback)((e=>{if(!l)return;const t=new URLSearchParams(r.location.search);t.set(l,e),r.replace({...r.location,search:t.toString()})}),[l,r])]}function b(e){const{defaultValue:t,queryString:n=!1,groupId:r}=e,l=d(e),[o,i]=(0,a.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const r=n.find((e=>e.default))??n[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:t,tabValues:l}))),[s,u]=f({queryString:n,groupId:r}),[p,b]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[r,l]=(0,c.Nk)(n);return[r,(0,a.useCallback)((e=>{n&&l.set(e)}),[n,l])]}({groupId:r}),h=(()=>{const e=s??p;return m({value:e,tabValues:l})?e:null})();(0,a.useLayoutEffect)((()=>{h&&i(h)}),[h]);return{selectedValue:o,selectValue:(0,a.useCallback)((e=>{if(!m({value:e,tabValues:l}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),b(e)}),[u,b,l]),tabValues:l}}var h=n(2389);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,o.o5)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),r=u[n].value;r!==i&&(p(t),s(r))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,l.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:o}=e;return a.createElement("li",(0,r.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},o,{className:(0,l.Z)("tabs__item",y.tabItem,o?.className,{"tabs__item--active":i===t})}),n??t)})))}function v(e){let{lazy:t,children:n,selectedValue:r}=e;const l=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=l.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},l.map(((e,t)=>(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==r}))))}function k(e){const t=b(e);return a.createElement("div",{className:(0,l.Z)("tabs-container",y.tabList)},a.createElement(g,(0,r.Z)({},e,t)),a.createElement(v,(0,r.Z)({},e,t)))}function w(e){const t=(0,h.Z)();return a.createElement(k,(0,r.Z)({key:String(t)},e))}},2574:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>f,frontMatter:()=>i,metadata:()=>u,toc:()=>p});var r=n(7462),a=(n(7294),n(3905)),l=n(4866),o=n(5162);const i={title:"rekalogika/psr-16-simple-cache-bundle"},s=void 0,u={unversionedId:"psr-16-simple-cache-bundle/index",id:"psr-16-simple-cache-bundle/index",title:"rekalogika/psr-16-simple-cache-bundle",description:"Enables PSR-16 Simple Cache services in Symfony projects. These were previously",source:"@site/docs/psr-16-simple-cache-bundle/index.md",sourceDirName:"psr-16-simple-cache-bundle",slug:"/psr-16-simple-cache-bundle/",permalink:"/psr-16-simple-cache-bundle/",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/psr-16-simple-cache-bundle/index.md",tags:[],version:"current",frontMatter:{title:"rekalogika/psr-16-simple-cache-bundle"},sidebar:"docs",previous:{title:"Creating an Object ID Resolver",permalink:"/file-bundle/object-id-resolver"},next:{title:"rekalogika/reconstitutor",permalink:"/reconstitutor/"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2},{value:"Rationale",id:"rationale",level:2},{value:"Credits",id:"credits",level:2}],d={toc:p},m="wrapper";function f(e){let{components:t,...n}=e;return(0,a.kt)(m,(0,r.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Enables PSR-16 Simple Cache services in Symfony projects. These were previously\nenabled in the older Symfony version but were removed in 4.3."),(0,a.kt)("h2",{id:"installation"},"Installation"),(0,a.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,a.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,a.kt)(l.Z,{mdxType:"Tabs"},(0,a.kt)(o.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Open a command console, enter your project directory, and execute:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/psr-16-simple-cache-bundle\n"))),(0,a.kt)(o.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Step 1: Download the Bundle"),(0,a.kt)("p",null,"Open a command console, enter your project directory, and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/psr-16-simple-cache-bundle\n")),(0,a.kt)("p",null,"Step 2: Enable the Bundle"),(0,a.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,a.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\Psr16SimpleCacheBundle\\RekalogikaPsr16SimpleCacheBundle::class => ['all' => true],\n];\n")))),(0,a.kt)("h2",{id:"usage"},"Usage"),(0,a.kt)("p",null,"Callers can simply wire in ",(0,a.kt)("inlineCode",{parentName:"p"},"Psr\\SimpleCache\\CacheInterface"),". The service uses\nthe same underlying pool used by Symfony's ",(0,a.kt)("inlineCode",{parentName:"p"},"CacheInterface"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Psr\\SimpleCache\\CacheInterface;\n\nclass SomeService\n{\n public function __construct(private CacheInterface $cache)\n {\n }\n\n public function doSomething()\n {\n $this->cache->set('foo', 'bar');\n }\n}\n")),(0,a.kt)("h2",{id:"rationale"},"Rationale"),(0,a.kt)("p",null,"We are using PSR-16 mostly as an expiring key-value storage. While PSR-6 and\nSymfony's CacheInterface are more powerful and easier to use for caching things,\nwe don't feel their interfaces are suitable for key-value storage."),(0,a.kt)("h2",{id:"credits"},"Credits"),(0,a.kt)("p",null,"This package is just a service definition. The actual implementation is done by\nthe Symfony project; they just don't make the service available by default."),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://symfony.com/doc/current/components/cache/psr6_psr16_adapters.html"},"Adapters For Interoperability between PSR-6 and PSR-16 Cache")),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://github.com/symfony/symfony/issues/28918#issuecomment-433489302"},"Service definition by Tobion"))))}f.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/bef88291.aa0492ad.js b/assets/js/bef88291.c0487475.js similarity index 87% rename from assets/js/bef88291.aa0492ad.js rename to assets/js/bef88291.c0487475.js index ab659a9e..3e879b9b 100644 --- a/assets/js/bef88291.aa0492ad.js +++ b/assets/js/bef88291.c0487475.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[522],{3905:(e,n,t)=>{t.d(n,{Zo:()=>m,kt:()=>v});var i=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);n&&(i=i.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,i)}return t}function r(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var l=i.createContext({}),c=function(e){var n=i.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},m=function(e){var n=c(e.components);return i.createElement(l.Provider,{value:n},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var n=e.children;return i.createElement(i.Fragment,{},n)}},p=i.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,m=s(e,["components","mdxType","originalType","parentName"]),u=c(t),p=a,v=u["".concat(l,".").concat(p)]||u[p]||d[p]||o;return t?i.createElement(v,r(r({ref:n},m),{},{components:t})):i.createElement(v,r({ref:n},m))}));function v(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,r=new Array(o);r[0]=p;var s={};for(var l in n)hasOwnProperty.call(n,l)&&(s[l]=n[l]);s.originalType=e,s[u]="string"==typeof e?e:a,r[1]=s;for(var c=2;c{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var i=t(7462),a=(t(7294),t(3905));const o={title:"Basic Usage"},r=void 0,s={unversionedId:"domain-event/basic-usage",id:"domain-event/basic-usage",title:"Basic Usage",description:"Creating Domain Events",source:"@site/docs/domain-event/01-basic-usage.md",sourceDirName:"domain-event",slug:"/domain-event/basic-usage",permalink:"/domain-event/basic-usage",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/domain-event/01-basic-usage.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{title:"Basic Usage"},sidebar:"docs",previous:{title:"Introduction & Installation",permalink:"/domain-event/intro"},next:{title:"Manual Control",permalink:"/domain-event/manual-control"}},l={},c=[{value:"Creating Domain Events",id:"creating-domain-events",level:2},{value:"Recording Events",id:"recording-events",level:2},{value:"Listening to Events",id:"listening-to-events",level:2},{value:"Equatable Domain Events",id:"equatable-domain-events",level:2}],m={toc:c},u="wrapper";function d(e){let{components:n,...t}=e;return(0,a.kt)(u,(0,i.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h2",{id:"creating-domain-events"},"Creating Domain Events"),(0,a.kt)("p",null,"Domain events are plain old PHP objects that you create to represent a specific\nevent happening in your domain."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"// our event superclass for the Post object\n\nabstract class AbstractPostEvent\n{\n public function __construct(private string $id)\n {\n }\n\n public function getId(): string\n {\n return $this->id;\n }\n}\n\n// our concrete events\n\nfinal class PostCreated extends AbstractPostEvent\n{\n}\n\nfinal class PostChanged extends AbstractPostEvent\n{\n}\n\nfinal class PostRemoved extends AbstractPostEvent\n{\n}\n")),(0,a.kt)("h2",{id:"recording-events"},"Recording Events"),(0,a.kt)("p",null,"Your emitters (entities) must implement ",(0,a.kt)("inlineCode",{parentName:"p"},"DomainEventEmitterInterface"),".\nThere is a ",(0,a.kt)("inlineCode",{parentName:"p"},"DomainEventEmitterTrait")," to help you with that. To record events,\nyou can use the method ",(0,a.kt)("inlineCode",{parentName:"p"},"recordEvents()")," defined in the trait."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collection;\nuse Rekalogika\\Contracts\\DomainEvent\\DomainEventEmitterInterface;\nuse Rekalogika\\Contracts\\DomainEvent\\DomainEventEmitterTrait;\nuse Symfony\\Component\\Uid\\UuidV7;\n\nclass Post implements DomainEventEmitterInterface\n{\n use DomainEventEmitterTrait;\n\n private string $id;\n private string $title;\n /** @var Collection */\n private Collection $comments;\n\n public function __construct(string $title)\n {\n $this->id = new UuidV7();\n $this->title = $title;\n $this->comments = new ArrayCollection();\n\n // highlight-next-line\n $this->recordEvent(new PostCreated($this->id));\n }\n\n // __remove() is our special method that gets triggered when the entity is\n // going to be removed from the persistence layer\n public function __remove()\n {\n // highlight-next-line\n $this->recordEvent(new PostRemoved($this->id));\n }\n\n public function setTitle(string $title): void\n {\n $this->title = $title;\n\n // highlight-next-line\n $this->recordEvent(new PostChanged($this->id));\n }\n}\n")),(0,a.kt)("h2",{id:"listening-to-events"},"Listening to Events"),(0,a.kt)("p",null,"To listen to the events, you can use the usual Symfony way of listening to\nevents. The framework will collect events from persisted entities, and dispatch\nthem at the end of the ",(0,a.kt)("inlineCode",{parentName:"p"},"flush()"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener;\n\n#[AsEventListener]\nclass PostEventListener\n{\n // this method will be invoked after a new Post is persist()-ed & flush()-ed\n public function __invoke(PostCreated $event) {\n $postId = $event->getId();\n // ...\n }\n}\n")),(0,a.kt)("p",null,"Alternatively, you can use different attributes to choose a different\ndispatching strategy."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\DomainEvent\\Attribute\\AsImmediateDomainEventListener;\nuse Rekalogika\\Contracts\\DomainEvent\\Attribute\\AsPostFlushDomainEventListener;\nuse Rekalogika\\Contracts\\DomainEvent\\Attribute\\AsPreFlushDomainEventListener;\n\nclass PostEventListener\n{\n #[AsImmediateDomainEventListener]\n public function immediate(PostCreated $event) {\n // this will run immediately after the entity records the event\n }\n\n #[AsPreFlushDomainEventListener]\n public function preFlush(PostCreated $event) {\n // this will run when you flush() the new post. before the actual\n // flush()\n }\n\n #[AsPostFlushDomainEventListener]\n public function postFlush(PostCreated $event) {\n // this will run when you flush() the new post. after the actual\n // flush()\n }\n}\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("ul",{parentName:"admonition"},(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AsEventListener")," and ",(0,a.kt)("inlineCode",{parentName:"li"},"AsPostFlushDomainEventListener")," currently have\nidentical behavior, but they utilize different event dispatchers. We plan to\nhave a different event dispatcher behavior with\n",(0,a.kt)("inlineCode",{parentName:"li"},"AsPostFlushDomainEventListener")," while keeping ",(0,a.kt)("inlineCode",{parentName:"li"},"AsEventListener")," standard."),(0,a.kt)("li",{parentName:"ul"},"Doing a ",(0,a.kt)("inlineCode",{parentName:"li"},"flush()")," inside a pre-flush listener is not allowed and will result\nin a ",(0,a.kt)("inlineCode",{parentName:"li"},"FlushNotAllowedException"),"."))),(0,a.kt)("h2",{id:"equatable-domain-events"},"Equatable Domain Events"),(0,a.kt)("p",null,"A domain event can optionally implement ",(0,a.kt)("inlineCode",{parentName:"p"},"EquatableDomainEventInterface")," which\nrequires the method ",(0,a.kt)("inlineCode",{parentName:"p"},"getSignature()"),". Two objects with the same signature will\nbe considered identical by ",(0,a.kt)("inlineCode",{parentName:"p"},"DomainEventManager")," and won't be dispatched twice."),(0,a.kt)("p",null,"This is useful if your entity is working with a million of related objects. By\nimplementing ",(0,a.kt)("inlineCode",{parentName:"p"},"EquatableDomainEventInterface"),", you can have your ",(0,a.kt)("inlineCode",{parentName:"p"},"ObjectChanged"),"\nevent dispatched only once, and occupies only a single spot in the memory,\ninstead of a million times."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\DomainEvent\\EquatableDomainEventInterface;\n\nclass PostCommentAdded implements EquatableDomainEventInterface\n{\n public function __construct(private string $postId)\n {\n }\n\n public function getSignature(): string\n {\n return sha1(serialize($this));\n }\n}\n\nuse Rekalogika\\Contracts\\DomainEvent\\DomainEventEmitterInterface;\nuse Rekalogika\\Contracts\\DomainEvent\\DomainEventEmitterTrait;\n\nclass Post implements DomainEventEmitterInterface\n{\n use DomainEventEmitterTrait;\n\n // ...\n\n public function addComment(string $comment): Comment\n {\n // ...\n\n // the PostCommentAdded event will only get dispatched once despite of\n // addComment being called multiple times.\n $this->recordEvent(new PostCommentAdded($this->id));\n }\n}\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"Equatable domain events only applies to pre-flush and post-flush events.\nImmediate domain events are dispatched immediately, and there is no chance for\nthe equatable check to take place.")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[522],{3905:(e,n,t)=>{t.d(n,{Zo:()=>m,kt:()=>v});var i=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);n&&(i=i.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,i)}return t}function r(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var l=i.createContext({}),c=function(e){var n=i.useContext(l),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},m=function(e){var n=c(e.components);return i.createElement(l.Provider,{value:n},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var n=e.children;return i.createElement(i.Fragment,{},n)}},p=i.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,m=s(e,["components","mdxType","originalType","parentName"]),u=c(t),p=a,v=u["".concat(l,".").concat(p)]||u[p]||d[p]||o;return t?i.createElement(v,r(r({ref:n},m),{},{components:t})):i.createElement(v,r({ref:n},m))}));function v(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,r=new Array(o);r[0]=p;var s={};for(var l in n)hasOwnProperty.call(n,l)&&(s[l]=n[l]);s.originalType=e,s[u]="string"==typeof e?e:a,r[1]=s;for(var c=2;c{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var i=t(7462),a=(t(7294),t(3905));const o={title:"Basic Usage"},r=void 0,s={unversionedId:"domain-event/basic-usage",id:"domain-event/basic-usage",title:"Basic Usage",description:"Creating Domain Events",source:"@site/docs/domain-event/01-basic-usage.md",sourceDirName:"domain-event",slug:"/domain-event/basic-usage",permalink:"/domain-event/basic-usage",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/domain-event/01-basic-usage.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{title:"Basic Usage"},sidebar:"docs",previous:{title:"Introduction & Installation",permalink:"/domain-event/intro"},next:{title:"Manual Control",permalink:"/domain-event/manual-control"}},l={},c=[{value:"Creating Domain Events",id:"creating-domain-events",level:2},{value:"Recording Events",id:"recording-events",level:2},{value:"Listening to Events",id:"listening-to-events",level:2},{value:"Equatable Domain Events",id:"equatable-domain-events",level:2}],m={toc:c},u="wrapper";function d(e){let{components:n,...t}=e;return(0,a.kt)(u,(0,i.Z)({},m,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h2",{id:"creating-domain-events"},"Creating Domain Events"),(0,a.kt)("p",null,"Domain events are plain old PHP objects that you create to represent a specific\nevent happening in your domain."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"// our event superclass for the Post object\n\nabstract class AbstractPostEvent\n{\n public function __construct(private string $id)\n {\n }\n\n public function getId(): string\n {\n return $this->id;\n }\n}\n\n// our concrete events\n\nfinal class PostCreated extends AbstractPostEvent\n{\n}\n\nfinal class PostChanged extends AbstractPostEvent\n{\n}\n\nfinal class PostRemoved extends AbstractPostEvent\n{\n}\n")),(0,a.kt)("h2",{id:"recording-events"},"Recording Events"),(0,a.kt)("p",null,"Your emitters (entities) must implement ",(0,a.kt)("inlineCode",{parentName:"p"},"DomainEventEmitterInterface"),".\nThere is a ",(0,a.kt)("inlineCode",{parentName:"p"},"DomainEventEmitterTrait")," to help you with that. To record events,\nyou can use the method ",(0,a.kt)("inlineCode",{parentName:"p"},"recordEvents()")," defined in the trait."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collection;\nuse Rekalogika\\Contracts\\DomainEvent\\DomainEventEmitterInterface;\nuse Rekalogika\\Contracts\\DomainEvent\\DomainEventEmitterTrait;\nuse Symfony\\Component\\Uid\\UuidV7;\n\nclass Post implements DomainEventEmitterInterface\n{\n use DomainEventEmitterTrait;\n\n private string $id;\n private string $title;\n /** @var Collection */\n private Collection $comments;\n\n public function __construct(string $title)\n {\n $this->id = new UuidV7();\n $this->title = $title;\n $this->comments = new ArrayCollection();\n\n // highlight-next-line\n $this->recordEvent(new PostCreated($this->id));\n }\n\n // __remove() is our special method that gets triggered when the entity is\n // going to be removed from the persistence layer\n public function __remove()\n {\n // highlight-next-line\n $this->recordEvent(new PostRemoved($this->id));\n }\n\n public function setTitle(string $title): void\n {\n $this->title = $title;\n\n // highlight-next-line\n $this->recordEvent(new PostChanged($this->id));\n }\n}\n")),(0,a.kt)("h2",{id:"listening-to-events"},"Listening to Events"),(0,a.kt)("p",null,"To listen to the events, you can use the usual Symfony way of listening to\nevents. The framework will collect events from persisted entities, and dispatch\nthem at the end of the ",(0,a.kt)("inlineCode",{parentName:"p"},"flush()"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\EventDispatcher\\Attribute\\AsEventListener;\n\n#[AsEventListener]\nclass PostEventListener\n{\n // this method will be invoked after a new Post is persist()-ed & flush()-ed\n public function __invoke(PostCreated $event) {\n $postId = $event->getId();\n // ...\n }\n}\n")),(0,a.kt)("p",null,"Alternatively, you can use different attributes to choose a different\ndispatching strategy."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\DomainEvent\\Attribute\\AsImmediateDomainEventListener;\nuse Rekalogika\\Contracts\\DomainEvent\\Attribute\\AsPostFlushDomainEventListener;\nuse Rekalogika\\Contracts\\DomainEvent\\Attribute\\AsPreFlushDomainEventListener;\n\nclass PostEventListener\n{\n #[AsImmediateDomainEventListener]\n public function immediate(PostCreated $event) {\n // this will run immediately after the entity records the event\n }\n\n #[AsPreFlushDomainEventListener]\n public function preFlush(PostCreated $event) {\n // this will run when you flush() the new post. before the actual\n // flush()\n }\n\n #[AsPostFlushDomainEventListener]\n public function postFlush(PostCreated $event) {\n // this will run when you flush() the new post. after the actual\n // flush()\n }\n}\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("ul",{parentName:"admonition"},(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"AsEventListener")," and ",(0,a.kt)("inlineCode",{parentName:"li"},"AsPostFlushDomainEventListener")," currently have\nidentical behavior, but they utilize different event dispatchers. We plan to\nhave a different event dispatcher behavior with\n",(0,a.kt)("inlineCode",{parentName:"li"},"AsPostFlushDomainEventListener")," while keeping ",(0,a.kt)("inlineCode",{parentName:"li"},"AsEventListener")," standard."),(0,a.kt)("li",{parentName:"ul"},"Doing a ",(0,a.kt)("inlineCode",{parentName:"li"},"flush()")," inside a pre-flush listener is not allowed and will result\nin a ",(0,a.kt)("inlineCode",{parentName:"li"},"FlushNotAllowedException"),"."))),(0,a.kt)("h2",{id:"equatable-domain-events"},"Equatable Domain Events"),(0,a.kt)("p",null,"A domain event can optionally implement ",(0,a.kt)("inlineCode",{parentName:"p"},"EquatableDomainEventInterface")," which\nrequires the method ",(0,a.kt)("inlineCode",{parentName:"p"},"getSignature()"),". Two objects with the same signature will\nbe considered identical by ",(0,a.kt)("inlineCode",{parentName:"p"},"DomainEventManager")," and won't be dispatched twice."),(0,a.kt)("p",null,"This is useful if your entity is working with a million of related objects. By\nimplementing ",(0,a.kt)("inlineCode",{parentName:"p"},"EquatableDomainEventInterface"),", you can have your ",(0,a.kt)("inlineCode",{parentName:"p"},"ObjectChanged"),"\nevent dispatched only once and occupy only a single spot in the memory,\ninstead of a million times."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\DomainEvent\\EquatableDomainEventInterface;\n\nclass PostCommentAdded implements EquatableDomainEventInterface\n{\n public function __construct(private string $postId)\n {\n }\n\n public function getSignature(): string\n {\n return sha1(serialize($this));\n }\n}\n\nuse Rekalogika\\Contracts\\DomainEvent\\DomainEventEmitterInterface;\nuse Rekalogika\\Contracts\\DomainEvent\\DomainEventEmitterTrait;\n\nclass Post implements DomainEventEmitterInterface\n{\n use DomainEventEmitterTrait;\n\n // ...\n\n public function addComment(string $comment): Comment\n {\n // ...\n\n // the PostCommentAdded event will only get dispatched once despite of\n // addComment being called multiple times.\n $this->recordEvent(new PostCommentAdded($this->id));\n }\n}\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"Equatable domain events only apply to pre-flush and post-flush events. Immediate\ndomain events are dispatched immediately, and there is no chance for the\nequatable check to take place.")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c2040271.d257e7b5.js b/assets/js/c2040271.c77f1c1b.js similarity index 94% rename from assets/js/c2040271.d257e7b5.js rename to assets/js/c2040271.c77f1c1b.js index 8d90f5f0..0523fe12 100644 --- a/assets/js/c2040271.d257e7b5.js +++ b/assets/js/c2040271.c77f1c1b.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[645],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>m});var r=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function a(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=r.createContext({}),u=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):a(a({},t),e)),n},c=function(e){var t=u(e.components);return r.createElement(s.Provider,{value:t},e.children)},p="mdxType",f={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},d=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,o=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),p=u(n),d=i,m=p["".concat(s,".").concat(d)]||p[d]||f[d]||o;return n?r.createElement(m,a(a({ref:t},c),{},{components:n})):r.createElement(m,a({ref:t},c))}));function m(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var o=n.length,a=new Array(o);a[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[p]="string"==typeof e?e:i,a[1]=l;for(var u=2;u{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>a,default:()=>f,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var r=n(7462),i=(n(7294),n(3905));const o={title:"Introduction"},a=void 0,l={unversionedId:"file-bundle/intro",id:"file-bundle/intro",title:"Introduction",description:"Symfony bundle to easily integrate the rekalogika/file framework and related",source:"@site/docs/file-bundle/01-intro.md",sourceDirName:"file-bundle",slug:"/file-bundle/intro",permalink:"/file-bundle/intro",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/01-intro.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{title:"Introduction"},sidebar:"docs",previous:{title:"rekalogika/file-bundle",permalink:"/file-bundle/"},next:{title:"Installation & Configuration",permalink:"/file-bundle/installation"}},s={},u=[{value:"Entity Association Features",id:"entity-association-features",level:3},{value:"License",id:"license",level:2},{value:"Contributing",id:"contributing",level:2}],c={toc:u},p="wrapper";function f(e){let{components:t,...n}=e;return(0,i.kt)(p,(0,r.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Symfony bundle to easily integrate the ",(0,i.kt)("inlineCode",{parentName:"p"},"rekalogika/file")," framework and related\npackages within a Symfony application."),(0,i.kt)("h3",{id:"entity-association-features"},"Entity Association Features"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Requires only a single property in the entity for each associated file."),(0,i.kt)("li",{parentName:"ul"},"File properties are file properties. It is not necessary to store any of the\nfile's properties in the entity associated with the file."),(0,i.kt)("li",{parentName:"ul"},"DX improvement, less micro-management of entity-file relations."),(0,i.kt)("li",{parentName:"ul"},"Reads and writes directly into the file properties, even if private. You are\nfree to have business logic in the getters and setters."),(0,i.kt)("li",{parentName:"ul"},"Doesn't require you to update another property of the entity (",(0,i.kt)("inlineCode",{parentName:"li"},"lastUpdated"),")\njust to make sure the correct Doctrine events will be fired.")),(0,i.kt)("h2",{id:"license"},"License"),(0,i.kt)("p",null,"MIT"),(0,i.kt)("h2",{id:"contributing"},"Contributing"),(0,i.kt)("p",null,"This framework consists of multiple repositories splitted from a monorepo. Be\nsure to submit issues and pull request to the\n",(0,i.kt)("a",{parentName:"p",href:"https://github.com/rekalogika/file-src"},"rekalogika/file-src")," monorepo."))}f.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[645],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>m});var r=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function a(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=r.createContext({}),u=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):a(a({},t),e)),n},c=function(e){var t=u(e.components);return r.createElement(s.Provider,{value:t},e.children)},p="mdxType",f={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},d=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,o=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),p=u(n),d=i,m=p["".concat(s,".").concat(d)]||p[d]||f[d]||o;return n?r.createElement(m,a(a({ref:t},c),{},{components:n})):r.createElement(m,a({ref:t},c))}));function m(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var o=n.length,a=new Array(o);a[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[p]="string"==typeof e?e:i,a[1]=l;for(var u=2;u{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>a,default:()=>f,frontMatter:()=>o,metadata:()=>l,toc:()=>u});var r=n(7462),i=(n(7294),n(3905));const o={title:"Introduction"},a=void 0,l={unversionedId:"file-bundle/intro",id:"file-bundle/intro",title:"Introduction",description:"Symfony bundle to easily integrate the rekalogika/file framework and related",source:"@site/docs/file-bundle/01-intro.md",sourceDirName:"file-bundle",slug:"/file-bundle/intro",permalink:"/file-bundle/intro",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/01-intro.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{title:"Introduction"},sidebar:"docs",previous:{title:"rekalogika/file-bundle",permalink:"/file-bundle/"},next:{title:"Installation & Configuration",permalink:"/file-bundle/installation"}},s={},u=[{value:"Entity Association Features",id:"entity-association-features",level:3},{value:"License",id:"license",level:2},{value:"Contributing",id:"contributing",level:2}],c={toc:u},p="wrapper";function f(e){let{components:t,...n}=e;return(0,i.kt)(p,(0,r.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Symfony bundle to easily integrate the ",(0,i.kt)("inlineCode",{parentName:"p"},"rekalogika/file")," framework and related\npackages within a Symfony application."),(0,i.kt)("h3",{id:"entity-association-features"},"Entity Association Features"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Requires only a single property in the entity for each associated file."),(0,i.kt)("li",{parentName:"ul"},"File properties are file properties. It is not necessary to store any of the\nfile's properties in the entity associated with the file."),(0,i.kt)("li",{parentName:"ul"},"DX improvement, less micro-management of entity-file relations."),(0,i.kt)("li",{parentName:"ul"},"Reads and writes directly into the file properties, even if private. You are\nfree to have business logic in the getters and setters."),(0,i.kt)("li",{parentName:"ul"},"Doesn't require you to update another property of the entity (",(0,i.kt)("inlineCode",{parentName:"li"},"lastUpdated"),")\njust to make sure the correct Doctrine events will be fired.")),(0,i.kt)("h2",{id:"license"},"License"),(0,i.kt)("p",null,"MIT"),(0,i.kt)("h2",{id:"contributing"},"Contributing"),(0,i.kt)("p",null,"This framework consists of multiple repositories split from a monorepo. Be\nsure to submit issues and pull requests to the\n",(0,i.kt)("a",{parentName:"p",href:"https://github.com/rekalogika/file-src"},"rekalogika/file-src")," monorepo."))}f.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c80f6930.ee4e4728.js b/assets/js/c80f6930.ee4e4728.js new file mode 100644 index 00000000..612585c8 --- /dev/null +++ b/assets/js/c80f6930.ee4e4728.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[686],{3905:(e,t,n)=>{n.d(t,{Zo:()=>m,kt:()=>d});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var p=r.createContext({}),s=function(e){var t=r.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},m=function(e){var t=s(e.components);return r.createElement(p.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,p=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),u=s(n),f=a,d=u["".concat(p,".").concat(f)]||u[f]||c[f]||i;return n?r.createElement(d,o(o({ref:t},m),{},{components:n})):r.createElement(d,o({ref:t},m))}));function d(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,o=new Array(i);o[0]=f;var l={};for(var p in t)hasOwnProperty.call(t,p)&&(l[p]=t[p]);l.originalType=e,l[u]="string"==typeof e?e:a,o[1]=l;for(var s=2;s{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var r=n(7462),a=(n(7294),n(3905));const i={title:"Serving Files"},o=void 0,l={unversionedId:"file-bundle/serving-files",id:"file-bundle/serving-files",title:"Serving Files",description:"This chapter describes how to serve files to the client web browser.",source:"@site/docs/file-bundle/05-serving-files.md",sourceDirName:"file-bundle",slug:"/file-bundle/serving-files",permalink:"/file-bundle/serving-files",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/05-serving-files.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{title:"Serving Files"},sidebar:"docs",previous:{title:"Symfony Integration",permalink:"/file-bundle/symfony"},next:{title:"Filtering",permalink:"/file-bundle/filtering"}},p={},s=[{value:"Streaming Files in a Symfony Controller",id:"streaming-files-in-a-symfony-controller",level:2},{value:"Generate a Temporary URL to a File",id:"generate-a-temporary-url-to-a-file",level:2},{value:"PHP Usage",id:"php-usage",level:3},{value:"Twig Usage",id:"twig-usage",level:3},{value:"More Information",id:"more-information",level:3}],m={toc:s},u="wrapper";function c(e){let{components:t,...n}=e;return(0,a.kt)(u,(0,r.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"This chapter describes how to serve files to the client web browser."),(0,a.kt)("h2",{id:"streaming-files-in-a-symfony-controller"},"Streaming Files in a Symfony Controller"),(0,a.kt)("admonition",{title:"Preparation",type:"info"},(0,a.kt)("p",{parentName:"admonition"},"You need to install the package ",(0,a.kt)("inlineCode",{parentName:"p"},"rekalogika/file-symfony-bridge")," to use this\nfeature:"),(0,a.kt)("pre",{parentName:"admonition"},(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-symfony-bridge\n"))),(0,a.kt)("p",null,"To send a file to the web browser, you can use ",(0,a.kt)("inlineCode",{parentName:"p"},"FileResponse"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\HttpFoundation\\FileResponse;\nuse Rekalogika\\Contracts\\File\\FileInterface;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass SomeController\n{\n public function download(): Response\n {\n /** @var FileInterface $file */\n $file = ...;\n\n return new FileResponse($file);\n }\n}\n")),(0,a.kt)("h2",{id:"generate-a-temporary-url-to-a-file"},"Generate a Temporary URL to a File"),(0,a.kt)("p",null,"Rather than creating a controller action to serve a file for every possible\nsituation, it is more convenient to generate a temporary URL to a file."),(0,a.kt)("admonition",{title:"Preparation",type:"info"},(0,a.kt)("p",{parentName:"admonition"},"You need to install the package ",(0,a.kt)("inlineCode",{parentName:"p"},"rekalogika/file-server")," to use this feature:"),(0,a.kt)("pre",{parentName:"admonition"},(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-server\n")),(0,a.kt)("p",{parentName:"admonition"},"If you are not using Symfony Flex, read the documentation of\n",(0,a.kt)("a",{parentName:"p",href:"../file-bundle/installation"},(0,a.kt)("inlineCode",{parentName:"a"},"rekalogika/file-bundle"))," and ",(0,a.kt)("a",{parentName:"p",href:"../temporary-url-bundle"},(0,a.kt)("inlineCode",{parentName:"a"},"rekalogika/temporary-url-bundle"))," to\nlearn how to register the required bundles.")),(0,a.kt)("h3",{id:"php-usage"},"PHP Usage"),(0,a.kt)("p",null,"Wire in the ",(0,a.kt)("inlineCode",{parentName:"p"},"TemporaryUrlGeneratorInterface")," service, and use the\n",(0,a.kt)("inlineCode",{parentName:"p"},"generateUrl()")," method to generate a temporary URL to a file. It accepts either\na ",(0,a.kt)("inlineCode",{parentName:"p"},"FileInterface")," or a ",(0,a.kt)("inlineCode",{parentName:"p"},"FilePointerInterface"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\TemporaryUrl\\TemporaryUrlGeneratorInterface;\nuse Rekalogika\\File\\FileInterface;\nuse Rekalogika\\File\\FilePointerInterface;\n\n/** @var TemporaryUrlGeneratorInterface $temporaryUrlGenerator */\n/** @var FileInterface|FilePointerInterface $file */\n\n$url = $temporaryUrlGenerator->generateUrl($file);\n")),(0,a.kt)("h3",{id:"twig-usage"},"Twig Usage"),(0,a.kt)("p",null,"In Twig templates, you can use the ",(0,a.kt)("inlineCode",{parentName:"p"},"temporary_url")," filter to generate a\ntemporary URL to a file."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-twig"},'Click here to download\n')),(0,a.kt)("p",null,"With images, a convenient pattern is to chain the ",(0,a.kt)("inlineCode",{parentName:"p"},"temporary_url")," filter with\nthe ",(0,a.kt)("inlineCode",{parentName:"p"},"image_resize")," filter from the ",(0,a.kt)("inlineCode",{parentName:"p"},"rekalogika/file-image")," package."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-twig"},'\n')),(0,a.kt)("h3",{id:"more-information"},"More Information"),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"generateUrl()")," method and the ",(0,a.kt)("inlineCode",{parentName:"p"},"temporary_url")," Twig filter accept several\noptions. Read the documentation of ",(0,a.kt)("a",{parentName:"p",href:"../temporary-url-bundle"},(0,a.kt)("inlineCode",{parentName:"a"},"rekalogika/temporary-url-bundle"))," to\nlearn more."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c80f6930.eed6e7b5.js b/assets/js/c80f6930.eed6e7b5.js deleted file mode 100644 index 665e4384..00000000 --- a/assets/js/c80f6930.eed6e7b5.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[686],{3905:(e,t,n)=>{n.d(t,{Zo:()=>m,kt:()=>d});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var p=r.createContext({}),s=function(e){var t=r.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},m=function(e){var t=s(e.components);return r.createElement(p.Provider,{value:t},e.children)},u="mdxType",c={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,p=e.parentName,m=l(e,["components","mdxType","originalType","parentName"]),u=s(n),f=a,d=u["".concat(p,".").concat(f)]||u[f]||c[f]||i;return n?r.createElement(d,o(o({ref:t},m),{},{components:n})):r.createElement(d,o({ref:t},m))}));function d(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,o=new Array(i);o[0]=f;var l={};for(var p in t)hasOwnProperty.call(t,p)&&(l[p]=t[p]);l.originalType=e,l[u]="string"==typeof e?e:a,o[1]=l;for(var s=2;s{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>o,default:()=>c,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var r=n(7462),a=(n(7294),n(3905));const i={title:"Serving Files"},o=void 0,l={unversionedId:"file-bundle/serving-files",id:"file-bundle/serving-files",title:"Serving Files",description:"This chapter describes how to serve files to client web browser.",source:"@site/docs/file-bundle/05-serving-files.md",sourceDirName:"file-bundle",slug:"/file-bundle/serving-files",permalink:"/file-bundle/serving-files",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/05-serving-files.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{title:"Serving Files"},sidebar:"docs",previous:{title:"Symfony Integration",permalink:"/file-bundle/symfony"},next:{title:"Filtering",permalink:"/file-bundle/filtering"}},p={},s=[{value:"Streaming Files in a Symfony Controller",id:"streaming-files-in-a-symfony-controller",level:2},{value:"Generate a Temporary URL to a File",id:"generate-a-temporary-url-to-a-file",level:2},{value:"PHP Usage",id:"php-usage",level:3},{value:"Twig Usage",id:"twig-usage",level:3},{value:"More Information",id:"more-information",level:3}],m={toc:s},u="wrapper";function c(e){let{components:t,...n}=e;return(0,a.kt)(u,(0,r.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"This chapter describes how to serve files to client web browser."),(0,a.kt)("h2",{id:"streaming-files-in-a-symfony-controller"},"Streaming Files in a Symfony Controller"),(0,a.kt)("admonition",{title:"Preparation",type:"info"},(0,a.kt)("p",{parentName:"admonition"},"You need to install the package ",(0,a.kt)("inlineCode",{parentName:"p"},"rekalogika/file-symfony-bridge")," to use this\nfeature:"),(0,a.kt)("pre",{parentName:"admonition"},(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-symfony-bridge\n"))),(0,a.kt)("p",null,"To send a file to client web browser, you can use ",(0,a.kt)("inlineCode",{parentName:"p"},"FileResponse"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Bridge\\Symfony\\HttpFoundation\\FileResponse;\nuse Rekalogika\\Contracts\\File\\FileInterface;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass SomeController\n{\n public function download(): Response\n {\n /** @var FileInterface $file */\n $file = ...;\n\n return new FileResponse($file);\n }\n}\n")),(0,a.kt)("h2",{id:"generate-a-temporary-url-to-a-file"},"Generate a Temporary URL to a File"),(0,a.kt)("p",null,"Rather than creating a controller action to serve a file for every possible\nsituations, it is more convenient to generate a temporary URL to a file."),(0,a.kt)("admonition",{title:"Preparation",type:"info"},(0,a.kt)("p",{parentName:"admonition"},"You need to install the package ",(0,a.kt)("inlineCode",{parentName:"p"},"rekalogika/file-server")," to use this feature:"),(0,a.kt)("pre",{parentName:"admonition"},(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-server\n")),(0,a.kt)("p",{parentName:"admonition"},"If you are not using Symfony Flex, read the documentation of\n",(0,a.kt)("a",{parentName:"p",href:"../file-bundle/installation"},(0,a.kt)("inlineCode",{parentName:"a"},"rekalogika/file-bundle"))," and ",(0,a.kt)("a",{parentName:"p",href:"../temporary-url-bundle"},(0,a.kt)("inlineCode",{parentName:"a"},"rekalogika/temporary-url-bundle"))," to\nlearn how to register the required bundles.")),(0,a.kt)("h3",{id:"php-usage"},"PHP Usage"),(0,a.kt)("p",null,"Wire in the ",(0,a.kt)("inlineCode",{parentName:"p"},"TemporaryUrlGeneratorInterface")," service, and use the\n",(0,a.kt)("inlineCode",{parentName:"p"},"generateUrl()")," method to generate a temporary URL to a file. It accepts either\na ",(0,a.kt)("inlineCode",{parentName:"p"},"FileInterface")," or a ",(0,a.kt)("inlineCode",{parentName:"p"},"FilePointerInterface"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\TemporaryUrl\\TemporaryUrlGeneratorInterface;\nuse Rekalogika\\File\\FileInterface;\nuse Rekalogika\\File\\FilePointerInterface;\n\n/** @var TemporaryUrlGeneratorInterface $temporaryUrlGenerator */\n/** @var FileInterface|FilePointerInterface $file */\n\n$url = $temporaryUrlGenerator->generateUrl($file);\n")),(0,a.kt)("h3",{id:"twig-usage"},"Twig Usage"),(0,a.kt)("p",null,"In Twig templates, you can use the ",(0,a.kt)("inlineCode",{parentName:"p"},"temporary_url")," filter to generate a\ntemporary URL to a file."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-twig"},'Click here to download\n')),(0,a.kt)("p",null,"With images, a convenient pattern is to chain the ",(0,a.kt)("inlineCode",{parentName:"p"},"temporary_url")," filter with\nthe ",(0,a.kt)("inlineCode",{parentName:"p"},"image_resize")," filter from the ",(0,a.kt)("inlineCode",{parentName:"p"},"rekalogika/file-image")," package."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-twig"},'\n')),(0,a.kt)("h3",{id:"more-information"},"More Information"),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"generateUrl()")," method and the ",(0,a.kt)("inlineCode",{parentName:"p"},"temporary_url")," Twig filter accept several\noptions. Read the documentation of ",(0,a.kt)("a",{parentName:"p",href:"../temporary-url-bundle"},(0,a.kt)("inlineCode",{parentName:"a"},"rekalogika/temporary-url-bundle"))," to\nlearn more."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/d39191ae.9921b83f.js b/assets/js/d39191ae.8e461b5f.js similarity index 57% rename from assets/js/d39191ae.9921b83f.js rename to assets/js/d39191ae.8e461b5f.js index 72dfcdec..d6bbe4ec 100644 --- a/assets/js/d39191ae.9921b83f.js +++ b/assets/js/d39191ae.8e461b5f.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[669],{3905:(e,t,r)=>{r.d(t,{Zo:()=>c,kt:()=>f});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function l(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var s=n.createContext({}),u=function(e){var t=n.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):l(l({},t),e)),r},c=function(e){var t=u(e.components);return n.createElement(s.Provider,{value:t},e.children)},p="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),p=u(r),d=a,f=p["".concat(s,".").concat(d)]||p[d]||m[d]||o;return r?n.createElement(f,l(l({ref:t},c),{},{components:r})):n.createElement(f,l({ref:t},c))}));function f(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=r.length,l=new Array(o);l[0]=d;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[p]="string"==typeof e?e:a,l[1]=i;for(var u=2;u{r.d(t,{Z:()=>l});var n=r(7294),a=r(6010);const o={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:r,className:l}=e;return n.createElement("div",{role:"tabpanel",className:(0,a.Z)(o.tabItem,l),hidden:r},t)}},4866:(e,t,r)=>{r.d(t,{Z:()=>T});var n=r(7462),a=r(7294),o=r(6010),l=r(2466),i=r(6550),s=r(1980),u=r(7392),c=r(12);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:r,attributes:n,default:a}}=e;return{value:t,label:r,attributes:n,default:a}}))}function m(e){const{values:t,children:r}=e;return(0,a.useMemo)((()=>{const e=t??p(r);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,r])}function d(e){let{value:t,tabValues:r}=e;return r.some((e=>e.value===t))}function f(e){let{queryString:t=!1,groupId:r}=e;const n=(0,i.k6)(),o=function(e){let{queryString:t=!1,groupId:r}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return r??null}({queryString:t,groupId:r});return[(0,s._X)(o),(0,a.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function y(e){const{defaultValue:t,queryString:r=!1,groupId:n}=e,o=m(e),[l,i]=(0,a.useState)((()=>function(e){let{defaultValue:t,tabValues:r}=e;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:r}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${r.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=r.find((e=>e.default))??r[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,u]=f({queryString:r,groupId:n}),[p,y]=function(e){let{groupId:t}=e;const r=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Nk)(r);return[n,(0,a.useCallback)((e=>{r&&o.set(e)}),[r,o])]}({groupId:n}),b=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{b&&i(b)}),[b]);return{selectedValue:l,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var b=r(2389);const h={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:r,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.o5)(),m=e=>{const t=e.currentTarget,r=c.indexOf(t),n=u[r].value;n!==i&&(p(t),s(n))},d=e=>{let t=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const r=c.indexOf(e.currentTarget)+1;t=c[r]??c[0];break}case"ArrowLeft":{const r=c.indexOf(e.currentTarget)-1;t=c[r]??c[c.length-1];break}}t?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.Z)("tabs",{"tabs--block":r},t)},u.map((e=>{let{value:t,label:r,attributes:l}=e;return a.createElement("li",(0,n.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:m},l,{className:(0,o.Z)("tabs__item",h.tabItem,l?.className,{"tabs__item--active":i===t})}),r??t)})))}function k(e){let{lazy:t,children:r,selectedValue:n}=e;const o=(Array.isArray(r)?r:[r]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function v(e){const t=y(e);return a.createElement("div",{className:(0,o.Z)("tabs-container",h.tabList)},a.createElement(g,(0,n.Z)({},e,t)),a.createElement(k,(0,n.Z)({},e,t)))}function T(e){const t=(0,b.Z)();return a.createElement(v,(0,n.Z)({key:String(t)},e))}},6431:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>f,frontMatter:()=>i,metadata:()=>u,toc:()=>p});var n=r(7462),a=(r(7294),r(3905)),o=r(4866),l=r(5162);const i={title:"rekalogika/temporary-url-bundle"},s=void 0,u={unversionedId:"temporary-url-bundle/index",id:"temporary-url-bundle/index",title:"rekalogika/temporary-url-bundle",description:"Symfony bundle for creating temporary URLs to your resources. You provide the",source:"@site/docs/temporary-url-bundle/index.md",sourceDirName:"temporary-url-bundle",slug:"/temporary-url-bundle/",permalink:"/temporary-url-bundle/",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/temporary-url-bundle/index.md",tags:[],version:"current",frontMatter:{title:"rekalogika/temporary-url-bundle"},sidebar:"docs",previous:{title:"rekalogika/reconstitutor",permalink:"/reconstitutor/"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Creating a Resource Class",id:"creating-a-resource-class",level:2},{value:"Creating a Resource Server",id:"creating-a-resource-server",level:2},{value:"Generating a Temporary URL",id:"generating-a-temporary-url",level:2},{value:"In Twig Templates",id:"in-twig-templates",level:2},{value:"Dealing With Unserializable Resources",id:"dealing-with-unserializable-resources",level:2}],m={toc:p},d="wrapper";function f(e){let{components:t,...r}=e;return(0,a.kt)(d,(0,n.Z)({},m,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Symfony bundle for creating temporary URLs to your resources. You provide the\nresource in a plain PHP object, and a service to turn it into a HTTP response.\nThe framework handles the rest."),(0,a.kt)("h2",{id:"installation"},"Installation"),(0,a.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,a.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,a.kt)(o.Z,{mdxType:"Tabs"},(0,a.kt)(l.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Open a command console, enter your project directory and execute:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/temporary-url-bundle\n"))),(0,a.kt)(l.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Step 1: Download the Bundle"),(0,a.kt)("p",null,"Open a command console, enter your project directory and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/temporary-url-bundle\n")),(0,a.kt)("p",null,"Step 2: Enable the Bundle"),(0,a.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,a.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\TemporaryUrl\\RekalogikaTemporaryUrlBundle::class => ['all' => true],\n];\n")),(0,a.kt)("p",null,"Step 3: Configure the route"),(0,a.kt)("p",null,"Add the route in ",(0,a.kt)("inlineCode",{parentName:"p"},"config/routes/rekalogika_temporary_url.yaml"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/routes/rekalogika_temporary_url.yaml",title:"config/routes/rekalogika_temporary_url.yaml"},"rekalogika_temporary_url:\n resource: '@RekalogikaTemporaryUrlBundle/config/routes.xml'\n prefix: /_temporary\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"You may change the prefix if you like.")))),(0,a.kt)("h2",{id:"creating-a-resource-class"},"Creating a Resource Class"),(0,a.kt)("p",null,"Create a class that describes your resource. There is no particular requirement\nfor this class, except that it must be serializable."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"class MyData\n{\n public function __construct(private string $name)\n {\n }\n\n public function getName(): string\n {\n return $this->name;\n }\n}\n")),(0,a.kt)("admonition",{title:"Protip",type:"tip"},(0,a.kt)("p",{parentName:"admonition"},"You can reuse your existing event, message, DTO, value objects, or\nother similar classes for this purpose.")),(0,a.kt)("h2",{id:"creating-a-resource-server"},"Creating a Resource Server"),(0,a.kt)("p",null,"Then create a server class or method that transforms the resource into an HTTP\nresponse. Use the ",(0,a.kt)("inlineCode",{parentName:"p"},"AsTemporaryUrlServer")," attribute to mark the method as a\ntemporary URL server. If the attribute is attached to the class, then the method\nis assumed to be ",(0,a.kt)("inlineCode",{parentName:"p"},"__invoke()"),". The method must accept the resource as its first\nargument, and return a ",(0,a.kt)("inlineCode",{parentName:"p"},"Response")," object."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\TemporaryUrl\\Attribute\\AsTemporaryUrlServer;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass MyDataServer\n{\n #[AsTemporaryUrlServer]\n public function respond(MyData $data): Response\n {\n return new Response('My name is ' . $data->getName());\n }\n}\n")),(0,a.kt)("h2",{id:"generating-a-temporary-url"},"Generating a Temporary URL"),(0,a.kt)("p",null,"To generate a temporary URL, use the ",(0,a.kt)("inlineCode",{parentName:"p"},"TemporaryUrlGeneratorInterface")," service."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\TemporaryUrl\\TemporaryUrlGeneratorInterface;\n\n/** @var TemporaryUrlGeneratorInterface $temporaryUrlGenerator */\n\n$resource = new MyData('123');\n$url = $temporaryUrlGenerator->generateUrl($resource);\n")),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"TemporaryUrlGeneratorInterface::generateUrl()")," offers additional options:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"$ttl")," (",(0,a.kt)("inlineCode",{parentName:"li"},"int")," or ",(0,a.kt)("inlineCode",{parentName:"li"},"DateInterval"),"): The time-to-live of the URL. Defaults to 30\nminutes."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"$pinSession")," (",(0,a.kt)("inlineCode",{parentName:"li"},"bool"),"): Whether to pin the URL to the session. Pinned URLs can\nonly be accessed by the same user that generated them. Defaults to ",(0,a.kt)("inlineCode",{parentName:"li"},"false"),"."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"$referenceType")," (",(0,a.kt)("inlineCode",{parentName:"li"},"int"),"): The type of reference to be generated (one of the\n",(0,a.kt)("inlineCode",{parentName:"li"},"UrlGeneratorInterface::ABSOLUTE_*")," constants). Defaults to\n",(0,a.kt)("inlineCode",{parentName:"li"},"UrlGeneratorInterface::ABSOLUTE_PATH"),".")),(0,a.kt)("h2",{id:"in-twig-templates"},"In Twig Templates"),(0,a.kt)("p",null,"In a Twig template, you can use the filter ",(0,a.kt)("inlineCode",{parentName:"p"},"temporary_url")," to generate a\ntemporary URL."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-twig"},'{# my_data here is a resource object #}\nClick here to download my data\n')),(0,a.kt)("p",null,"The filter accepts the same options as the ",(0,a.kt)("inlineCode",{parentName:"p"},"generateUrl()")," method above."),(0,a.kt)("h2",{id:"dealing-with-unserializable-resources"},"Dealing With Unserializable Resources"),(0,a.kt)("p",null,"If your resource is not serializable, you can create a resource transformer\nmethod that converts your resource into a serializable object."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\TemporaryUrl\\Attribute\\AsTemporaryUrlResourceTransformer;\nuse Rekalogika\\TemporaryUrl\\Attribute\\AsTemporaryUrlServer;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass MyDataServer\n{\n /**\n * This method transforms the resource into a serializable object.\n */\n #[AsTemporaryUrlResourceTransformer]\n public function transform(MyUnserializableData $data): MySerializableData\n {\n return new MySerializableData($data);\n }\n\n ./**\n * This uses the transformed data and send it to the client.\n */\n #[AsTemporaryUrlServer]\n public function respond(MySerializableData $data): Response\n {\n return new Response('My name is ' . $data->getName());\n }\n}\n")))}f.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[669],{3905:(e,t,r)=>{r.d(t,{Zo:()=>c,kt:()=>f});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function l(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var s=n.createContext({}),u=function(e){var t=n.useContext(s),r=t;return e&&(r="function"==typeof e?e(t):l(l({},t),e)),r},c=function(e){var t=u(e.components);return n.createElement(s.Provider,{value:t},e.children)},p="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),p=u(r),d=a,f=p["".concat(s,".").concat(d)]||p[d]||m[d]||o;return r?n.createElement(f,l(l({ref:t},c),{},{components:r})):n.createElement(f,l({ref:t},c))}));function f(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=r.length,l=new Array(o);l[0]=d;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[p]="string"==typeof e?e:a,l[1]=i;for(var u=2;u{r.d(t,{Z:()=>l});var n=r(7294),a=r(6010);const o={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:r,className:l}=e;return n.createElement("div",{role:"tabpanel",className:(0,a.Z)(o.tabItem,l),hidden:r},t)}},4866:(e,t,r)=>{r.d(t,{Z:()=>T});var n=r(7462),a=r(7294),o=r(6010),l=r(2466),i=r(6550),s=r(1980),u=r(7392),c=r(12);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:r,attributes:n,default:a}}=e;return{value:t,label:r,attributes:n,default:a}}))}function m(e){const{values:t,children:r}=e;return(0,a.useMemo)((()=>{const e=t??p(r);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,r])}function d(e){let{value:t,tabValues:r}=e;return r.some((e=>e.value===t))}function f(e){let{queryString:t=!1,groupId:r}=e;const n=(0,i.k6)(),o=function(e){let{queryString:t=!1,groupId:r}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!r)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return r??null}({queryString:t,groupId:r});return[(0,s._X)(o),(0,a.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(n.location.search);t.set(o,e),n.replace({...n.location,search:t.toString()})}),[o,n])]}function y(e){const{defaultValue:t,queryString:r=!1,groupId:n}=e,o=m(e),[l,i]=(0,a.useState)((()=>function(e){let{defaultValue:t,tabValues:r}=e;if(0===r.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!d({value:t,tabValues:r}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${r.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const n=r.find((e=>e.default))??r[0];if(!n)throw new Error("Unexpected error: 0 tabValues");return n.value}({defaultValue:t,tabValues:o}))),[s,u]=f({queryString:r,groupId:n}),[p,y]=function(e){let{groupId:t}=e;const r=function(e){return e?`docusaurus.tab.${e}`:null}(t),[n,o]=(0,c.Nk)(r);return[n,(0,a.useCallback)((e=>{r&&o.set(e)}),[r,o])]}({groupId:n}),b=(()=>{const e=s??p;return d({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{b&&i(b)}),[b]);return{selectedValue:l,selectValue:(0,a.useCallback)((e=>{if(!d({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),y(e)}),[u,y,o]),tabValues:o}}var b=r(2389);const h={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:r,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.o5)(),m=e=>{const t=e.currentTarget,r=c.indexOf(t),n=u[r].value;n!==i&&(p(t),s(n))},d=e=>{let t=null;switch(e.key){case"Enter":m(e);break;case"ArrowRight":{const r=c.indexOf(e.currentTarget)+1;t=c[r]??c[0];break}case"ArrowLeft":{const r=c.indexOf(e.currentTarget)-1;t=c[r]??c[c.length-1];break}}t?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.Z)("tabs",{"tabs--block":r},t)},u.map((e=>{let{value:t,label:r,attributes:l}=e;return a.createElement("li",(0,n.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:d,onClick:m},l,{className:(0,o.Z)("tabs__item",h.tabItem,l?.className,{"tabs__item--active":i===t})}),r??t)})))}function k(e){let{lazy:t,children:r,selectedValue:n}=e;const o=(Array.isArray(r)?r:[r]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===n));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==n}))))}function v(e){const t=y(e);return a.createElement("div",{className:(0,o.Z)("tabs-container",h.tabList)},a.createElement(g,(0,n.Z)({},e,t)),a.createElement(k,(0,n.Z)({},e,t)))}function T(e){const t=(0,b.Z)();return a.createElement(v,(0,n.Z)({key:String(t)},e))}},6431:(e,t,r)=>{r.r(t),r.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>f,frontMatter:()=>i,metadata:()=>u,toc:()=>p});var n=r(7462),a=(r(7294),r(3905)),o=r(4866),l=r(5162);const i={title:"rekalogika/temporary-url-bundle"},s=void 0,u={unversionedId:"temporary-url-bundle/index",id:"temporary-url-bundle/index",title:"rekalogika/temporary-url-bundle",description:"Symfony bundle for creating temporary URLs to your resources. You provide the",source:"@site/docs/temporary-url-bundle/index.md",sourceDirName:"temporary-url-bundle",slug:"/temporary-url-bundle/",permalink:"/temporary-url-bundle/",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/temporary-url-bundle/index.md",tags:[],version:"current",frontMatter:{title:"rekalogika/temporary-url-bundle"},sidebar:"docs",previous:{title:"rekalogika/reconstitutor",permalink:"/reconstitutor/"}},c={},p=[{value:"Installation",id:"installation",level:2},{value:"Creating a Resource Class",id:"creating-a-resource-class",level:2},{value:"Creating a Resource Server",id:"creating-a-resource-server",level:2},{value:"Generating a Temporary URL",id:"generating-a-temporary-url",level:2},{value:"In Twig Templates",id:"in-twig-templates",level:2},{value:"Dealing With Unserializable Resources",id:"dealing-with-unserializable-resources",level:2}],m={toc:p},d="wrapper";function f(e){let{components:t,...r}=e;return(0,a.kt)(d,(0,n.Z)({},m,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"Symfony bundle for creating temporary URLs to your resources. You provide the\nresource in a plain PHP object, and a service to turn it into a HTTP response.\nThe framework handles the rest."),(0,a.kt)("h2",{id:"installation"},"Installation"),(0,a.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,a.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,a.kt)(o.Z,{mdxType:"Tabs"},(0,a.kt)(l.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Open a command console, enter your project directory, and execute:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/temporary-url-bundle\n"))),(0,a.kt)(l.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Step 1: Download the Bundle"),(0,a.kt)("p",null,"Open a command console, enter your project directory, and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/temporary-url-bundle\n")),(0,a.kt)("p",null,"Step 2: Enable the Bundle"),(0,a.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,a.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\TemporaryUrl\\RekalogikaTemporaryUrlBundle::class => ['all' => true],\n];\n")),(0,a.kt)("p",null,"Step 3: Configure the route"),(0,a.kt)("p",null,"Add the route in ",(0,a.kt)("inlineCode",{parentName:"p"},"config/routes/rekalogika_temporary_url.yaml"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/routes/rekalogika_temporary_url.yaml",title:"config/routes/rekalogika_temporary_url.yaml"},"rekalogika_temporary_url:\n resource: '@RekalogikaTemporaryUrlBundle/config/routes.xml'\n prefix: /_temporary\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"You may change the prefix if you like.")))),(0,a.kt)("h2",{id:"creating-a-resource-class"},"Creating a Resource Class"),(0,a.kt)("p",null,"Create a class that describes your resource. There is no particular requirement\nfor this class, except that it must be serializable."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"class MyData\n{\n public function __construct(private string $name)\n {\n }\n\n public function getName(): string\n {\n return $this->name;\n }\n}\n")),(0,a.kt)("admonition",{title:"Protip",type:"tip"},(0,a.kt)("p",{parentName:"admonition"},"You can reuse your existing event, message, DTO, value objects, or\nother similar classes for this purpose.")),(0,a.kt)("h2",{id:"creating-a-resource-server"},"Creating a Resource Server"),(0,a.kt)("p",null,"Then create a server class or method that transforms the resource into an HTTP\nresponse. Use the ",(0,a.kt)("inlineCode",{parentName:"p"},"AsTemporaryUrlServer")," attribute to mark the method as a\ntemporary URL server. If the attribute is attached to the class, then the method\nis assumed to be ",(0,a.kt)("inlineCode",{parentName:"p"},"__invoke()"),". The method must accept the resource as its first\nargument, and return a ",(0,a.kt)("inlineCode",{parentName:"p"},"Response")," object."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\TemporaryUrl\\Attribute\\AsTemporaryUrlServer;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass MyDataServer\n{\n #[AsTemporaryUrlServer]\n public function respond(MyData $data): Response\n {\n return new Response('My name is ' . $data->getName());\n }\n}\n")),(0,a.kt)("h2",{id:"generating-a-temporary-url"},"Generating a Temporary URL"),(0,a.kt)("p",null,"To generate a temporary URL, use the ",(0,a.kt)("inlineCode",{parentName:"p"},"TemporaryUrlGeneratorInterface")," service."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\TemporaryUrl\\TemporaryUrlGeneratorInterface;\n\n/** @var TemporaryUrlGeneratorInterface $temporaryUrlGenerator */\n\n$resource = new MyData('123');\n$url = $temporaryUrlGenerator->generateUrl($resource);\n")),(0,a.kt)("p",null,"The ",(0,a.kt)("inlineCode",{parentName:"p"},"TemporaryUrlGeneratorInterface::generateUrl()")," offers additional options:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"$ttl")," (",(0,a.kt)("inlineCode",{parentName:"li"},"int")," or ",(0,a.kt)("inlineCode",{parentName:"li"},"DateInterval"),"): The time-to-live of the URL. Defaults to 30\nminutes."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"$pinSession")," (",(0,a.kt)("inlineCode",{parentName:"li"},"bool"),"): Whether to pin the URL to the session. Pinned URLs can\nonly be accessed by the same user that generated them. Defaults to ",(0,a.kt)("inlineCode",{parentName:"li"},"false"),"."),(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("inlineCode",{parentName:"li"},"$referenceType")," (",(0,a.kt)("inlineCode",{parentName:"li"},"int"),"): The type of reference to be generated (one of the\n",(0,a.kt)("inlineCode",{parentName:"li"},"UrlGeneratorInterface::ABSOLUTE_*")," constants). Defaults to\n",(0,a.kt)("inlineCode",{parentName:"li"},"UrlGeneratorInterface::ABSOLUTE_PATH"),".")),(0,a.kt)("h2",{id:"in-twig-templates"},"In Twig Templates"),(0,a.kt)("p",null,"In a Twig template, you can use the filter ",(0,a.kt)("inlineCode",{parentName:"p"},"temporary_url")," to generate a\ntemporary URL."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-twig"},'{# my_data here is a resource object #}\nClick here to download my data\n')),(0,a.kt)("p",null,"The filter accepts the same options as the ",(0,a.kt)("inlineCode",{parentName:"p"},"generateUrl()")," method above."),(0,a.kt)("h2",{id:"dealing-with-unserializable-resources"},"Dealing With Unserializable Resources"),(0,a.kt)("p",null,"If your resource is not serializable, you can create a resource transformer\nmethod that converts your resource into a serializable object."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\TemporaryUrl\\Attribute\\AsTemporaryUrlResourceTransformer;\nuse Rekalogika\\TemporaryUrl\\Attribute\\AsTemporaryUrlServer;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nclass MyDataServer\n{\n /**\n * This method transforms the resource into a serializable object.\n */\n #[AsTemporaryUrlResourceTransformer]\n public function transform(MyUnserializableData $data): MySerializableData\n {\n return new MySerializableData($data);\n }\n\n ./**\n * This uses the transformed data and send it to the client.\n */\n #[AsTemporaryUrlServer]\n public function respond(MySerializableData $data): Response\n {\n return new Response('My name is ' . $data->getName());\n }\n}\n")))}f.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/e0be7bd3.1a316765.js b/assets/js/e0be7bd3.39c2c5d7.js similarity index 61% rename from assets/js/e0be7bd3.1a316765.js rename to assets/js/e0be7bd3.39c2c5d7.js index 52482323..1151c3c5 100644 --- a/assets/js/e0be7bd3.1a316765.js +++ b/assets/js/e0be7bd3.39c2c5d7.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[529],{3905:(e,t,i)=>{i.d(t,{Zo:()=>c,kt:()=>m});var r=i(7294);function n(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function a(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,r)}return i}function l(e){for(var t=1;t=0||(n[i]=e[i]);return n}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(n[i]=e[i])}return n}var s=r.createContext({}),p=function(e){var t=r.useContext(s),i=t;return e&&(i="function"==typeof e?e(t):l(l({},t),e)),i},c=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var i=e.components,n=e.mdxType,a=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),u=p(i),f=n,m=u["".concat(s,".").concat(f)]||u[f]||d[f]||a;return i?r.createElement(m,l(l({ref:t},c),{},{components:i})):r.createElement(m,l({ref:t},c))}));function m(e,t){var i=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var a=i.length,l=new Array(a);l[0]=f;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[u]="string"==typeof e?e:n,l[1]=o;for(var p=2;p{i.r(t),i.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>d,frontMatter:()=>a,metadata:()=>o,toc:()=>p});var r=i(7462),n=(i(7294),i(3905));const a={title:"Filtering"},l=void 0,o={unversionedId:"file-bundle/filtering",id:"file-bundle/filtering",title:"Filtering",description:"In this framework, 'filtering' is the opportunistic creation & caching of",source:"@site/docs/file-bundle/06-filtering.md",sourceDirName:"file-bundle",slug:"/file-bundle/filtering",permalink:"/file-bundle/filtering",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/06-filtering.md",tags:[],version:"current",sidebarPosition:6,frontMatter:{title:"Filtering"},sidebar:"docs",previous:{title:"Serving Files",permalink:"/file-bundle/serving-files"},next:{title:"File Association Internal Details",permalink:"/file-bundle/entity-association-internal"}},s={},p=[{value:"ImageResizer",id:"imageresizer",level:2},{value:"PHP Usage",id:"php-usage",level:3},{value:"Twig Usage",id:"twig-usage",level:3}],c={toc:p},u="wrapper";function d(e){let{components:t,...i}=e;return(0,n.kt)(u,(0,r.Z)({},c,i,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("p",null,"In this framework, 'filtering' is the opportunistic creation & caching of\nderived files from a source file. If a filter is applied to a ",(0,n.kt)("inlineCode",{parentName:"p"},"FileInterface"),",\none of these things happens:"),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},"If the derived file does not exist, process the source and create the\nderived file, then save it."),(0,n.kt)("li",{parentName:"ol"},"If the derived file exists and newer than the source file, return the\nalready made derived file."),(0,n.kt)("li",{parentName:"ol"},"If the derived file exists and older than the source file, then it is stale,\nthe filter will create a derivation out of the source file, then overwrite\nthe old derived file.")),(0,n.kt)("p",null,"Currently, there is only one filter available, ",(0,n.kt)("inlineCode",{parentName:"p"},"ImageResizer"),"."),(0,n.kt)("h2",{id:"imageresizer"},(0,n.kt)("inlineCode",{parentName:"h2"},"ImageResizer")),(0,n.kt)("admonition",{title:"Preparation",type:"note"},(0,n.kt)("p",{parentName:"admonition"},"You need to install the package ",(0,n.kt)("inlineCode",{parentName:"p"},"rekalogika/file-image")," to use this feature:"),(0,n.kt)("pre",{parentName:"admonition"},(0,n.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-image\n"))),(0,n.kt)("h3",{id:"php-usage"},"PHP Usage"),(0,n.kt)("p",null,"In PHP files, you need to inject the ",(0,n.kt)("inlineCode",{parentName:"p"},"ImageResizer")," class to your service\nor controller:"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Image\\ImageResizer;\nuse Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var ImageResizer $imageResizer */\n/** @var FileInterface $image */\n\n$resizedImage = $imageResizer\n ->take($image)\n ->resize(100, ImageResizer::ASPECRATIO_SQUARE)\n ->getResult();\n")),(0,n.kt)("p",null,"The first time it is called, the filter will create a 100-pixel-square-cropped\nimage from the original image. The second time it is called, the filter will\nreturn the already created derived image."),(0,n.kt)("p",null,"If the original image is updated, the filter will create a new derived image and\noverwrite the old one."),(0,n.kt)("h3",{id:"twig-usage"},"Twig Usage"),(0,n.kt)("p",null,"In Twig templates, you can use the ",(0,n.kt)("inlineCode",{parentName:"p"},"image_resize")," filter. For example:"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-twig"},"\n")),(0,n.kt)("p",null,"The example above will give us a temporary URL to a square-cropped image with a\nmaximum width or height of 100 pixels from the original image\n",(0,n.kt)("inlineCode",{parentName:"p"},"image_file"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[529],{3905:(e,t,i)=>{i.d(t,{Zo:()=>c,kt:()=>m});var r=i(7294);function n(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function a(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,r)}return i}function l(e){for(var t=1;t=0||(n[i]=e[i]);return n}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(n[i]=e[i])}return n}var s=r.createContext({}),p=function(e){var t=r.useContext(s),i=t;return e&&(i="function"==typeof e?e(t):l(l({},t),e)),i},c=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},f=r.forwardRef((function(e,t){var i=e.components,n=e.mdxType,a=e.originalType,s=e.parentName,c=o(e,["components","mdxType","originalType","parentName"]),u=p(i),f=n,m=u["".concat(s,".").concat(f)]||u[f]||d[f]||a;return i?r.createElement(m,l(l({ref:t},c),{},{components:i})):r.createElement(m,l({ref:t},c))}));function m(e,t){var i=arguments,n=t&&t.mdxType;if("string"==typeof e||n){var a=i.length,l=new Array(a);l[0]=f;var o={};for(var s in t)hasOwnProperty.call(t,s)&&(o[s]=t[s]);o.originalType=e,o[u]="string"==typeof e?e:n,l[1]=o;for(var p=2;p{i.r(t),i.d(t,{assets:()=>s,contentTitle:()=>l,default:()=>d,frontMatter:()=>a,metadata:()=>o,toc:()=>p});var r=i(7462),n=(i(7294),i(3905));const a={title:"Filtering"},l=void 0,o={unversionedId:"file-bundle/filtering",id:"file-bundle/filtering",title:"Filtering",description:"In this framework, 'filtering' is the opportunistic creation & caching of",source:"@site/docs/file-bundle/06-filtering.md",sourceDirName:"file-bundle",slug:"/file-bundle/filtering",permalink:"/file-bundle/filtering",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/06-filtering.md",tags:[],version:"current",sidebarPosition:6,frontMatter:{title:"Filtering"},sidebar:"docs",previous:{title:"Serving Files",permalink:"/file-bundle/serving-files"},next:{title:"File Association Internal Details",permalink:"/file-bundle/entity-association-internal"}},s={},p=[{value:"ImageResizer",id:"imageresizer",level:2},{value:"PHP Usage",id:"php-usage",level:3},{value:"Twig Usage",id:"twig-usage",level:3}],c={toc:p},u="wrapper";function d(e){let{components:t,...i}=e;return(0,n.kt)(u,(0,r.Z)({},c,i,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("p",null,"In this framework, 'filtering' is the opportunistic creation & caching of\nderived files from a source file. If a filter is applied to a ",(0,n.kt)("inlineCode",{parentName:"p"},"FileInterface"),",\none of these things happens:"),(0,n.kt)("ol",null,(0,n.kt)("li",{parentName:"ol"},"If the derived file does not exist, process the source and create the\nderived file, then save it."),(0,n.kt)("li",{parentName:"ol"},"If the derived file exists and newer than the source file, return the\nexisting derived file."),(0,n.kt)("li",{parentName:"ol"},"If the derived file exists and older than the source file, then it is stale,\nthe filter will create a derivation out of the source file, then overwrite\nthe old derived file.")),(0,n.kt)("p",null,"Currently, there is only one filter available, ",(0,n.kt)("inlineCode",{parentName:"p"},"ImageResizer"),"."),(0,n.kt)("h2",{id:"imageresizer"},(0,n.kt)("inlineCode",{parentName:"h2"},"ImageResizer")),(0,n.kt)("admonition",{title:"Preparation",type:"note"},(0,n.kt)("p",{parentName:"admonition"},"You need to install the package ",(0,n.kt)("inlineCode",{parentName:"p"},"rekalogika/file-image")," to use this feature:"),(0,n.kt)("pre",{parentName:"admonition"},(0,n.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-image\n"))),(0,n.kt)("h3",{id:"php-usage"},"PHP Usage"),(0,n.kt)("p",null,"In PHP files, you need to inject the ",(0,n.kt)("inlineCode",{parentName:"p"},"ImageResizer")," class to your service\nor controller:"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\File\\Image\\ImageResizer;\nuse Rekalogika\\Contracts\\File\\FileInterface;\n\n/** @var ImageResizer $imageResizer */\n/** @var FileInterface $image */\n\n$resizedImage = $imageResizer\n ->take($image)\n ->resize(100, ImageResizer::ASPECRATIO_SQUARE)\n ->getResult();\n")),(0,n.kt)("p",null,"The first time it is called, the filter will create a 100-pixel-square-cropped\nimage from the original image. The second time it is called, the filter will\nreturn the already created derived image."),(0,n.kt)("p",null,"If the original image is updated, the filter will create a new derived image and\noverwrite the old one."),(0,n.kt)("h3",{id:"twig-usage"},"Twig Usage"),(0,n.kt)("p",null,"In Twig templates, you can use the ",(0,n.kt)("inlineCode",{parentName:"p"},"image_resize")," filter. For example:"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-twig"},"\n")),(0,n.kt)("p",null,"The example above will give us a temporary URL to a square-cropped image with a\nmaximum width or height of 100 pixels from the original image\n",(0,n.kt)("inlineCode",{parentName:"p"},"image_file"),"."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/e6bf16cc.633d3b3b.js b/assets/js/e6bf16cc.27af3e37.js similarity index 68% rename from assets/js/e6bf16cc.633d3b3b.js rename to assets/js/e6bf16cc.27af3e37.js index 768e7474..399f83ec 100644 --- a/assets/js/e6bf16cc.633d3b3b.js +++ b/assets/js/e6bf16cc.27af3e37.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[862],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>h});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),p=u(n),m=r,h=p["".concat(s,".").concat(m)]||p[m]||d[m]||o;return n?a.createElement(h,i(i({ref:t},c),{},{components:n})):a.createElement(h,i({ref:t},c))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,i=new Array(o);i[0]=m;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[p]="string"==typeof e?e:r,i[1]=l;for(var u=2;u{n.d(t,{Z:()=>i});var a=n(7294),r=n(6010);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.Z)(o.tabItem,i),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>w});var a=n(7462),r=n(7294),o=n(6010),i=n(2466),l=n(6550),s=n(1980),u=n(7392),c=n(12);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function m(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.k6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=d(e),[i,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[s,u]=h({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,c.Nk)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),b=(()=>{const e=s??p;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{b&&l(b)}),[b]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),f(e)}),[u,f,o]),tabValues:o}}var b=n(2389);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.o5)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),s(a))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.Z)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},i,{className:(0,o.Z)("tabs__item",y.tabItem,i?.className,{"tabs__item--active":l===t})}),n??t)})))}function k(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function v(e){const t=f(e);return r.createElement("div",{className:(0,o.Z)("tabs-container",y.tabList)},r.createElement(g,(0,a.Z)({},e,t)),r.createElement(k,(0,a.Z)({},e,t)))}function w(e){const t=(0,b.Z)();return r.createElement(v,(0,a.Z)({key:String(t)},e))}},5966:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(7462),r=(n(7294),n(3905)),o=n(4866),i=n(5162);const l={title:"rekalogika/reconstitutor"},s=void 0,u={unversionedId:"reconstitutor/index",id:"reconstitutor/index",title:"rekalogika/reconstitutor",description:"This library provides a thin layer that sits above Doctrine events to help you",source:"@site/docs/reconstitutor/index.md",sourceDirName:"reconstitutor",slug:"/reconstitutor/",permalink:"/reconstitutor/",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/reconstitutor/index.md",tags:[],version:"current",frontMatter:{title:"rekalogika/reconstitutor"},sidebar:"docs",previous:{title:"rekalogika/psr-16-simple-cache-bundle",permalink:"/psr-16-simple-cache-bundle/"},next:{title:"rekalogika/temporary-url-bundle",permalink:"/temporary-url-bundle/"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2},{value:"Reconstitutor Abstract Class",id:"reconstitutor-abstract-class",level:2}],d={toc:p},m="wrapper";function h(e){let{components:t,...n}=e;return(0,r.kt)(m,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"This library provides a thin layer that sits above Doctrine events to help you\nreconstitute/hydrate your entities. It lets you augment Doctrine's hydration\nwith your logic in a concise and expressive class."),(0,r.kt)("p",null,"After Doctrine hydrates an object from the database, this framework gives you\nthe control to hydrate additional properties not handled by Doctrine, without\nhaving to deal with the peculiarities of Doctrine events and Unit of Work.\nSimilar things also happen when the object is persisted to the database, or\nremoved."),(0,r.kt)("p",null,"The most common case of this type of tasks is for handling file uploads, of\nwhich many specialized libraries have already been written. But plenty of other\ncases exist:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"A lazy-loading proxy that fetches the real resource using an API call."),(0,r.kt)("li",{parentName:"ul"},"Linking objects that are managed by different object managers, or non-Doctrine\nentities.")),(0,r.kt)("p",null,"These days we usually call the process ",(0,r.kt)("em",{parentName:"p"},"hydration"),". ",(0,r.kt)("em",{parentName:"p"},"Reconstitution")," is the term\nused by Eric Evans in the Blue Book: ",(0,r.kt)("em",{parentName:"p"},'"Domain-Driven Design: Tackling Complexity\nin the Heart of Software"'),"."),(0,r.kt)("h2",{id:"features"},"Features"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Simple declaration in a class. You can create a reconstitutor class to handle\nthe reconstitution of a specific entity class, entities that implement a\nspecific interface, entities in a class hierarchy, or those with a specific\nPHP attribute."),(0,r.kt)("li",{parentName:"ul"},"Our abstract classes provide ",(0,r.kt)("inlineCode",{parentName:"li"},"get()")," and ",(0,r.kt)("inlineCode",{parentName:"li"},"set()")," methods as a convenience.\nThey let you work with the properties directly, bypassing getters and setters.\nIt is the best practice in reconstitutions as it frees you to have business\nlogic in the getters and setters."),(0,r.kt)("li",{parentName:"ul"},"The ",(0,r.kt)("inlineCode",{parentName:"li"},"get()")," and ",(0,r.kt)("inlineCode",{parentName:"li"},"set()")," methods are forwarders to a custom implementation of\nSymfony's ",(0,r.kt)("inlineCode",{parentName:"li"},"PropertyAccessorInterface"),". Therefore, you can use the same\nexceptions defined in ",(0,r.kt)("inlineCode",{parentName:"li"},"PropertyAccessorInterface"),"."),(0,r.kt)("li",{parentName:"ul"},"It has what we think is the correct behavior. It asks your reconstitutor to\nsave only after Doctrine has successfully saved the object. It doesn't rely on\nDoctrine seeing the object being dirty before ",(0,r.kt)("inlineCode",{parentName:"li"},"flush()"),"-ing. i.e. your\nentities don't have to modify a Doctrine-managed property \u2014like\n",(0,r.kt)("inlineCode",{parentName:"li"},"$lastUpdated"),"\u2014 just to make sure the correct Doctrine event will be fired.")),(0,r.kt)("h2",{id:"installation"},"Installation"),(0,r.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,r.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,r.kt)(o.Z,{mdxType:"Tabs"},(0,r.kt)(i.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,r.kt)("p",null,"Open a command console, enter your project directory and execute:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/reconstitutor\n"))),(0,r.kt)(i.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,r.kt)("p",null,"Step 1: Download the Bundle"),(0,r.kt)("p",null,"Open a command console, enter your project directory and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/reconstitutor\n")),(0,r.kt)("p",null,"Step 2: Enable the Bundle"),(0,r.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,r.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\Reconstitutor\\RekalogikaReconstitutorBundle::class => ['all' => true],\n];\n")))),(0,r.kt)("h2",{id:"usage"},"Usage"),(0,r.kt)("p",null,"Because everyone knows about file uploads, we are going to use it as an\nexample, even if you probably won't use this framework as a means for handling\nfile uploads."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Speaking about file uploads, we also provide ",(0,r.kt)("a",{parentName:"p",href:"../file"},(0,r.kt)("inlineCode",{parentName:"a"},"rekalogika/file")),"\nframework that handles file uploads and much more. It also utilizes this library\nbehind the scenes.")),(0,r.kt)("p",null,"Suppose you have an ",(0,r.kt)("inlineCode",{parentName:"p"},"Order")," object that stores a payment receipt in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"paymentReceipt")," property:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\File\\File;\nuse Symfony\\Component\\Uid\\UuidV7;\n\nclass Order\n{\n private string $id;\n private ?File $paymentReceipt = null;\n\n public function __construct()\n {\n $this->id = new UuidV7;\n }\n\n public function getId(): string\n {\n return $this->id;\n }\n\n public function getPaymentReceipt(): ?File\n {\n return $this->paymentReceipt;\n }\n\n public function setPaymentReceipt(File $paymentReceipt): void\n {\n $this->paymentReceipt = $paymentReceipt;\n }\n}\n")),(0,r.kt)("p",null,"During the fetching of the object from the database, Doctrine will instantiate\nthe object and hydrate ",(0,r.kt)("inlineCode",{parentName:"p"},"$id")," and other properties that it manages. Afterwards,\nit will be our reconstitutor's turn to handle the ",(0,r.kt)("inlineCode",{parentName:"p"},"$paymentReceipt")," property.\nSimilar things also happen when the object is persisted to the database, or\nremoved."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Reconstitutor\\AbstractClassReconstitutor;\nuse Symfony\\Component\\HttpFoundation\\File\\File;\n\n/**\n * @extends AbstractClassReconstitutor\n */\nfinal class OrderReconstitutor extends AbstractClassReconstitutor\n{\n /**\n * The class that this reconstitutor manages. It can also be a super class\n * or an interface.\n */\n public static function getClass(): string\n {\n return Order::class;\n }\n\n /**\n * When the object is being saved, we check if the paymentReceipt has been\n * just uploaded. If it is, we save it to a file.\n */\n public function onSave(object $order): void\n {\n $path = sprintf('/tmp/payment_receipt/%s', $order->getId());\n\n $file = $this->get($order, 'paymentReceipt');\n\n if ($file instanceof UploadedFile) {\n file_put_contents($path, $file->getContent());\n $this->set($order, 'paymentReceipt', new File($path));\n }\n }\n\n /**\n * When the object is being loaded from the database, we check if the\n * supposed payment receipt is already saved. If it is, then we load the\n * file to the property.\n */\n public function onLoad(object $order): void\n {\n $path = sprintf('/tmp/payment_receipt/%s', $order->getId());\n\n if (file_exists($path)) {\n $file = new File($path);\n } else {\n $file = null;\n }\n\n $this->set($order, 'paymentReceipt', $file);\n }\n\n /**\n * If the order is being removed, we remove the associated payment receipt\n * here.\n */\n public function onRemove(object $order): void\n {\n $path = sprintf('/tmp/payment_receipt/%s', $order->getId());\n\n if (file_exists($path)) {\n unlink($path);\n }\n }\n}\n")),(0,r.kt)("h2",{id:"reconstitutor-abstract-class"},"Reconstitutor Abstract Class"),(0,r.kt)("p",null,"The example above uses ",(0,r.kt)("inlineCode",{parentName:"p"},"AbstractClassReconstitutor")," where our target object is\nmatched using the class provided by ",(0,r.kt)("inlineCode",{parentName:"p"},"getClass()"),". There is also\n",(0,r.kt)("inlineCode",{parentName:"p"},"AbstractAttributeReconstitutor")," that operates on objects that have a specific\nPHP attribute."))}h.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[862],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>h});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var s=a.createContext({}),u=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=u(e.components);return a.createElement(s.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},m=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),p=u(n),m=r,h=p["".concat(s,".").concat(m)]||p[m]||d[m]||o;return n?a.createElement(h,i(i({ref:t},c),{},{components:n})):a.createElement(h,i({ref:t},c))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,i=new Array(o);i[0]=m;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[p]="string"==typeof e?e:r,i[1]=l;for(var u=2;u{n.d(t,{Z:()=>i});var a=n(7294),r=n(6010);const o={tabItem:"tabItem_Ymn6"};function i(e){let{children:t,hidden:n,className:i}=e;return a.createElement("div",{role:"tabpanel",className:(0,r.Z)(o.tabItem,i),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>w});var a=n(7462),r=n(7294),o=n(6010),i=n(2466),l=n(6550),s=n(1980),u=n(7392),c=n(12);function p(e){return function(e){return r.Children.map(e,(e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:a,default:r}}=e;return{value:t,label:n,attributes:a,default:r}}))}function d(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function m(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function h(e){let{queryString:t=!1,groupId:n}=e;const a=(0,l.k6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(o),(0,r.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(a.location.search);t.set(o,e),a.replace({...a.location,search:t.toString()})}),[o,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,o=d(e),[i,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:t,tabValues:o}))),[s,u]=h({queryString:n,groupId:a}),[p,f]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,o]=(0,c.Nk)(n);return[a,(0,r.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:a}),b=(()=>{const e=s??p;return m({value:e,tabValues:o})?e:null})();(0,r.useLayoutEffect)((()=>{b&&l(b)}),[b]);return{selectedValue:i,selectValue:(0,r.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),f(e)}),[u,f,o]),tabValues:o}}var b=n(2389);const y={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function g(e){let{className:t,block:n,selectedValue:l,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,i.o5)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),a=u[n].value;a!==l&&(p(t),s(a))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return r.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:i}=e;return r.createElement("li",(0,a.Z)({role:"tab",tabIndex:l===t?0:-1,"aria-selected":l===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},i,{className:(0,o.Z)("tabs__item",y.tabItem,i?.className,{"tabs__item--active":l===t})}),n??t)})))}function k(e){let{lazy:t,children:n,selectedValue:a}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:"margin-top--md"}):null}return r.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a}))))}function v(e){const t=f(e);return r.createElement("div",{className:(0,o.Z)("tabs-container",y.tabList)},r.createElement(g,(0,a.Z)({},e,t)),r.createElement(k,(0,a.Z)({},e,t)))}function w(e){const t=(0,b.Z)();return r.createElement(v,(0,a.Z)({key:String(t)},e))}},5966:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>h,frontMatter:()=>l,metadata:()=>u,toc:()=>p});var a=n(7462),r=(n(7294),n(3905)),o=n(4866),i=n(5162);const l={title:"rekalogika/reconstitutor"},s=void 0,u={unversionedId:"reconstitutor/index",id:"reconstitutor/index",title:"rekalogika/reconstitutor",description:"This library provides a thin layer that sits above Doctrine events to help you",source:"@site/docs/reconstitutor/index.md",sourceDirName:"reconstitutor",slug:"/reconstitutor/",permalink:"/reconstitutor/",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/reconstitutor/index.md",tags:[],version:"current",frontMatter:{title:"rekalogika/reconstitutor"},sidebar:"docs",previous:{title:"rekalogika/psr-16-simple-cache-bundle",permalink:"/psr-16-simple-cache-bundle/"},next:{title:"rekalogika/temporary-url-bundle",permalink:"/temporary-url-bundle/"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Installation",id:"installation",level:2},{value:"Usage",id:"usage",level:2},{value:"Reconstitutor Abstract Class",id:"reconstitutor-abstract-class",level:2}],d={toc:p},m="wrapper";function h(e){let{components:t,...n}=e;return(0,r.kt)(m,(0,a.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"This library provides a thin layer that sits above Doctrine events to help you\nreconstitute/hydrate your entities. It lets you augment Doctrine's hydration\nwith your logic in a concise and expressive class."),(0,r.kt)("p",null,"After Doctrine hydrates an object from the database, this framework gives you\nthe control to hydrate additional properties not handled by Doctrine, without\nhaving to deal with the peculiarities of Doctrine events and Unit of Work.\nSimilar things also happen when the object is persisted to the database, or\nremoved."),(0,r.kt)("p",null,"The most common case of this type of task is for handling file uploads, of\nwhich many specialized libraries have already been written. But plenty of other\ncases exist:"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"A lazy-loading proxy that fetches the real resource using an API call."),(0,r.kt)("li",{parentName:"ul"},"Linking objects that are managed by different object managers, or non-Doctrine\nentities.")),(0,r.kt)("p",null,"These days we usually call the process ",(0,r.kt)("em",{parentName:"p"},"hydration"),". ",(0,r.kt)("em",{parentName:"p"},"Reconstitution")," is the term\nused by Eric Evans in the Blue Book: ",(0,r.kt)("em",{parentName:"p"},'"Domain-Driven Design: Tackling Complexity\nin the Heart of Software"'),"."),(0,r.kt)("h2",{id:"features"},"Features"),(0,r.kt)("ul",null,(0,r.kt)("li",{parentName:"ul"},"Simple declaration in a class. You can create a reconstitutor class to handle\nthe reconstitution of a specific entity class, entities that implement a\nspecific interface, entities in a class hierarchy, or those with a specific\nPHP attribute."),(0,r.kt)("li",{parentName:"ul"},"Our abstract classes provide ",(0,r.kt)("inlineCode",{parentName:"li"},"get()")," and ",(0,r.kt)("inlineCode",{parentName:"li"},"set()")," methods as a convenience.\nThey let you work with the properties directly, bypassing getters and setters.\nIt is the best practice in reconstitutions as it frees you to have business\nlogic in the getters and setters."),(0,r.kt)("li",{parentName:"ul"},"The ",(0,r.kt)("inlineCode",{parentName:"li"},"get()")," and ",(0,r.kt)("inlineCode",{parentName:"li"},"set()")," methods are forwarders to a custom implementation of\nSymfony's ",(0,r.kt)("inlineCode",{parentName:"li"},"PropertyAccessorInterface"),". Therefore, you can use the same\nexceptions defined in ",(0,r.kt)("inlineCode",{parentName:"li"},"PropertyAccessorInterface"),"."),(0,r.kt)("li",{parentName:"ul"},"It has what we think is the correct behavior. It asks your reconstitutor to\nsave only after Doctrine has successfully saved the object. It doesn't rely on\nDoctrine seeing the object being dirty before ",(0,r.kt)("inlineCode",{parentName:"li"},"flush()"),"-ing. i.e. your\nentities don't have to modify a Doctrine-managed property \u2014like\n",(0,r.kt)("inlineCode",{parentName:"li"},"$lastUpdated"),"\u2014 just to make sure the correct Doctrine event will be fired.")),(0,r.kt)("h2",{id:"installation"},"Installation"),(0,r.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,r.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,r.kt)(o.Z,{mdxType:"Tabs"},(0,r.kt)(i.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,r.kt)("p",null,"Open a command console, enter your project directory, and execute:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/reconstitutor\n"))),(0,r.kt)(i.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,r.kt)("p",null,"Step 1: Download the Bundle"),(0,r.kt)("p",null,"Open a command console, enter your project directory, and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/reconstitutor\n")),(0,r.kt)("p",null,"Step 2: Enable the Bundle"),(0,r.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,r.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\Reconstitutor\\RekalogikaReconstitutorBundle::class => ['all' => true],\n];\n")))),(0,r.kt)("h2",{id:"usage"},"Usage"),(0,r.kt)("p",null,"Because everyone knows about file uploads, we are going to use it as an\nexample, even if you probably won't use this framework as a means for handling\nfile uploads."),(0,r.kt)("admonition",{type:"info"},(0,r.kt)("p",{parentName:"admonition"},"Speaking about file uploads, we also provide ",(0,r.kt)("a",{parentName:"p",href:"../file"},(0,r.kt)("inlineCode",{parentName:"a"},"rekalogika/file")),"\nframework that handles file uploads and much more. It also utilizes this library\nbehind the scenes.")),(0,r.kt)("p",null,"Suppose you have an ",(0,r.kt)("inlineCode",{parentName:"p"},"Order")," object that stores a payment receipt in the\n",(0,r.kt)("inlineCode",{parentName:"p"},"paymentReceipt")," property:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use Symfony\\Component\\HttpFoundation\\File\\File;\nuse Symfony\\Component\\Uid\\UuidV7;\n\nclass Order\n{\n private string $id;\n private ?File $paymentReceipt = null;\n\n public function __construct()\n {\n $this->id = new UuidV7;\n }\n\n public function getId(): string\n {\n return $this->id;\n }\n\n public function getPaymentReceipt(): ?File\n {\n return $this->paymentReceipt;\n }\n\n public function setPaymentReceipt(File $paymentReceipt): void\n {\n $this->paymentReceipt = $paymentReceipt;\n }\n}\n")),(0,r.kt)("p",null,"During the fetching of the object from the database, Doctrine will instantiate\nthe object and hydrate ",(0,r.kt)("inlineCode",{parentName:"p"},"$id")," and other properties that it manages. Afterward, it\nwill be our reconstitutor's turn to handle the ",(0,r.kt)("inlineCode",{parentName:"p"},"$paymentReceipt")," property.\nSimilar things also happen when the object is persisted to the database, or\nremoved."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Reconstitutor\\AbstractClassReconstitutor;\nuse Symfony\\Component\\HttpFoundation\\File\\File;\n\n/**\n * @extends AbstractClassReconstitutor\n */\nfinal class OrderReconstitutor extends AbstractClassReconstitutor\n{\n /**\n * The class that this reconstitutor manages. It can also be a super class\n * or an interface.\n */\n public static function getClass(): string\n {\n return Order::class;\n }\n\n /**\n * When the object is being saved, we check if the paymentReceipt has been\n * just uploaded. If it is, we save it to a file.\n */\n public function onSave(object $order): void\n {\n $path = sprintf('/tmp/payment_receipt/%s', $order->getId());\n\n $file = $this->get($order, 'paymentReceipt');\n\n if ($file instanceof UploadedFile) {\n file_put_contents($path, $file->getContent());\n $this->set($order, 'paymentReceipt', new File($path));\n }\n }\n\n /**\n * When the object is being loaded from the database, we check if the\n * supposed payment receipt is already saved. If it is, then we load the\n * file to the property.\n */\n public function onLoad(object $order): void\n {\n $path = sprintf('/tmp/payment_receipt/%s', $order->getId());\n\n if (file_exists($path)) {\n $file = new File($path);\n } else {\n $file = null;\n }\n\n $this->set($order, 'paymentReceipt', $file);\n }\n\n /**\n * If the order is being removed, we remove the associated payment receipt\n * here.\n */\n public function onRemove(object $order): void\n {\n $path = sprintf('/tmp/payment_receipt/%s', $order->getId());\n\n if (file_exists($path)) {\n unlink($path);\n }\n }\n}\n")),(0,r.kt)("h2",{id:"reconstitutor-abstract-class"},"Reconstitutor Abstract Class"),(0,r.kt)("p",null,"The example above uses ",(0,r.kt)("inlineCode",{parentName:"p"},"AbstractClassReconstitutor")," where our target object is\nmatched using the class provided by ",(0,r.kt)("inlineCode",{parentName:"p"},"getClass()"),". There is also\n",(0,r.kt)("inlineCode",{parentName:"p"},"AbstractAttributeReconstitutor")," that operates on objects that have a specific\nPHP attribute."))}h.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/efa664a1.143b4df1.js b/assets/js/efa664a1.c0232dd2.js similarity index 66% rename from assets/js/efa664a1.143b4df1.js rename to assets/js/efa664a1.c0232dd2.js index f7f5ad08..d7dc709b 100644 --- a/assets/js/efa664a1.143b4df1.js +++ b/assets/js/efa664a1.c0232dd2.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[181],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>f});var r=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=r.createContext({}),c=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return r.createElement(s.Provider,{value:t},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},d=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=c(n),d=i,f=u["".concat(s,".").concat(d)]||u[d]||m[d]||a;return n?r.createElement(f,o(o({ref:t},p),{},{components:n})):r.createElement(f,o({ref:t},p))}));function f(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:i,o[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>a,metadata:()=>l,toc:()=>c});var r=n(7462),i=(n(7294),n(3905));const a={title:"Creating an Object ID Resolver"},o=void 0,l={unversionedId:"file-bundle/object-id-resolver",id:"file-bundle/object-id-resolver",title:"Creating an Object ID Resolver",description:"The framework assumes that the ID of your entity is returned by the method",source:"@site/docs/file-bundle/23-object-id-resolver.md",sourceDirName:"file-bundle",slug:"/file-bundle/object-id-resolver",permalink:"/file-bundle/object-id-resolver",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/23-object-id-resolver.md",tags:[],version:"current",sidebarPosition:23,frontMatter:{title:"Creating an Object ID Resolver"},sidebar:"docs",previous:{title:"Creating Filters",permalink:"/file-bundle/creating-filters"},next:{title:"rekalogika/psr-16-simple-cache-bundle",permalink:"/psr-16-simple-cache-bundle/"}},s={},c=[{value:"If Your Entity Simply Uses a Different Method",id:"if-your-entity-simply-uses-a-different-method",level:2},{value:"If It Is More Complicated Than That",id:"if-it-is-more-complicated-than-that",level:2}],p={toc:c},u="wrapper";function m(e){let{components:t,...n}=e;return(0,i.kt)(u,(0,r.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"The framework assumes that the ID of your entity is returned by the method\n",(0,i.kt)("inlineCode",{parentName:"p"},"getId()"),". If your entity uses a different mechanism, you need to create an\nimplementation of ",(0,i.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),"."),(0,i.kt)("admonition",{title:"Protip",type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"You can have multiple implementations of ",(0,i.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface")," in your\napplication. The framework will use the first one that returns a value.")),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"},"If you have a custom implementation of ",(0,i.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),", the default\nimplementation is still active, but has a lower priority than your custom\nimplementation.")),(0,i.kt)("h2",{id:"if-your-entity-simply-uses-a-different-method"},"If Your Entity Simply Uses a Different Method"),(0,i.kt)("p",null,"If your entity simply uses a different method name, you can reuse the default\nimplementation of ",(0,i.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/services.yaml",title:"config/services.yaml"},"services:\n app.object_id_resolver:\n class: 'Rekalogika\\File\\Association\\ObjectIdResolver\\DefaultObjectIdResolver'\n args:\n - 'getIdentifier' # put the method name here\n tags:\n - { name: 'rekalogika.file.association.object_id_resolver' }\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},(0,i.kt)("inlineCode",{parentName:"p"},"DefaultObjectIdResolver")," can handle return types of ",(0,i.kt)("inlineCode",{parentName:"p"},"string"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"int"),", and\n",(0,i.kt)("inlineCode",{parentName:"p"},"Stringable"),".")),(0,i.kt)("h2",{id:"if-it-is-more-complicated-than-that"},"If It Is More Complicated Than That"),(0,i.kt)("p",null,"Then you need to create your own implementation of ",(0,i.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\Association\\ObjectIdResolverInterface;\n\nclass MyObjectIdResolver implements ObjectIdResolverInterface\n{\n public function getObjectId(object $object): string\n {\n // your implementation here\n }\n}\n")),(0,i.kt)("p",null,"If you are using autoconfiguration, then it is all set. If not, you need to\nregister your class in the service container:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/services.yaml",title:"config/services.yaml"},"services:\n App\\MyObjectIdResolver:\n tags:\n - { name: 'rekalogika.file.association.object_id_resolver' }\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[181],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>f});var r=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var s=r.createContext({}),c=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return r.createElement(s.Provider,{value:t},e.children)},u="mdxType",m={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},d=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=c(n),d=i,f=u["".concat(s,".").concat(d)]||u[d]||m[d]||a;return n?r.createElement(f,o(o({ref:t},p),{},{components:n})):r.createElement(f,o({ref:t},p))}));function f(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:i,o[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>m,frontMatter:()=>a,metadata:()=>l,toc:()=>c});var r=n(7462),i=(n(7294),n(3905));const a={title:"Creating an Object ID Resolver"},o=void 0,l={unversionedId:"file-bundle/object-id-resolver",id:"file-bundle/object-id-resolver",title:"Creating an Object ID Resolver",description:"The framework assumes that the ID of your entity is returned by the method",source:"@site/docs/file-bundle/23-object-id-resolver.md",sourceDirName:"file-bundle",slug:"/file-bundle/object-id-resolver",permalink:"/file-bundle/object-id-resolver",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/23-object-id-resolver.md",tags:[],version:"current",sidebarPosition:23,frontMatter:{title:"Creating an Object ID Resolver"},sidebar:"docs",previous:{title:"Creating Filters",permalink:"/file-bundle/creating-filters"},next:{title:"rekalogika/psr-16-simple-cache-bundle",permalink:"/psr-16-simple-cache-bundle/"}},s={},c=[{value:"If Your Entity Simply Uses a Different Method",id:"if-your-entity-simply-uses-a-different-method",level:2},{value:"If It Is More Complicated Than That",id:"if-it-is-more-complicated-than-that",level:2}],p={toc:c},u="wrapper";function m(e){let{components:t,...n}=e;return(0,i.kt)(u,(0,r.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"The framework assumes that the ID of your entity is returned by the method\n",(0,i.kt)("inlineCode",{parentName:"p"},"getId()"),". If your entity uses a different mechanism, you need to create an\nimplementation of ",(0,i.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),"."),(0,i.kt)("admonition",{title:"Protip",type:"tip"},(0,i.kt)("p",{parentName:"admonition"},"You can have multiple implementations of ",(0,i.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface")," in your\napplication. The framework will use the first one that returns a value.")),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"},"If you have a custom implementation of ",(0,i.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),", the default\nimplementation is still active but has a lower priority than your custom\nimplementation.")),(0,i.kt)("h2",{id:"if-your-entity-simply-uses-a-different-method"},"If Your Entity Simply Uses a Different Method"),(0,i.kt)("p",null,"If your entity simply uses a different method name, you can reuse the default\nimplementation of ",(0,i.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/services.yaml",title:"config/services.yaml"},"services:\n app.object_id_resolver:\n class: 'Rekalogika\\File\\Association\\ObjectIdResolver\\DefaultObjectIdResolver'\n args:\n - 'getIdentifier' # put the method name here\n tags:\n - { name: 'rekalogika.file.association.object_id_resolver' }\n")),(0,i.kt)("admonition",{type:"note"},(0,i.kt)("p",{parentName:"admonition"},(0,i.kt)("inlineCode",{parentName:"p"},"DefaultObjectIdResolver")," can handle return types of ",(0,i.kt)("inlineCode",{parentName:"p"},"string"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"int"),", and\n",(0,i.kt)("inlineCode",{parentName:"p"},"Stringable"),".")),(0,i.kt)("h2",{id:"if-it-is-more-complicated-than-that"},"If It Is More Complicated Than That"),(0,i.kt)("p",null,"Then you need to create your own implementation of ",(0,i.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\Association\\ObjectIdResolverInterface;\n\nclass MyObjectIdResolver implements ObjectIdResolverInterface\n{\n public function getObjectId(object $object): string\n {\n // your implementation here\n }\n}\n")),(0,i.kt)("p",null,"If you are using autoconfiguration, then it is all set. If not, you need to\nregister your class in the service container:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-yaml",metastring:"title=config/services.yaml",title:"config/services.yaml"},"services:\n App\\MyObjectIdResolver:\n tags:\n - { name: 'rekalogika.file.association.object_id_resolver' }\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/fd37b2f4.9c1a9d35.js b/assets/js/fd37b2f4.9c1a9d35.js new file mode 100644 index 00000000..60d1df89 --- /dev/null +++ b/assets/js/fd37b2f4.9c1a9d35.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[814],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>g});var i=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=i.createContext({}),c=function(e){var t=i.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return i.createElement(s.Provider,{value:t},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},f=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=c(n),f=a,g=u["".concat(s,".").concat(f)]||u[f]||d[f]||r;return n?i.createElement(g,o(o({ref:t},p),{},{components:n})):i.createElement(g,o({ref:t},p))}));function g(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,o=new Array(r);o[0]=f;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:a,o[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var i=n(7462),a=(n(7294),n(3905));const r={title:"Associating Files with Doctrine Entities"},o=void 0,l={unversionedId:"file-bundle/doctrine-entity",id:"file-bundle/doctrine-entity",title:"Associating Files with Doctrine Entities",description:"This chapter describes how to create a file property in a Doctrine entity that",source:"@site/docs/file-bundle/03-doctrine-entity.md",sourceDirName:"file-bundle",slug:"/file-bundle/doctrine-entity",permalink:"/file-bundle/doctrine-entity",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/03-doctrine-entity.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{title:"Associating Files with Doctrine Entities"},sidebar:"docs",previous:{title:"Installation & Configuration",permalink:"/file-bundle/installation"},next:{title:"Symfony Integration",permalink:"/file-bundle/symfony"}},s={},c=[{value:"Creating a File Property in an Entity",id:"creating-a-file-property-in-an-entity",level:2},{value:"Working With Entities & Files",id:"working-with-entities--files",level:2},{value:"Creating an entity, associating it with a file, & persisting it",id:"creating-an-entity-associating-it-with-a-file--persisting-it",level:3},{value:"Replacing an associated file",id:"replacing-an-associated-file",level:3},{value:"Updating the metadata of an associated file",id:"updating-the-metadata-of-an-associated-file",level:3},{value:"Removing an associated file",id:"removing-an-associated-file",level:3},{value:"Removing the entity will also remove the associated file",id:"removing-the-entity-will-also-remove-the-associated-file",level:3},{value:"Symfony Integration (or How To Upload Files)",id:"symfony-integration-or-how-to-upload-files",level:2},{value:"(alternative) Entity Setup Using Interface",id:"alternative-entity-setup-using-interface",level:2}],p={toc:c},u="wrapper";function d(e){let{components:t,...n}=e;return(0,a.kt)(u,(0,i.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"This chapter describes how to create a file property in a Doctrine entity that\nyou can use to associate a file to an entity, including to store the result of a\nfile upload."),(0,a.kt)("admonition",{title:"Preparation",type:"info"},(0,a.kt)("p",{parentName:"admonition"},"To enable this feature, you need to install the package\n",(0,a.kt)("inlineCode",{parentName:"p"},"rekalogika/file-association"),":"),(0,a.kt)("pre",{parentName:"admonition"},(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-association\n"))),(0,a.kt)("h2",{id:"creating-a-file-property-in-an-entity"},"Creating a File Property in an Entity"),(0,a.kt)("p",null,"To create a file property in an entity that will be managed by this framework,\nyou need to:"),(0,a.kt)("ol",null,(0,a.kt)("li",{parentName:"ol"},"Create a property that accepts a `FileInterface``."),(0,a.kt)("li",{parentName:"ol"},"Add the attribute ",(0,a.kt)("inlineCode",{parentName:"li"},"#[WithFileAssociation]")," to the class."),(0,a.kt)("li",{parentName:"ol"},"Add the attribute ",(0,a.kt)("inlineCode",{parentName:"li"},"#[AsFileAssociation]")," to the property.")),(0,a.kt)("admonition",{type:"caution"},(0,a.kt)("p",{parentName:"admonition"},"The framework assumes that it can get the ID of the entity by calling the method\n",(0,a.kt)("inlineCode",{parentName:"p"},"getId()"),". If your entity uses a different mechanism, you need to implement\n",(0,a.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),". See the chapter ",(0,a.kt)("a",{parentName:"p",href:"object-id-resolver"},"Creating Object ID\nResolver")," for more information.")),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\File\\Association\\Attribute\\WithFileAssociation;\nuse Rekalogika\\File\\Association\\Attribute\\AsFileAssociation;\nuse Rekalogika\\File\\File;\n\n// highlight-next-line\n#[WithFileAssociation]\nclass Product\n{\n /**\n * The file property must accept a FileInterface\n */\n // highlight-start\n #[AsFileAssociation]\n private ?FileInterface $image = null;\n // highlight-end\n\n /**\n * The framework needs the ID of the entity. By default, it will call getId()\n * of the object to get the ID. If your entity doesn't use getId(), read\n * the next section.\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n //\n // The rest of this class is inconsequential to the framework\n //\n\n /**\n * This framework reads and writes directly to the properties, even if\n * private. Therefore, you are free to have your own business logic in the\n * getters and setters.\n */\n public function getImage(): FileInterface\n {\n if (date('m-d') == '04-01') { // if today is april 1st\n return new File('shock-image.jpg'); // april fools!\n }\n\n return $this->image;\n }\n\n public function setImage(?FileInterface $image): self\n {\n if ($this->status == 'published') {\n throw new \\Exception(\"Cannot change a published product's image\");\n }\n\n $this->image = $image;\n\n return $this;\n }\n}\n")),(0,a.kt)("h2",{id:"working-with-entities--files"},"Working With Entities & Files"),(0,a.kt)("p",null,"You can work with the entities and associated files as usual, and they will work\nthe way you expect them to work."),(0,a.kt)("h3",{id:"creating-an-entity-associating-it-with-a-file--persisting-it"},"Creating an entity, associating it with a file, & persisting it"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\ORM\\EntityManagerInterface;\nuse Rekalogika\\File\\File;\n\n/** @var EntityManagerInterface $entityManager */\n\n$product = new Product();\n$image = new File('/tmp/image.png');\n$product->setImage($image);\n\n$entityManager->persist($product);\n$entityManager->flush();\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"The framework will copy the file to the storage location, and leave the original\nfile alone. It is the responsibility of the caller to delete the original if it\nwishes to do so."),(0,a.kt)("p",{parentName:"admonition"},"If the file arrived from a file upload, PHP will delete the file automatically\nwhen the request ends.")),(0,a.kt)("h3",{id:"replacing-an-associated-file"},"Replacing an associated file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\ORM\\EntityManagerInterface;\nuse Rekalogika\\File\\File;\n\n/** @var EntityManagerInterface $entityManager */\n/** @var Product $product */\n\n$newImage = new File('/tmp/newImage.png')\n$product->setImage($newImage);\n$entityManager->flush();\n")),(0,a.kt)("h3",{id:"updating-the-metadata-of-an-associated-file"},"Updating the metadata of an associated file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\ORM\\EntityManagerInterface;\n\n/** @var Product $product */\n\n$product->getImage()?->setName('newImage.png');\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"Files are not Doctrine entities. File modifications are carried out\nimmediately, independent of Doctrine's ",(0,a.kt)("inlineCode",{parentName:"p"},"flush()"),".")),(0,a.kt)("h3",{id:"removing-an-associated-file"},"Removing an associated file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\ORM\\EntityManagerInterface;\n\n/** @var EntityManagerInterface $entityManager */\n/** @var Product $product */\n\n$product->setImage(null);\n$entityManager->flush();\n")),(0,a.kt)("h3",{id:"removing-the-entity-will-also-remove-the-associated-file"},"Removing the entity will also remove the associated file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\ORM\\EntityManagerInterface;\n\n/** @var EntityManagerInterface $entityManager */\n/** @var Product $product */\n\n$entityManager->remove($product);\n$entityManager->flush();\n")),(0,a.kt)("h2",{id:"symfony-integration-or-how-to-upload-files"},"Symfony Integration (or How To Upload Files)"),(0,a.kt)("p",null,"For integrations with various Symfony Components, including HttpFoundation,\nForm, and Validator, please read the chapter ",(0,a.kt)("a",{parentName:"p",href:"symfony"},"Symfony Integration"),"."),(0,a.kt)("h2",{id:"alternative-entity-setup-using-interface"},"(alternative) Entity Setup Using Interface"),(0,a.kt)("p",null,"As an alternative to using attributes to mark the file property above, you can\nalso have your entity implement ",(0,a.kt)("inlineCode",{parentName:"p"},"FileAssociationInterface"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\Contracts\\File\\Association\\FileAssociationInterface;\n\nclass Product implements FileAssociationInterface\n{\n /**\n * The file properties must accept FileInterface\n */\n private ?FileInterface $image = null;\n\n /**\n * This method gives the list of properties of this class that will\n * be managed by this framework. In this case it tells us that the\n * property 'image' is a file property we need to manage.\n */\n public static function getFileAssociationPropertyList(): array\n {\n return ['image'];\n }\n\n /**\n * The framework needs the ID of the entity. By default, it will call getId()\n * of the object to get the ID.\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n //\n // The rest of this class is inconsequential to the framework\n //\n\n /**\n * This framework reads and writes directly to the properties, even if\n * private. Therefore, you are free to have your own business logic in the\n * getters and setters.\n */\n public function getImage(): FileInterface\n {\n if (date('m-d') == '04-01') { // if today is april 1st\n return new File('shock-image.jpg'); // april fools!\n }\n\n return $this->image;\n }\n\n public function setImage(?FileInterface $image): self\n {\n if ($this->status == 'published') {\n throw new \\Exception(\"Cannot change a published product's image\");\n }\n\n $this->image = $image;\n\n return $this;\n }\n}\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"We recommend using attributes instead of implementing the interface. But\ncurrently using attributes can be less performant than using the interface.")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/fd37b2f4.f10784d2.js b/assets/js/fd37b2f4.f10784d2.js deleted file mode 100644 index 7e5b90db..00000000 --- a/assets/js/fd37b2f4.f10784d2.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[814],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>g});var i=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=i.createContext({}),c=function(e){var t=i.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},p=function(e){var t=c(e.components);return i.createElement(s.Provider,{value:t},e.children)},u="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},f=i.forwardRef((function(e,t){var n=e.components,a=e.mdxType,r=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=c(n),f=a,g=u["".concat(s,".").concat(f)]||u[f]||d[f]||r;return n?i.createElement(g,o(o({ref:t},p),{},{components:n})):i.createElement(g,o({ref:t},p))}));function g(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var r=n.length,o=new Array(r);o[0]=f;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l[u]="string"==typeof e?e:a,o[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var i=n(7462),a=(n(7294),n(3905));const r={title:"Associating Files with Doctrine Entities"},o=void 0,l={unversionedId:"file-bundle/doctrine-entity",id:"file-bundle/doctrine-entity",title:"Associating Files with Doctrine Entities",description:"This chapter describes how to create a file property in a Doctrine entity that",source:"@site/docs/file-bundle/03-doctrine-entity.md",sourceDirName:"file-bundle",slug:"/file-bundle/doctrine-entity",permalink:"/file-bundle/doctrine-entity",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/file-bundle/03-doctrine-entity.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{title:"Associating Files with Doctrine Entities"},sidebar:"docs",previous:{title:"Installation & Configuration",permalink:"/file-bundle/installation"},next:{title:"Symfony Integration",permalink:"/file-bundle/symfony"}},s={},c=[{value:"Creating a File Property in an Entity",id:"creating-a-file-property-in-an-entity",level:2},{value:"Working With Entities & Files",id:"working-with-entities--files",level:2},{value:"Creating an entity, associating it with a file, & persisting it",id:"creating-an-entity-associating-it-with-a-file--persisting-it",level:3},{value:"Replacing an associated file",id:"replacing-an-associated-file",level:3},{value:"Updating the metadata of an associated file",id:"updating-the-metadata-of-an-associated-file",level:3},{value:"Removing an associated file",id:"removing-an-associated-file",level:3},{value:"Removing the entity will also remove the associated file",id:"removing-the-entity-will-also-remove-the-associated-file",level:3},{value:"Symfony Integration (or How To Upload Files)",id:"symfony-integration-or-how-to-upload-files",level:2},{value:"(alternative) Entity Setup Using Interface",id:"alternative-entity-setup-using-interface",level:2}],p={toc:c},u="wrapper";function d(e){let{components:t,...n}=e;return(0,a.kt)(u,(0,i.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"This chapter describes how to create a file property in a Doctrine entity that\nyou can use to associate a file to an entity, including to store the result of a\nfile upload."),(0,a.kt)("admonition",{title:"Preparation",type:"info"},(0,a.kt)("p",{parentName:"admonition"},"To enable this feature, you need to install the package\n",(0,a.kt)("inlineCode",{parentName:"p"},"rekalogika/file-association"),":"),(0,a.kt)("pre",{parentName:"admonition"},(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/file-association\n"))),(0,a.kt)("h2",{id:"creating-a-file-property-in-an-entity"},"Creating a File Property in an Entity"),(0,a.kt)("p",null,"To create a file property in an entity that will be managed by this framework,\nyou need to:"),(0,a.kt)("ol",null,(0,a.kt)("li",{parentName:"ol"},"Create a property that accept a ",(0,a.kt)("inlineCode",{parentName:"li"},"FileInterface"),"."),(0,a.kt)("li",{parentName:"ol"},"Add the attribute ",(0,a.kt)("inlineCode",{parentName:"li"},"#[WithFileAssociation]")," to the class."),(0,a.kt)("li",{parentName:"ol"},"Add the attribute ",(0,a.kt)("inlineCode",{parentName:"li"},"#[AsFileAssociation]")," to the property.")),(0,a.kt)("admonition",{type:"caution"},(0,a.kt)("p",{parentName:"admonition"},"The framework assumes that it can get the ID of the entity by calling the method\n",(0,a.kt)("inlineCode",{parentName:"p"},"getId()"),". If your entity uses a different mechanism, you need to implement\n",(0,a.kt)("inlineCode",{parentName:"p"},"ObjectIdResolverInterface"),". See the chapter ",(0,a.kt)("a",{parentName:"p",href:"object-id-resolver"},"Creating Object ID\nResolver")," for more information.")),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\File\\Association\\Attribute\\WithFileAssociation;\nuse Rekalogika\\File\\Association\\Attribute\\AsFileAssociation;\nuse Rekalogika\\File\\File;\n\n// highlight-next-line\n#[WithFileAssociation]\nclass Product\n{\n /**\n * The file property must accept a FileInterface\n */\n // highlight-start\n #[AsFileAssociation]\n private ?FileInterface $image = null;\n // highlight-end\n\n /**\n * The framework needs the ID of the entity. By default, it will call getId()\n * of the object to get the ID. If your entity doesn't use getId(), read\n * the next section.\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n //\n // The rest of this class is inconsequential to the framework\n //\n\n /**\n * This framework reads and writes directly to the properties, even if\n * private. Therefore, you are free to have your own business logic in the\n * getters and setters.\n */\n public function getImage(): FileInterface\n {\n if (date('m-d') == '04-01') { // if today is april 1st\n return new File('shock-image.jpg'); // april fools!\n }\n\n return $this->image;\n }\n\n public function setImage(?FileInterface $image): self\n {\n if ($this->status == 'published') {\n throw new \\Exception(\"Cannot change a published product's image\");\n }\n\n $this->image = $image;\n\n return $this;\n }\n}\n")),(0,a.kt)("h2",{id:"working-with-entities--files"},"Working With Entities & Files"),(0,a.kt)("p",null,"You can work with the entities and associated files as usual, and they will work\nthe way you expect them to work."),(0,a.kt)("h3",{id:"creating-an-entity-associating-it-with-a-file--persisting-it"},"Creating an entity, associating it with a file, & persisting it"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\ORM\\EntityManagerInterface;\nuse Rekalogika\\File\\File;\n\n/** @var EntityManagerInterface $entityManager */\n\n$product = new Product();\n$image = new File('/tmp/image.png');\n$product->setImage($image);\n\n$entityManager->persist($product);\n$entityManager->flush();\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"The framework will copy the file to the storage location, and leave the original\nfile alone. It is the responsibility of the caller to delete the original if it\nwishes to do so."),(0,a.kt)("p",{parentName:"admonition"},"If the file arrived from a file upload, PHP will delete the file automatically\nwhen the request ends.")),(0,a.kt)("h3",{id:"replacing-an-associated-file"},"Replacing an associated file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\ORM\\EntityManagerInterface;\nuse Rekalogika\\File\\File;\n\n/** @var EntityManagerInterface $entityManager */\n/** @var Product $product */\n\n$newImage = new File('/tmp/newImage.png')\n$product->setImage($newImage);\n$entityManager->flush();\n")),(0,a.kt)("h3",{id:"updating-the-metadata-of-an-associated-file"},"Updating the metadata of an associated file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\ORM\\EntityManagerInterface;\n\n/** @var Product $product */\n\n$product->getImage()?->setName('newImage.png');\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"Files are not Doctrine entities. File modifications are carried out\nimmediately, independent of Doctrine's ",(0,a.kt)("inlineCode",{parentName:"p"},"flush()"),".")),(0,a.kt)("h3",{id:"removing-an-associated-file"},"Removing an associated file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\ORM\\EntityManagerInterface;\n\n/** @var EntityManagerInterface $entityManager */\n/** @var Product $product */\n\n$product->setImage(null);\n$entityManager->flush();\n")),(0,a.kt)("h3",{id:"removing-the-entity-will-also-remove-the-associated-file"},"Removing the entity will also remove the associated file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Doctrine\\ORM\\EntityManagerInterface;\n\n/** @var EntityManagerInterface $entityManager */\n/** @var Product $product */\n\n$entityManager->remove($product);\n$entityManager->flush();\n")),(0,a.kt)("h2",{id:"symfony-integration-or-how-to-upload-files"},"Symfony Integration (or How To Upload Files)"),(0,a.kt)("p",null,"For integrations with various Symfony Components, including HttpFoundation,\nForm, and Validator, please read the chapter ",(0,a.kt)("a",{parentName:"p",href:"symfony"},"Symfony Integration"),"."),(0,a.kt)("h2",{id:"alternative-entity-setup-using-interface"},"(alternative) Entity Setup Using Interface"),(0,a.kt)("p",null,"As an alternative to using attributes to mark the file property above, you can\nalso have your entity implement ",(0,a.kt)("inlineCode",{parentName:"p"},"FileAssociationInterface"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php"},"use Rekalogika\\Contracts\\File\\FileInterface;\nuse Rekalogika\\Contracts\\File\\Association\\FileAssociationInterface;\n\nclass Product implements FileAssociationInterface\n{\n /**\n * The file properties must accept FileInterface\n */\n private ?FileInterface $image = null;\n\n /**\n * This method gives the list of properties of this class that will\n * be managed by this framework. In this case it tells us that the\n * property 'image' is a file property we need to manage.\n */\n public static function getFileAssociationPropertyList(): array\n {\n return ['image'];\n }\n\n /**\n * The framework needs the ID of the entity. By default, it will call getId()\n * of the object to get the ID.\n */\n public function getId(): string\n {\n return $this->id;\n }\n\n //\n // The rest of this class is inconsequential to the framework\n //\n\n /**\n * This framework reads and writes directly to the properties, even if\n * private. Therefore, you are free to have your own business logic in the\n * getters and setters.\n */\n public function getImage(): FileInterface\n {\n if (date('m-d') == '04-01') { // if today is april 1st\n return new File('shock-image.jpg'); // april fools!\n }\n\n return $this->image;\n }\n\n public function setImage(?FileInterface $image): self\n {\n if ($this->status == 'published') {\n throw new \\Exception(\"Cannot change a published product's image\");\n }\n\n $this->image = $image;\n\n return $this;\n }\n}\n")),(0,a.kt)("admonition",{type:"note"},(0,a.kt)("p",{parentName:"admonition"},"We recommend using attributes instead of implementing the interface. But\ncurrently using attributes can be less performant than using the interface.")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/fe998a72.f0b7208d.js b/assets/js/fe998a72.4b733fce.js similarity index 82% rename from assets/js/fe998a72.f0b7208d.js rename to assets/js/fe998a72.4b733fce.js index 39c77e47..3422c730 100644 --- a/assets/js/fe998a72.f0b7208d.js +++ b/assets/js/fe998a72.4b733fce.js @@ -1 +1 @@ -"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[43],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>f});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),u=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},c=function(e){var t=u(e.components);return r.createElement(s.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),p=u(n),m=a,f=p["".concat(s,".").concat(m)]||p[m]||d[m]||o;return n?r.createElement(f,l(l({ref:t},c),{},{components:n})):r.createElement(f,l({ref:t},c))}));function f(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,l=new Array(o);l[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[p]="string"==typeof e?e:a,l[1]=i;for(var u=2;u{n.d(t,{Z:()=>l});var r=n(7294),a=n(6010);const o={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.Z)(o.tabItem,l),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>w});var r=n(7462),a=n(7294),o=n(6010),l=n(2466),i=n(6550),s=n(1980),u=n(7392),c=n(12);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:r,default:a}}=e;return{value:t,label:n,attributes:r,default:a}}))}function d(e){const{values:t,children:n}=e;return(0,a.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function m(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function f(e){let{queryString:t=!1,groupId:n}=e;const r=(0,i.k6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(o),(0,a.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(r.location.search);t.set(o,e),r.replace({...r.location,search:t.toString()})}),[o,r])]}function h(e){const{defaultValue:t,queryString:n=!1,groupId:r}=e,o=d(e),[l,i]=(0,a.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const r=n.find((e=>e.default))??n[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:t,tabValues:o}))),[s,u]=f({queryString:n,groupId:r}),[p,h]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[r,o]=(0,c.Nk)(n);return[r,(0,a.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:r}),b=(()=>{const e=s??p;return m({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{b&&i(b)}),[b]);return{selectedValue:l,selectValue:(0,a.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,o]),tabValues:o}}var b=n(2389);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function y(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.o5)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),r=u[n].value;r!==i&&(p(t),s(r))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return a.createElement("li",(0,r.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},l,{className:(0,o.Z)("tabs__item",v.tabItem,l?.className,{"tabs__item--active":i===t})}),n??t)})))}function g(e){let{lazy:t,children:n,selectedValue:r}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==r}))))}function k(e){const t=h(e);return a.createElement("div",{className:(0,o.Z)("tabs-container",v.tabList)},a.createElement(y,(0,r.Z)({},e,t)),a.createElement(g,(0,r.Z)({},e,t)))}function w(e){const t=(0,b.Z)();return a.createElement(k,(0,r.Z)({key:String(t)},e))}},263:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>f,frontMatter:()=>i,metadata:()=>u,toc:()=>p});var r=n(7462),a=(n(7294),n(3905)),o=n(4866),l=n(5162);const i={title:"Introduction & Installation"},s=void 0,u={unversionedId:"domain-event/intro",id:"domain-event/intro",title:"Introduction & Installation",description:"An implementation of domain event pattern",source:"@site/docs/domain-event/00-intro.md",sourceDirName:"domain-event",slug:"/domain-event/intro",permalink:"/domain-event/intro",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/domain-event/00-intro.md",tags:[],version:"current",sidebarPosition:0,frontMatter:{title:"Introduction & Installation"},sidebar:"docs",previous:{title:"rekalogika/domain-event",permalink:"/domain-event/"},next:{title:"Basic Usage",permalink:"/domain-event/basic-usage"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Installation",id:"installation",level:2},{value:"Caveats",id:"caveats",level:2},{value:"License",id:"license",level:2},{value:"Contributing",id:"contributing",level:2}],d={toc:p},m="wrapper";function f(e){let{components:t,...n}=e;return(0,a.kt)(m,(0,r.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"An implementation of ",(0,a.kt)("a",{parentName:"p",href:"https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-events-design-implementation"},"domain event pattern"),"\nfor Symfony & Doctrine."),(0,a.kt)("h2",{id:"features"},"Features"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Works out of the box. No configuration is required."),(0,a.kt)("li",{parentName:"ul"},"Simple, unopinionated architecture. Uses plain event objects, and doesn't\nrequire much from your domain entities."),(0,a.kt)("li",{parentName:"ul"},"Uses standard Symfony's event dispatcher, with the same dispatching semantics\n& listener registrations."),(0,a.kt)("li",{parentName:"ul"},"Three dispatching strategies: pre-flush, post-flush, and immediate."),(0,a.kt)("li",{parentName:"ul"},"In pre-flush or post-flush modes, multiple events considered identical are\ndispatched only once."),(0,a.kt)("li",{parentName:"ul"},"Does not require you to change how you work with entities, most of the time."),(0,a.kt)("li",{parentName:"ul"},"Should work everywhere without any change: in controllers, message handlers,\ncommand line, etc."),(0,a.kt)("li",{parentName:"ul"},"Separated contracts & framework. Useful for enforcing architectural\nboundaries. Your domain doesn't have to depend on the framework.")),(0,a.kt)("h2",{id:"installation"},"Installation"),(0,a.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,a.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,a.kt)(o.Z,{mdxType:"Tabs"},(0,a.kt)(l.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Open a command console, enter your project directory and execute:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/domain-event\n"))),(0,a.kt)(l.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Step 1: Download the Bundle"),(0,a.kt)("p",null,"Open a command console, enter your project directory and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/domain-event\n")),(0,a.kt)("p",null,"Step 2: Enable the Bundle"),(0,a.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,a.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\DomainEvent\\RekalogikaDomainEventBundle::class => ['all' => true],\n];\n")))),(0,a.kt)("h2",{id:"caveats"},"Caveats"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Currently only supports ",(0,a.kt)("inlineCode",{parentName:"li"},"EntityManager"),". Support for other ",(0,a.kt)("inlineCode",{parentName:"li"},"ObjectManager"),"s\nis planned."),(0,a.kt)("li",{parentName:"ul"},"It is not aware of explicit transactions yet. You should dispatch the events\nmanually if you are using an explicit transaction as described above. In the\nfuture, we have plans to dispatch post-flush events after the outermost\n",(0,a.kt)("inlineCode",{parentName:"li"},"commit()"),", and dispatch pre-flush events before every ",(0,a.kt)("inlineCode",{parentName:"li"},"commit()"),"."),(0,a.kt)("li",{parentName:"ul"},"It is an inconvenience that Symfony Event Dispatcher does not currently\nsupport event inheritance. We cannot have a single listener for an entire\nclass of domain events, and for example, use it to implement the outbox\npattern. We plan to fix this in the future.")),(0,a.kt)("h2",{id:"license"},"License"),(0,a.kt)("p",null,"MIT"),(0,a.kt)("h2",{id:"contributing"},"Contributing"),(0,a.kt)("p",null,"This framework consists of multiple repositories splitted from a monorepo. Be\nsure to submit issues and pull request to the\n",(0,a.kt)("a",{parentName:"p",href:"https://github.com/rekalogika/domain-event-src"},(0,a.kt)("inlineCode",{parentName:"a"},"rekalogika/domain-event-src"))," monorepo."))}f.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[43],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>f});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),u=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):l(l({},t),e)),n},c=function(e){var t=u(e.components);return r.createElement(s.Provider,{value:t},e.children)},p="mdxType",d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,c=i(e,["components","mdxType","originalType","parentName"]),p=u(n),m=a,f=p["".concat(s,".").concat(m)]||p[m]||d[m]||o;return n?r.createElement(f,l(l({ref:t},c),{},{components:n})):r.createElement(f,l({ref:t},c))}));function f(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,l=new Array(o);l[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i[p]="string"==typeof e?e:a,l[1]=i;for(var u=2;u{n.d(t,{Z:()=>l});var r=n(7294),a=n(6010);const o={tabItem:"tabItem_Ymn6"};function l(e){let{children:t,hidden:n,className:l}=e;return r.createElement("div",{role:"tabpanel",className:(0,a.Z)(o.tabItem,l),hidden:n},t)}},4866:(e,t,n)=>{n.d(t,{Z:()=>w});var r=n(7462),a=n(7294),o=n(6010),l=n(2466),i=n(6550),s=n(1980),u=n(7392),c=n(12);function p(e){return function(e){return a.Children.map(e,(e=>{if(!e||(0,a.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the component should be , and every should have a unique "value" prop.`)}))?.filter(Boolean)??[]}(e).map((e=>{let{props:{value:t,label:n,attributes:r,default:a}}=e;return{value:t,label:n,attributes:r,default:a}}))}function d(e){const{values:t,children:n}=e;return(0,a.useMemo)((()=>{const e=t??p(n);return function(e){const t=(0,u.l)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in . Every value needs to be unique.`)}(e),e}),[t,n])}function m(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function f(e){let{queryString:t=!1,groupId:n}=e;const r=(0,i.k6)(),o=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,s._X)(o),(0,a.useCallback)((e=>{if(!o)return;const t=new URLSearchParams(r.location.search);t.set(o,e),r.replace({...r.location,search:t.toString()})}),[o,r])]}function h(e){const{defaultValue:t,queryString:n=!1,groupId:r}=e,o=d(e),[l,i]=(0,a.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the component requires at least one children component");if(t){if(!m({value:t,tabValues:n}))throw new Error(`Docusaurus error: The has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const r=n.find((e=>e.default))??n[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:t,tabValues:o}))),[s,u]=f({queryString:n,groupId:r}),[p,h]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[r,o]=(0,c.Nk)(n);return[r,(0,a.useCallback)((e=>{n&&o.set(e)}),[n,o])]}({groupId:r}),b=(()=>{const e=s??p;return m({value:e,tabValues:o})?e:null})();(0,a.useLayoutEffect)((()=>{b&&i(b)}),[b]);return{selectedValue:l,selectValue:(0,a.useCallback)((e=>{if(!m({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);i(e),u(e),h(e)}),[u,h,o]),tabValues:o}}var b=n(2389);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};function y(e){let{className:t,block:n,selectedValue:i,selectValue:s,tabValues:u}=e;const c=[],{blockElementScrollPositionUntilNextRender:p}=(0,l.o5)(),d=e=>{const t=e.currentTarget,n=c.indexOf(t),r=u[n].value;r!==i&&(p(t),s(r))},m=e=>{let t=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const n=c.indexOf(e.currentTarget)+1;t=c[n]??c[0];break}case"ArrowLeft":{const n=c.indexOf(e.currentTarget)-1;t=c[n]??c[c.length-1];break}}t?.focus()};return a.createElement("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.Z)("tabs",{"tabs--block":n},t)},u.map((e=>{let{value:t,label:n,attributes:l}=e;return a.createElement("li",(0,r.Z)({role:"tab",tabIndex:i===t?0:-1,"aria-selected":i===t,key:t,ref:e=>c.push(e),onKeyDown:m,onClick:d},l,{className:(0,o.Z)("tabs__item",v.tabItem,l?.className,{"tabs__item--active":i===t})}),n??t)})))}function g(e){let{lazy:t,children:n,selectedValue:r}=e;const o=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=o.find((e=>e.props.value===r));return e?(0,a.cloneElement)(e,{className:"margin-top--md"}):null}return a.createElement("div",{className:"margin-top--md"},o.map(((e,t)=>(0,a.cloneElement)(e,{key:t,hidden:e.props.value!==r}))))}function k(e){const t=h(e);return a.createElement("div",{className:(0,o.Z)("tabs-container",v.tabList)},a.createElement(y,(0,r.Z)({},e,t)),a.createElement(g,(0,r.Z)({},e,t)))}function w(e){const t=(0,b.Z)();return a.createElement(k,(0,r.Z)({key:String(t)},e))}},263:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>s,default:()=>f,frontMatter:()=>i,metadata:()=>u,toc:()=>p});var r=n(7462),a=(n(7294),n(3905)),o=n(4866),l=n(5162);const i={title:"Introduction & Installation"},s=void 0,u={unversionedId:"domain-event/intro",id:"domain-event/intro",title:"Introduction & Installation",description:"An implementation of domain event pattern",source:"@site/docs/domain-event/00-intro.md",sourceDirName:"domain-event",slug:"/domain-event/intro",permalink:"/domain-event/intro",draft:!1,editUrl:"https://github.com/rekalogika/rekalogika-docs/edit/main/docs/domain-event/00-intro.md",tags:[],version:"current",sidebarPosition:0,frontMatter:{title:"Introduction & Installation"},sidebar:"docs",previous:{title:"rekalogika/domain-event",permalink:"/domain-event/"},next:{title:"Basic Usage",permalink:"/domain-event/basic-usage"}},c={},p=[{value:"Features",id:"features",level:2},{value:"Installation",id:"installation",level:2},{value:"Caveats",id:"caveats",level:2},{value:"License",id:"license",level:2},{value:"Contributing",id:"contributing",level:2}],d={toc:p},m="wrapper";function f(e){let{components:t,...n}=e;return(0,a.kt)(m,(0,r.Z)({},d,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("p",null,"An implementation of ",(0,a.kt)("a",{parentName:"p",href:"https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/domain-events-design-implementation"},"domain event pattern"),"\nfor Symfony & Doctrine."),(0,a.kt)("h2",{id:"features"},"Features"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Works out of the box. No configuration is required."),(0,a.kt)("li",{parentName:"ul"},"Simple, unopinionated architecture. Uses plain event objects, and doesn't\nrequire much from your domain entities."),(0,a.kt)("li",{parentName:"ul"},"Uses standard Symfony's event dispatcher, with the same dispatching semantics\n& listener registrations."),(0,a.kt)("li",{parentName:"ul"},"Three dispatching strategies: pre-flush, post-flush, and immediate."),(0,a.kt)("li",{parentName:"ul"},"In pre-flush or post-flush modes, multiple events considered identical are\ndispatched only once."),(0,a.kt)("li",{parentName:"ul"},"Does not require you to change how you work with entities, most of the time."),(0,a.kt)("li",{parentName:"ul"},"Should work everywhere without any change: in controllers, message handlers,\ncommand line, etc."),(0,a.kt)("li",{parentName:"ul"},"Separated contracts & framework. Useful for enforcing architectural\nboundaries. Your domain doesn't have to depend on the framework.")),(0,a.kt)("h2",{id:"installation"},"Installation"),(0,a.kt)("p",null,"Make sure Composer is installed globally, as explained in the\n",(0,a.kt)("a",{parentName:"p",href:"https://getcomposer.org/doc/00-intro.md"},"installation chapter"),"\nof the Composer documentation."),(0,a.kt)(o.Z,{mdxType:"Tabs"},(0,a.kt)(l.Z,{value:"flex",label:"With Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Open a command console, enter your project directory and execute:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/domain-event\n"))),(0,a.kt)(l.Z,{value:"noflex",label:"Without Symfony Flex",mdxType:"TabItem"},(0,a.kt)("p",null,"Step 1: Download the Bundle"),(0,a.kt)("p",null,"Open a command console, enter your project directory, and execute the\nfollowing command to download the latest stable version of this bundle:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"composer require rekalogika/domain-event\n")),(0,a.kt)("p",null,"Step 2: Enable the Bundle"),(0,a.kt)("p",null,"Then, enable the bundle by adding it to the list of registered bundles\nin the ",(0,a.kt)("inlineCode",{parentName:"p"},"config/bundles.php")," file of your project:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-php",metastring:"title=config/bundles.php",title:"config/bundles.php"},"return [\n // ...\n Rekalogika\\DomainEvent\\RekalogikaDomainEventBundle::class => ['all' => true],\n];\n")))),(0,a.kt)("h2",{id:"caveats"},"Caveats"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Currently only supports ",(0,a.kt)("inlineCode",{parentName:"li"},"EntityManager"),". Support for other ",(0,a.kt)("inlineCode",{parentName:"li"},"ObjectManager"),"s\nis planned."),(0,a.kt)("li",{parentName:"ul"},"It is not aware of explicit transactions yet. You should dispatch the events\nmanually if you are using an explicit transaction as described above. In the\nfuture, we have plans to dispatch post-flush events after the outermost\n",(0,a.kt)("inlineCode",{parentName:"li"},"commit()"),", and dispatch pre-flush events before every ",(0,a.kt)("inlineCode",{parentName:"li"},"commit()"),"."),(0,a.kt)("li",{parentName:"ul"},"It is an inconvenience that Symfony Event Dispatcher does not currently\nsupport event inheritance. We cannot have a single listener for an entire\nclass of domain events, and for example, use it to implement the outbox\npattern. We plan to fix this in the future.")),(0,a.kt)("h2",{id:"license"},"License"),(0,a.kt)("p",null,"MIT"),(0,a.kt)("h2",{id:"contributing"},"Contributing"),(0,a.kt)("p",null,"This framework consists of multiple repositories split from a monorepo. Be\nsure to submit issues and pull requests to the\n",(0,a.kt)("a",{parentName:"p",href:"https://github.com/rekalogika/domain-event-src"},(0,a.kt)("inlineCode",{parentName:"a"},"rekalogika/domain-event-src"))," monorepo."))}f.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.894dae63.js b/assets/js/runtime~main.894dae63.js new file mode 100644 index 00000000..7858c748 --- /dev/null +++ b/assets/js/runtime~main.894dae63.js @@ -0,0 +1 @@ +(()=>{"use strict";var e,t,r,a,f,o={},c={};function n(e){var t=c[e];if(void 0!==t)return t.exports;var r=c[e]={exports:{}};return o[e].call(r.exports,r,r.exports,n),r.exports}n.m=o,e=[],n.O=(t,r,a,f)=>{if(!r){var o=1/0;for(i=0;i=f)&&Object.keys(n.O).every((e=>n.O[e](r[d])))?r.splice(d--,1):(c=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[r,a,f]},n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,n.t=function(e,a){if(1&a&&(e=this(e)),8&a)return e;if("object"==typeof e&&e){if(4&a&&e.__esModule)return e;if(16&a&&"function"==typeof e.then)return e}var f=Object.create(null);n.r(f);var o={};t=t||[null,r({}),r([]),r(r)];for(var c=2&a&&e;"object"==typeof c&&!~t.indexOf(c);c=r(c))Object.getOwnPropertyNames(c).forEach((t=>o[t]=()=>e[t]));return o.default=()=>e,n.d(f,o),f},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce(((t,r)=>(n.f[r](e,t),t)),[])),n.u=e=>"assets/js/"+({6:"5e8cf002",17:"e3fdc92c",43:"fe998a72",53:"935f2afb",130:"4d08d432",138:"94d2cbb6",160:"6cacf3ff",178:"a1516275",181:"efa664a1",195:"c4f5d8e4",349:"27d2408b",388:"3fc97551",425:"5498dd6e",441:"4215e4a8",449:"6e829f05",496:"86f92be2",514:"1be78505",522:"bef88291",529:"e0be7bd3",559:"05b80167",569:"498f3971",645:"c2040271",669:"d39191ae",670:"dae05379",686:"c80f6930",748:"9e75f0a3",769:"39dae2a3",787:"68b66c02",814:"fd37b2f4",862:"e6bf16cc",881:"77356f5a",903:"4ef8bca5",918:"17896441",944:"5060314a",947:"08447aa8"}[e]||e)+"."+{6:"36ef2699",17:"818a69e3",43:"4b733fce",53:"45e5fd75",130:"4ff81050",138:"53dcaadc",160:"ef2782ff",178:"a28c79aa",181:"c0232dd2",195:"a59f34b1",349:"07ed72e3",388:"6319118c",425:"b6935c04",441:"49f2616c",449:"acb0b300",496:"90f5c8a9",514:"e9c25f85",522:"c0487475",529:"39c2c5d7",559:"98de3201",569:"6c11ea81",645:"c77f1c1b",669:"8e461b5f",670:"1635fe86",686:"ee4e4728",748:"e4463abd",769:"55733114",787:"283509d7",814:"9c1a9d35",862:"27af3e37",881:"84518d85",903:"527fde67",918:"815162df",944:"d158853b",947:"798509ca",972:"5058414f"}[e]+".js",n.miniCssF=e=>{},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),a={},f="my-website:",n.l=(e,t,r,o)=>{if(a[e])a[e].push(t);else{var c,d;if(void 0!==r)for(var b=document.getElementsByTagName("script"),i=0;i{c.onerror=c.onload=null,clearTimeout(s);var f=a[e];if(delete a[e],c.parentNode&&c.parentNode.removeChild(c),f&&f.forEach((e=>e(r))),t)return t(r)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=l.bind(null,c.onerror),c.onload=l.bind(null,c.onload),d&&document.head.appendChild(c)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.p="/",n.gca=function(e){return e={17896441:"918","5e8cf002":"6",e3fdc92c:"17",fe998a72:"43","935f2afb":"53","4d08d432":"130","94d2cbb6":"138","6cacf3ff":"160",a1516275:"178",efa664a1:"181",c4f5d8e4:"195","27d2408b":"349","3fc97551":"388","5498dd6e":"425","4215e4a8":"441","6e829f05":"449","86f92be2":"496","1be78505":"514",bef88291:"522",e0be7bd3:"529","05b80167":"559","498f3971":"569",c2040271:"645",d39191ae:"669",dae05379:"670",c80f6930:"686","9e75f0a3":"748","39dae2a3":"769","68b66c02":"787",fd37b2f4:"814",e6bf16cc:"862","77356f5a":"881","4ef8bca5":"903","5060314a":"944","08447aa8":"947"}[e]||e,n.p+n.u(e)},(()=>{var e={303:0,532:0};n.f.j=(t,r)=>{var a=n.o(e,t)?e[t]:void 0;if(0!==a)if(a)r.push(a[2]);else if(/^(303|532)$/.test(t))e[t]=0;else{var f=new Promise(((r,f)=>a=e[t]=[r,f]));r.push(a[2]=f);var o=n.p+n.u(t),c=new Error;n.l(o,(r=>{if(n.o(e,t)&&(0!==(a=e[t])&&(e[t]=void 0),a)){var f=r&&("load"===r.type?"missing":r.type),o=r&&r.target&&r.target.src;c.message="Loading chunk "+t+" failed.\n("+f+": "+o+")",c.name="ChunkLoadError",c.type=f,c.request=o,a[1](c)}}),"chunk-"+t,t)}},n.O.j=t=>0===e[t];var t=(t,r)=>{var a,f,o=r[0],c=r[1],d=r[2],b=0;if(o.some((t=>0!==e[t]))){for(a in c)n.o(c,a)&&(n.m[a]=c[a]);if(d)var i=d(n)}for(t&&t(r);b{"use strict";var e,t,a,r,f,o={},c={};function n(e){var t=c[e];if(void 0!==t)return t.exports;var a=c[e]={exports:{}};return o[e].call(a.exports,a,a.exports,n),a.exports}n.m=o,e=[],n.O=(t,a,r,f)=>{if(!a){var o=1/0;for(b=0;b=f)&&Object.keys(n.O).every((e=>n.O[e](a[d])))?a.splice(d--,1):(c=!1,f0&&e[b-1][2]>f;b--)e[b]=e[b-1];e[b]=[a,r,f]},n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,n.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var f=Object.create(null);n.r(f);var o={};t=t||[null,a({}),a([]),a(a)];for(var c=2&r&&e;"object"==typeof c&&!~t.indexOf(c);c=a(c))Object.getOwnPropertyNames(c).forEach((t=>o[t]=()=>e[t]));return o.default=()=>e,n.d(f,o),f},n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce(((t,a)=>(n.f[a](e,t),t)),[])),n.u=e=>"assets/js/"+({6:"5e8cf002",17:"e3fdc92c",43:"fe998a72",53:"935f2afb",130:"4d08d432",138:"94d2cbb6",160:"6cacf3ff",178:"a1516275",181:"efa664a1",195:"c4f5d8e4",349:"27d2408b",388:"3fc97551",425:"5498dd6e",441:"4215e4a8",449:"6e829f05",496:"86f92be2",514:"1be78505",522:"bef88291",529:"e0be7bd3",559:"05b80167",569:"498f3971",645:"c2040271",669:"d39191ae",670:"dae05379",686:"c80f6930",748:"9e75f0a3",769:"39dae2a3",787:"68b66c02",814:"fd37b2f4",862:"e6bf16cc",881:"77356f5a",903:"4ef8bca5",918:"17896441",944:"5060314a",947:"08447aa8"}[e]||e)+"."+{6:"aad28784",17:"818a69e3",43:"f0b7208d",53:"6812ad13",130:"4ff81050",138:"8cfcc62d",160:"481b774d",178:"7e3129e1",181:"143b4df1",195:"a59f34b1",349:"07ed72e3",388:"6319118c",425:"f363b7f3",441:"9836e004",449:"acb0b300",496:"90f5c8a9",514:"e9c25f85",522:"aa0492ad",529:"1a316765",559:"9f6244af",569:"9a9dfb0e",645:"d257e7b5",669:"9921b83f",670:"1635fe86",686:"eed6e7b5",748:"4d253da4",769:"37e7c2f3",787:"283509d7",814:"f10784d2",862:"633d3b3b",881:"6abaf47c",903:"028366d3",918:"815162df",944:"05bc9e36",947:"1593ff7d",972:"5058414f"}[e]+".js",n.miniCssF=e=>{},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r={},f="my-website:",n.l=(e,t,a,o)=>{if(r[e])r[e].push(t);else{var c,d;if(void 0!==a)for(var i=document.getElementsByTagName("script"),b=0;b{c.onerror=c.onload=null,clearTimeout(s);var f=r[e];if(delete r[e],c.parentNode&&c.parentNode.removeChild(c),f&&f.forEach((e=>e(a))),t)return t(a)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:c}),12e4);c.onerror=l.bind(null,c.onerror),c.onload=l.bind(null,c.onload),d&&document.head.appendChild(c)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.p="/",n.gca=function(e){return e={17896441:"918","5e8cf002":"6",e3fdc92c:"17",fe998a72:"43","935f2afb":"53","4d08d432":"130","94d2cbb6":"138","6cacf3ff":"160",a1516275:"178",efa664a1:"181",c4f5d8e4:"195","27d2408b":"349","3fc97551":"388","5498dd6e":"425","4215e4a8":"441","6e829f05":"449","86f92be2":"496","1be78505":"514",bef88291:"522",e0be7bd3:"529","05b80167":"559","498f3971":"569",c2040271:"645",d39191ae:"669",dae05379:"670",c80f6930:"686","9e75f0a3":"748","39dae2a3":"769","68b66c02":"787",fd37b2f4:"814",e6bf16cc:"862","77356f5a":"881","4ef8bca5":"903","5060314a":"944","08447aa8":"947"}[e]||e,n.p+n.u(e)},(()=>{var e={303:0,532:0};n.f.j=(t,a)=>{var r=n.o(e,t)?e[t]:void 0;if(0!==r)if(r)a.push(r[2]);else if(/^(303|532)$/.test(t))e[t]=0;else{var f=new Promise(((a,f)=>r=e[t]=[a,f]));a.push(r[2]=f);var o=n.p+n.u(t),c=new Error;n.l(o,(a=>{if(n.o(e,t)&&(0!==(r=e[t])&&(e[t]=void 0),r)){var f=a&&("load"===a.type?"missing":a.type),o=a&&a.target&&a.target.src;c.message="Loading chunk "+t+" failed.\n("+f+": "+o+")",c.name="ChunkLoadError",c.type=f,c.request=o,r[1](c)}}),"chunk-"+t,t)}},n.O.j=t=>0===e[t];var t=(t,a)=>{var r,f,o=a[0],c=a[1],d=a[2],i=0;if(o.some((t=>0!==e[t]))){for(r in c)n.o(c,r)&&(n.m[r]=c[r]);if(d)var b=d(n)}for(t&&t(a);i rekalogika/direct-property-access | Rekalogika.DEV - + @@ -12,15 +12,15 @@

rekalogika/direct-property-access

Implementation of Symfony's PropertyAccessorInterface that reads and writes directly to the object's properties, bypassing getters and setters.

Installation

Make sure Composer is installed globally, as explained in the installation chapter -of the Composer documentation.

Open a command console, enter your project directory and execute:

composer require rekalogika/direct-property-access

Usage

In Symfony projects, you can autowire DirectPropertyAccessor. In other projects, you can simply instantiate it.

Read Symfony's PropertyAccess documentation for more information on how to use it. The difference is that DirectPropertyAccessor does not call any of the object's methods, but reads and writes directly to the object's properties, even if they are private.

Caveats

Currently does not support arrays and paths beyond one level deep.

Credits

This project took inspiration from the following projects.

- + \ No newline at end of file diff --git a/domain-event.html b/domain-event.html index 9e062fa0..4b01d3bd 100644 --- a/domain-event.html +++ b/domain-event.html @@ -4,14 +4,14 @@ rekalogika/domain-event | Rekalogika.DEV - + - + \ No newline at end of file diff --git a/domain-event/basic-usage.html b/domain-event/basic-usage.html index 05812bea..edb6e59d 100644 --- a/domain-event/basic-usage.html +++ b/domain-event/basic-usage.html @@ -4,7 +4,7 @@ Basic Usage | Rekalogika.DEV - + @@ -23,11 +23,11 @@ requires the method getSignature(). Two objects with the same signature will be considered identical by DomainEventManager and won't be dispatched twice.

This is useful if your entity is working with a million of related objects. By implementing EquatableDomainEventInterface, you can have your ObjectChanged -event dispatched only once, and occupies only a single spot in the memory, -instead of a million times.

use Rekalogika\Contracts\DomainEvent\EquatableDomainEventInterface;

class PostCommentAdded implements EquatableDomainEventInterface
{
public function __construct(private string $postId)
{
}

public function getSignature(): string
{
return sha1(serialize($this));
}
}

use Rekalogika\Contracts\DomainEvent\DomainEventEmitterInterface;
use Rekalogika\Contracts\DomainEvent\DomainEventEmitterTrait;

class Post implements DomainEventEmitterInterface
{
use DomainEventEmitterTrait;

// ...

public function addComment(string $comment): Comment
{
// ...

// the PostCommentAdded event will only get dispatched once despite of
// addComment being called multiple times.
$this->recordEvent(new PostCommentAdded($this->id));
}
}
note

Equatable domain events only applies to pre-flush and post-flush events. -Immediate domain events are dispatched immediately, and there is no chance for -the equatable check to take place.

- +event dispatched only once and occupy only a single spot in the memory, +instead of a million times.

use Rekalogika\Contracts\DomainEvent\EquatableDomainEventInterface;

class PostCommentAdded implements EquatableDomainEventInterface
{
public function __construct(private string $postId)
{
}

public function getSignature(): string
{
return sha1(serialize($this));
}
}

use Rekalogika\Contracts\DomainEvent\DomainEventEmitterInterface;
use Rekalogika\Contracts\DomainEvent\DomainEventEmitterTrait;

class Post implements DomainEventEmitterInterface
{
use DomainEventEmitterTrait;

// ...

public function addComment(string $comment): Comment
{
// ...

// the PostCommentAdded event will only get dispatched once despite of
// addComment being called multiple times.
$this->recordEvent(new PostCommentAdded($this->id));
}
}
note

Equatable domain events only apply to pre-flush and post-flush events. Immediate +domain events are dispatched immediately, and there is no chance for the +equatable check to take place.

+ \ No newline at end of file diff --git a/domain-event/immediate-dispatcher.html b/domain-event/immediate-dispatcher.html index 658a0aa8..ce892ee9 100644 --- a/domain-event/immediate-dispatcher.html +++ b/domain-event/immediate-dispatcher.html @@ -4,7 +4,7 @@ Immediate Dispatcher Handling & Troubleshooting | Rekalogika.DEV - + @@ -12,11 +12,11 @@

Immediate Dispatcher Handling & Troubleshooting

Immediate Dispatcher in Unit Tests

Immediate event dispatcher works by installing the event dispatcher to a static variable. This installation happens on several opportunities:

  • In these events: kernel.request and console.command.
  • During the initialization of ManagerRegistry.
  • During the initialization of an EntityManagerInterface.

When any of these don't occur, there is no opportunity to install the event dispatcher. This usually happens only in isolated unit tests. To fix the -problem, you can install a stub event dispatcher manually like the following.

use PHPUnit\Framework\TestCase;
use Rekalogika\DomainEvent\ImmediateDomainEventDispatcherInstaller;
use Symfony\Component\EventDispatcher\EventDispatcher;

class SomeTest extends TestCase
{
public function setUp(): void
{
$installer = new ImmediateDomainEventDispatcherInstaller(new EventDispatcher);
$installer->install();

}

// ...
}
note

Stub dispatcher doesn't do anything. If you want to test the dispatching, you -need to get the real dispatcher from the container.

In integration tests where you have access to the service container, but the +problem, you can install a stub event dispatcher manually like the following.

use PHPUnit\Framework\TestCase;
use Rekalogika\DomainEvent\ImmediateDomainEventDispatcherInstaller;
use Symfony\Component\EventDispatcher\EventDispatcher;

class SomeTest extends TestCase
{
public function setUp(): void
{
$installer = new ImmediateDomainEventDispatcherInstaller(new EventDispatcher);
$installer->install();

}

// ...
}
note

The stub dispatcher doesn't do anything. If you want to test the dispatching, +you need to get the real dispatcher from the container.

In integration tests where you have access to the service container, but the tests don't involve EntityManager or ManagerRegistry, you can manually pull the installer from the container to install the immediate dispatcher:

use Rekalogika\DomainEvent\ImmediateDomainEventDispatcherInstaller;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class SomeTest extends KernelTestCase
{
public function setUp(): void
{
self::bootKernel();
static::getContainer()
->get(ImmediateDomainEventDispatcherInstaller::class)->install();
}

// ...
}
- + \ No newline at end of file diff --git a/domain-event/intro.html b/domain-event/intro.html index 07773c32..449d6b46 100644 --- a/domain-event/intro.html +++ b/domain-event/intro.html @@ -4,7 +4,7 @@ Introduction & Installation | Rekalogika.DEV - + @@ -17,7 +17,7 @@ command line, etc.
  • Separated contracts & framework. Useful for enforcing architectural boundaries. Your domain doesn't have to depend on the framework.
  • Installation

    Make sure Composer is installed globally, as explained in the installation chapter -of the Composer documentation.

    Open a command console, enter your project directory and execute:

    composer require rekalogika/domain-event
    - + \ No newline at end of file diff --git a/domain-event/manual-control.html b/domain-event/manual-control.html index e37a2691..b283a06b 100644 --- a/domain-event/manual-control.html +++ b/domain-event/manual-control.html @@ -4,7 +4,7 @@ Manual Control | Rekalogika.DEV - + @@ -25,12 +25,12 @@ catch an exception that previously caused pending events not to be dispatched, you need to manually clear the events.

    Getting the Events in the Queue

    You can get the undispatched events in the queue by calling popDomainEvents().

    /** @var DomainEventAwareEntityManagerInterface $entityManager */

    $events = $entityManager->popDomainEvents();

    This can be useful if you want to dispatch the events in another process, or store them in a database, etc.

    Immediate Dispatcher Installation

    Immediate event dispatcher works by installing the event dispatcher to a static -variable. This installation happens on several opportunities:

    • In these events: kernel.request and console.command.
    • During the initialization of ManagerRegistry.
    • During the initialization of an EntityManagerInterface.

    When any of these don't occur, there is no opportunity to install the event +variable. This installation happens on several opportunities:

    • In these events: kernel.request and console.command.
    • During the initialization of ManagerRegistry.
    • During the initialization of an EntityManagerInterface.

    When none of these occurs, there is no opportunity to install the event dispatcher. This usually happens only in isolated unit tests. To fix the problem, you can install a stub event dispatcher manually like the following.

    use PHPUnit\Framework\TestCase;
    use Rekalogika\DomainEvent\ImmediateDomainEventDispatcherInstaller;
    use Symfony\Component\EventDispatcher\EventDispatcher;

    class SomeTest extends TestCase
    {
    public function setUp(): void
    {
    $installer = new ImmediateDomainEventDispatcherInstaller(new EventDispatcher);
    $installer->install();

    }

    // ...
    }

    In integration tests where you have access to the service container, but the tests don't involve EntityManager or ManagerRegistry, you can pull the installer from the container to install the immediate dispatcher:

    use Rekalogika\DomainEvent\ImmediateDomainEventDispatcherInstaller;
    use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

    class SomeTest extends KernelTestCase
    {
    public function setUp(): void
    {
    self::bootKernel();
    static::getContainer()
    ->get(ImmediateDomainEventDispatcherInstaller::class)->install();
    }

    // ...
    }
    - + \ No newline at end of file diff --git a/domain-event/tips.html b/domain-event/tips.html index 0317237b..76259892 100644 --- a/domain-event/tips.html +++ b/domain-event/tips.html @@ -4,7 +4,7 @@ Tips and Best Practices | Rekalogika.DEV - + @@ -12,12 +12,12 @@

    Tips and Best Practices

    This chapter explains the tips and our best practices that others might find useful, but not strictly required.

    Use UUIDs as Identifiers

    Use UUIDs as entity identifiers & have the entities generate one for themselves on instantiation. That means new entities already have an ID before flush().

    use Symfony\Component\Uid\UuidV7;

    class Post
    {
    private string $id;

    public function __construct(string $title)
    {
    $this->id = new UuidV7();
    }

    // ...
    }

    Therefore, you can reliably store the ID in your event objects, instead of the -object itself. Using the ID in the events mean your events can be realiably -serialized, and you can pass them anywhere without alteration.

    Choosing Dispatching Strategy

    If you want to something similar that you are used to do with application -events, you probably want post-flush strategy.

    Use post-flush for things that should occur only if the change is successful, +object itself. Using the ID in the events means your events can be reliably +serialized, and you can pass them anywhere without alteration.

    Choosing Dispatching Strategy

    If you want to do something similar to what you are used to doing with +application events, you probably want the post-flush strategy.

    Use post-flush for things that should occur only if the change is successful, like notifications, etc.

    Use pre-flush events to make alterations to your domain that will be flush()-ed together along with the other changes.

    - + \ No newline at end of file diff --git a/file-bundle.html b/file-bundle.html index 1c6ce8c3..ff67a0c0 100644 --- a/file-bundle.html +++ b/file-bundle.html @@ -4,13 +4,13 @@ rekalogika/file-bundle | Rekalogika.DEV - + - +
    + \ No newline at end of file diff --git a/file-bundle/creating-filters.html b/file-bundle/creating-filters.html index 0351e8a7..77045164 100644 --- a/file-bundle/creating-filters.html +++ b/file-bundle/creating-filters.html @@ -4,7 +4,7 @@ Creating Filters | Rekalogika.DEV - + @@ -16,7 +16,7 @@ the abstract methods.

    The following is an example filter class that creates a derived file by (rather uselessly) appending a text to the original content:

    use Rekalogika\Contracts\File\FileInterface;
    use Rekalogika\File\Derivation\Filter\AbstractFileFilter;
    use Rekalogika\File\TemporaryFile;

    class TextAppender extends AbstractFileFilter
    {
    private string $text;

    /**
    * Your custom method that lets the caller specify the filtering parameters.
    */
    public function appendText(string $text): self
    {
    assert(ctype_alpha($text)); // ensure alpha characters only
    $this->text = $text;

    return $this;
    }

    /**
    * This method return the derivation ID from the filtering parameters the
    * caller provided.
    */
    #[\Override]
    protected function getDerivationId(): string
    {
    return 'append_' . $this->text;
    }

    #[\Override]
    protected function process(): FileInterface
    {
    $originalContent = $this->getSourceFile()->getContent();

    return new TemporaryFile::createFromString($originalContent . $this->text);
    }
    }

    If you are using autoconfiguration, then you are all set. Otherwise, you need to tag your class with rekalogika.file.derivation.filter:

    config/services.yaml
    services:
    App\TextAppender:
    tags:
    - { name: 'rekalogika.file.derivation.filter' }

    A caller will be able to use the above filter like the following:

    use Rekalogika\Contracts\File\FileInterface;

    /** @var TextAppender $textAppender */
    /** @var FileInterface $file */

    $derivedFile = $textAppender
    ->take($file)
    ->appendText('foo')
    ->getResult();
    - + \ No newline at end of file diff --git a/file-bundle/doctrine-entity.html b/file-bundle/doctrine-entity.html index 44763021..8df1f25d 100644 --- a/file-bundle/doctrine-entity.html +++ b/file-bundle/doctrine-entity.html @@ -4,7 +4,7 @@ Associating Files with Doctrine Entities | Rekalogika.DEV - + @@ -13,7 +13,7 @@ you can use to associate a file to an entity, including to store the result of a file upload.

    Preparation

    To enable this feature, you need to install the package rekalogika/file-association:

    composer require rekalogika/file-association

    Creating a File Property in an Entity

    To create a file property in an entity that will be managed by this framework, -you need to:

    1. Create a property that accept a FileInterface.
    2. Add the attribute #[WithFileAssociation] to the class.
    3. Add the attribute #[AsFileAssociation] to the property.
    caution

    The framework assumes that it can get the ID of the entity by calling the method +you need to:

    1. Create a property that accepts a `FileInterface``.
    2. Add the attribute #[WithFileAssociation] to the class.
    3. Add the attribute #[AsFileAssociation] to the property.
    caution

    The framework assumes that it can get the ID of the entity by calling the method getId(). If your entity uses a different mechanism, you need to implement ObjectIdResolverInterface. See the chapter Creating Object ID Resolver for more information.

    use Rekalogika\Contracts\File\FileInterface;
    use Rekalogika\File\Association\Attribute\WithFileAssociation;
    use Rekalogika\File\Association\Attribute\AsFileAssociation;
    use Rekalogika\File\File;

    #[WithFileAssociation]
    class Product
    {
    /**
    * The file property must accept a FileInterface
    */
    #[AsFileAssociation]
    private ?FileInterface $image = null;

    /**
    * The framework needs the ID of the entity. By default, it will call getId()
    * of the object to get the ID. If your entity doesn't use getId(), read
    * the next section.
    */
    public function getId(): string
    {
    return $this->id;
    }

    //
    // The rest of this class is inconsequential to the framework
    //

    /**
    * This framework reads and writes directly to the properties, even if
    * private. Therefore, you are free to have your own business logic in the
    * getters and setters.
    */
    public function getImage(): FileInterface
    {
    if (date('m-d') == '04-01') { // if today is april 1st
    return new File('shock-image.jpg'); // april fools!
    }

    return $this->image;
    }

    public function setImage(?FileInterface $image): self
    {
    if ($this->status == 'published') {
    throw new \Exception("Cannot change a published product's image");
    }

    $this->image = $image;

    return $this;
    }
    }

    Working With Entities & Files

    You can work with the entities and associated files as usual, and they will work @@ -25,7 +25,7 @@ Form, and Validator, please read the chapter Symfony Integration.

    (alternative) Entity Setup Using Interface

    As an alternative to using attributes to mark the file property above, you can also have your entity implement FileAssociationInterface:

    use Rekalogika\Contracts\File\FileInterface;
    use Rekalogika\Contracts\File\Association\FileAssociationInterface;

    class Product implements FileAssociationInterface
    {
    /**
    * The file properties must accept FileInterface
    */
    private ?FileInterface $image = null;

    /**
    * This method gives the list of properties of this class that will
    * be managed by this framework. In this case it tells us that the
    * property 'image' is a file property we need to manage.
    */
    public static function getFileAssociationPropertyList(): array
    {
    return ['image'];
    }

    /**
    * The framework needs the ID of the entity. By default, it will call getId()
    * of the object to get the ID.
    */
    public function getId(): string
    {
    return $this->id;
    }

    //
    // The rest of this class is inconsequential to the framework
    //

    /**
    * This framework reads and writes directly to the properties, even if
    * private. Therefore, you are free to have your own business logic in the
    * getters and setters.
    */
    public function getImage(): FileInterface
    {
    if (date('m-d') == '04-01') { // if today is april 1st
    return new File('shock-image.jpg'); // april fools!
    }

    return $this->image;
    }

    public function setImage(?FileInterface $image): self
    {
    if ($this->status == 'published') {
    throw new \Exception("Cannot change a published product's image");
    }

    $this->image = $image;

    return $this;
    }
    }
    note

    We recommend using attributes instead of implementing the interface. But currently using attributes can be less performant than using the interface.

    - + \ No newline at end of file diff --git a/file-bundle/entity-association-internal.html b/file-bundle/entity-association-internal.html index 7e0ad10e..b12c6db4 100644 --- a/file-bundle/entity-association-internal.html +++ b/file-bundle/entity-association-internal.html @@ -4,20 +4,20 @@ File Association Internal Details | Rekalogika.DEV - +

    File Association Internal Details

    Where The Files Are Stored

    FileLocationResolverInterface decides where to store the file. It takes the -entity instance and the name of the property holding the file, and outputs a +entity instance and the name of the property holding the file and outputs a FilePointer describing where the file in that property will be stored. The default implementation DefaultFileLocationResolver stores files into the filesystem with the identifier 'default' and the key similar to the following:

    entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337
    ╰----╯ ╰--------------------------------------╯ ╰---╯ ╰---------╯ ╰---╯
    A B C D E
    • A: Prefix, defaults to 'entity'.
    • B: SHA-1 hash of the entity's fully-qualified class name.
    • C: Property name.
    • D: Hashed directories of the entity's ID. The ID is hashed using SHA-1, then -splitted by 2 characters each. Then, the first four of them are taken to form +split by 2 characters each. Then, the first four of them are taken to form the directory structure.
    • E: The entity ID.

    This default should be sufficient in most cases, for all entities, and all filesystems. It masks internal details (entity class names). It does not pile -too many files in one directory (some filesystems struggle with huge amount of +too many files in one directory (some filesystems struggle with a huge amount of files in a directory). The ordering is chosen to make it easier for manual administration tasks.

    To obtain the entity's ID, DefaultFileLocationResolver calls ObjectIdResolverInterface. By default, it is DefaultObjectIdResolver which @@ -25,13 +25,13 @@ either FileLocationResolverInterface or ObjectIdResolverInterface. If you are using autoconfiguration, then you are good to go. Otherwise, you need to tag them in the service container:

    services:
    App\MyFileLocationResolver:
    tags:
    - { name: 'rekalogika.file.association.file_location_resolver' }
    App\MyObjectIdResolver:
    tags:
    - { name: 'rekalogika.file.association.object_id_resolver' }

    About File Names

    Like modern key-value cloud storage services, this framework uses the concept of -'keys', not 'paths'. The file name is not part of the key, but stored in the +'keys', not 'paths'. The file name is not part of the key but is stored in the metadata, along with other properties of the file. The original file name is never taken into consideration when determining where to store the file.

    The metadata itself is stored in a sidecar file. Using the example above, the metadata will be stored in this location:

    entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337.metadata

    The caller can obtain the file name using the appropriate methods:

    $imageFilename = $entity->getImage()?->getName();

    When possible, the framework should have copied the file name of the original file to the destination metadata when the file was first associated with the entity.

    How It Works

    The storage key of the file is deterministic. It is determined only by the -object's class name, the object's ID and the name of the property containing the +object's class name, the object's ID, and the name of the property containing the file. As long as those don't change, the key will remain the same.

    When persisting an entity, the framework will calculate the destination storage key of every applicable property of the entity, and compare it to the current file residing on each property:

    • If both are the same, the framework leaves it alone.
    • If they are different, the framework will copy the file from the entity to the @@ -39,13 +39,13 @@ irrespective of whether the file exists or not.

    Architecture

    In a nutshell: Doctrine Unit Of Work ➡️ Doctrine Events ➡️ rekalogika/reconstitutor ➡️ InterfaceReconstitutor & AttributeReconstitutor ➡️ FileAssociationManager ➡️ FileRepository (from rekalogika/file).

    InterfaceReconstitutor & AttributeReconstitutor are the entry points of this -package. They execute methods of FileAssociationManager which work with the +package. They execute methods of FileAssociationManager which works with the entities and FileRepository to manage the association between the entities and files.

    InterfaceReconstitutor & AttributeReconstitutor are registered to the service container so that they are called by our rekalogika/reconstitutor when the relevant events are being emitted by Doctrine. The service configuration is done by the package rekalogika/file-bundle.

    - + \ No newline at end of file diff --git a/file-bundle/filtering.html b/file-bundle/filtering.html index 017a0620..f51bbb12 100644 --- a/file-bundle/filtering.html +++ b/file-bundle/filtering.html @@ -4,7 +4,7 @@ Filtering | Rekalogika.DEV - + @@ -13,7 +13,7 @@ derived files from a source file. If a filter is applied to a FileInterface, one of these things happens:

    1. If the derived file does not exist, process the source and create the derived file, then save it.
    2. If the derived file exists and newer than the source file, return the -already made derived file.
    3. If the derived file exists and older than the source file, then it is stale, +existing derived file.
    4. If the derived file exists and older than the source file, then it is stale, the filter will create a derivation out of the source file, then overwrite the old derived file.

    Currently, there is only one filter available, ImageResizer.

    ImageResizer

    Preparation

    You need to install the package rekalogika/file-image to use this feature:

    composer require rekalogika/file-image

    PHP Usage

    In PHP files, you need to inject the ImageResizer class to your service or controller:

    use Rekalogika\File\Image\ImageResizer;
    use Rekalogika\Contracts\File\FileInterface;

    /** @var ImageResizer $imageResizer */
    /** @var FileInterface $image */

    $resizedImage = $imageResizer
    ->take($image)
    ->resize(100, ImageResizer::ASPECRATIO_SQUARE)
    ->getResult();

    The first time it is called, the filter will create a 100-pixel-square-cropped @@ -22,7 +22,7 @@ overwrite the old one.

    Twig Usage

    In Twig templates, you can use the image_resize filter. For example:

    <img src="{{ image_file|image_resize(100, 'square')|temporary_url }}" />

    The example above will give us a temporary URL to a square-cropped image with a maximum width or height of 100 pixels from the original image image_file.

    - + \ No newline at end of file diff --git a/file-bundle/installation.html b/file-bundle/installation.html index d2a7bb79..4abf5c30 100644 --- a/file-bundle/installation.html +++ b/file-bundle/installation.html @@ -4,7 +4,7 @@ Installation & Configuration | Rekalogika.DEV - + @@ -13,13 +13,13 @@ Symfony applications. If you are not using Symfony, please refer to the rekalogika/file documentation instead.

    Installation

    Make sure Composer is installed globally, as explained in the installation chapter -of the Composer documentation.

    Open a command console, enter your project directory and execute:

    composer require rekalogika/file-bundle
    - + \ No newline at end of file diff --git a/file-bundle/intro.html b/file-bundle/intro.html index 562c3d28..76c9c710 100644 --- a/file-bundle/intro.html +++ b/file-bundle/intro.html @@ -4,7 +4,7 @@ Introduction | Rekalogika.DEV - + @@ -13,10 +13,10 @@ packages within a Symfony application.

    Entity Association Features

    • Requires only a single property in the entity for each associated file.
    • File properties are file properties. It is not necessary to store any of the file's properties in the entity associated with the file.
    • DX improvement, less micro-management of entity-file relations.
    • Reads and writes directly into the file properties, even if private. You are free to have business logic in the getters and setters.
    • Doesn't require you to update another property of the entity (lastUpdated) -just to make sure the correct Doctrine events will be fired.

    License

    MIT

    Contributing

    This framework consists of multiple repositories splitted from a monorepo. Be -sure to submit issues and pull request to the +just to make sure the correct Doctrine events will be fired.

    License

    MIT

    Contributing

    This framework consists of multiple repositories split from a monorepo. Be +sure to submit issues and pull requests to the rekalogika/file-src monorepo.

    - + \ No newline at end of file diff --git a/file-bundle/object-id-resolver.html b/file-bundle/object-id-resolver.html index 749985f4..0660aaa4 100644 --- a/file-bundle/object-id-resolver.html +++ b/file-bundle/object-id-resolver.html @@ -4,7 +4,7 @@ Creating an Object ID Resolver | Rekalogika.DEV - + @@ -13,12 +13,12 @@ getId(). If your entity uses a different mechanism, you need to create an implementation of ObjectIdResolverInterface.

    Protip

    You can have multiple implementations of ObjectIdResolverInterface in your application. The framework will use the first one that returns a value.

    info

    If you have a custom implementation of ObjectIdResolverInterface, the default -implementation is still active, but has a lower priority than your custom +implementation is still active but has a lower priority than your custom implementation.

    If Your Entity Simply Uses a Different Method

    If your entity simply uses a different method name, you can reuse the default implementation of ObjectIdResolverInterface:

    config/services.yaml
    services:
    app.object_id_resolver:
    class: 'Rekalogika\File\Association\ObjectIdResolver\DefaultObjectIdResolver'
    args:
    - 'getIdentifier' # put the method name here
    tags:
    - { name: 'rekalogika.file.association.object_id_resolver' }
    note

    DefaultObjectIdResolver can handle return types of string, int, and Stringable.

    If It Is More Complicated Than That

    Then you need to create your own implementation of ObjectIdResolverInterface.

    use Rekalogika\Contracts\File\Association\ObjectIdResolverInterface;

    class MyObjectIdResolver implements ObjectIdResolverInterface
    {
    public function getObjectId(object $object): string
    {
    // your implementation here
    }
    }

    If you are using autoconfiguration, then it is all set. If not, you need to register your class in the service container:

    config/services.yaml
    services:
    App\MyObjectIdResolver:
    tags:
    - { name: 'rekalogika.file.association.object_id_resolver' }
    - + \ No newline at end of file diff --git a/file-bundle/serving-files.html b/file-bundle/serving-files.html index 12d429f0..da4b61be 100644 --- a/file-bundle/serving-files.html +++ b/file-bundle/serving-files.html @@ -3,15 +3,15 @@ -Serving Files | Rekalogika.DEV - +Serving Files | Rekalogika.DEV +
    -

    Serving Files

    This chapter describes how to serve files to client web browser.

    Streaming Files in a Symfony Controller

    Preparation

    You need to install the package rekalogika/file-symfony-bridge to use this -feature:

    composer require rekalogika/file-symfony-bridge

    To send a file to client web browser, you can use FileResponse:

    use Rekalogika\File\Bridge\Symfony\HttpFoundation\FileResponse;
    use Rekalogika\Contracts\File\FileInterface;
    use Symfony\Component\HttpFoundation\Response;

    class SomeController
    {
    public function download(): Response
    {
    /** @var FileInterface $file */
    $file = ...;

    return new FileResponse($file);
    }
    }

    Generate a Temporary URL to a File

    Rather than creating a controller action to serve a file for every possible -situations, it is more convenient to generate a temporary URL to a file.

    Preparation

    You need to install the package rekalogika/file-server to use this feature:

    composer require rekalogika/file-server

    If you are not using Symfony Flex, read the documentation of +

    Serving Files

    This chapter describes how to serve files to the client web browser.

    Streaming Files in a Symfony Controller

    Preparation

    You need to install the package rekalogika/file-symfony-bridge to use this +feature:

    composer require rekalogika/file-symfony-bridge

    To send a file to the web browser, you can use FileResponse:

    use Rekalogika\File\Bridge\Symfony\HttpFoundation\FileResponse;
    use Rekalogika\Contracts\File\FileInterface;
    use Symfony\Component\HttpFoundation\Response;

    class SomeController
    {
    public function download(): Response
    {
    /** @var FileInterface $file */
    $file = ...;

    return new FileResponse($file);
    }
    }

    Generate a Temporary URL to a File

    Rather than creating a controller action to serve a file for every possible +situation, it is more convenient to generate a temporary URL to a file.

    Preparation

    You need to install the package rekalogika/file-server to use this feature:

    composer require rekalogika/file-server

    If you are not using Symfony Flex, read the documentation of rekalogika/file-bundle and rekalogika/temporary-url-bundle to learn how to register the required bundles.

    PHP Usage

    Wire in the TemporaryUrlGeneratorInterface service, and use the generateUrl() method to generate a temporary URL to a file. It accepts either @@ -20,7 +20,7 @@ the image_resize filter from the rekalogika/file-image package.

    <img src="{{ my_image|image_resize(200)|temporary_url }}" />

    More Information

    The generateUrl() method and the temporary_url Twig filter accept several options. Read the documentation of rekalogika/temporary-url-bundle to learn more.

    - + \ No newline at end of file diff --git a/file-bundle/symfony.html b/file-bundle/symfony.html index 8f1cb211..41a75fa3 100644 --- a/file-bundle/symfony.html +++ b/file-bundle/symfony.html @@ -4,7 +4,7 @@ Symfony Integration | Rekalogika.DEV - + @@ -12,8 +12,8 @@

    Symfony Integration

    This chapter describes how to integrate this framework with the typical Symfony components used to work with files.

    Preparation

    To enable this feature, you need to install the package rekalogika/file-symfony-bridge:

    composer require rekalogika/file-symfony-bridge

    Components Summary

    • Adapters to convert HttpFoundation File objects to a FileInterface and -vice versa, with special handling for UploadedFile.
    • FileResponse for streaming a FileInterface to client web browser.
    • FileType form that works with FileInterface objects.
    • A form transformer FileTransformer that you can add to an existing Symfony -FileType fields so that it gives us a FileInterface instead of an +vice versa, with special handling for UploadedFile.
    • FileResponse for streaming a FileInterface to the client web browser.
    • FileType form that works with FileInterface objects.
    • A form transformer FileTransformer that you can add to an existing Symfony +FileType fields so that it gives us a FileInterface instead of a UploadedFile object.
    • A form extension FileTypeExtension that you can optionally register to automatically convert all the existing Symfony FileType so they all give us a FileInterface.
    • Subclassed FileValidator and ImageValidator that works with @@ -30,7 +30,7 @@ File and Image validators, except that they work with FileInterface objects instead of HttpFoundation File objects:

      use Rekalogika\Contracts\File\FileInterface;
      use Rekalogika\File\Bridge\Symfony\Constraints\File as FileConstraint;
      use Rekalogika\File\Bridge\Symfony\Constraints\Image as ImageConstraint;

      class Product
      {
      #[ImageConstraint(minWidth: '1000']
      private ?FileInterface $photo = null;

      #[ImageConstraint(maxSize: '10000k']
      private ?FileInterface $manual = null;

      // ...
      }
      caution

      Due to how the adapters work, some validator functions might not work correctly, like those that check file names.

    - + \ No newline at end of file diff --git a/file.html b/file.html index 3faf477a..3b4f2a36 100644 --- a/file.html +++ b/file.html @@ -4,7 +4,7 @@ rekalogika/file | Rekalogika.DEV - + @@ -12,8 +12,8 @@
    - +the library lets you work with them in the same way.

    + \ No newline at end of file diff --git a/file/adapters.html b/file/adapters.html index 105f84c9..06da674a 100644 --- a/file/adapters.html +++ b/file/adapters.html @@ -4,7 +4,7 @@ Adapters | Rekalogika.DEV - + @@ -13,7 +13,7 @@ a file object from another library to our FileInterface.

    use Rekalogika\File\FileAdapter;
    use Rekalogika\File\FileInterface;

    // $theirFile is any of the supported file object

    $ourFile = FileAdapter::adapt($theirFile);
    assert($ourFile instanceof FileInterface);

    Currently supported objects:

    • string: assumed to be a path to a local file
    • PHP's SplFileInfo
    • Symfony HttpFoundation File (and descendants, including the ubiquitous UploadedFile). Requires the rekalogika/file-symfony-bridge package.
    • FileInterface of OneupUploaderBundle. Requires the rekalogika/file-oneup-uploader-bridge package.
    - + \ No newline at end of file diff --git a/file/concepts.html b/file/concepts.html index ea95998c..ea31ff92 100644 --- a/file/concepts.html +++ b/file/concepts.html @@ -4,27 +4,28 @@ Concepts & Terms | Rekalogika.DEV - +
    -

    Concepts & Terms

    Terms

    • FileRepository: manages files in framework, implements -FileRepositoryInterface.
    • File: a file in a Flysystem filesystem, implements FileInterface. Each -file is identified by a filesystem identifier and a key. Null filesystem -identifier denotes that the file is in the local filesystem.
    • FilePointer: a pointer to a file, implements FilePointerInterface. Like +

      Concepts & Terms

      Terms

      • FileRepository: Manages files in the framework. Implements +FileRepositoryInterface.
      • File: A file in a Flysystem filesystem. Implements FileInterface. Each +file is identified by a filesystem identifier and a key. A null filesystem +identifier denotes that the file is in the local filesystem.
      • FilePointer: A pointer to a file. Implements FilePointerInterface. Like a file, a file pointer has a filesystem identifier and a key, but nothing -else.
      • Filesystem: a Flysystem filesystem, implements Flysystem's -FilesystemOperator. The caller should not use it directly, but use the -FileRepository instead.
      • Local filesystem: a special Flysystem filesystem initialized by the -framework that points to unscoped local filesystem, using '/' as its root -location.
      info

      A Flysystem filesystem using LocalFilesystemAdapter that is setup by -the user is not considered a local filesystem in this document.

      Class Diagram

      note

      'Interface' in the names are stripped for brevity. Simple getters are represented by properties.

      File classesFile classes

      Keys vs Paths

      The library encourages using the concept of 'keys', not 'paths'. Although the +else.

    • Filesystem: A Flysystem filesystem. Implements Flysystem's +FilesystemOperator. The caller should not use it directly but use the +FileRepository instead.
    • Local filesystem: A special Flysystem filesystem initialized by the +framework that points to an unscoped local filesystem, using '/' as its root +location.
    info

    A Flysystem filesystem using LocalFilesystemAdapter that is set up by the user +is not considered a local filesystem in this document.

    Class Diagram

    note

    'Interface' in the names are stripped for brevity. Simple getters are +represented by properties.

    File classesFile classes

    Keys vs Paths

    The library encourages using the concept of 'keys', not 'paths'. Although the key can appear similar to a path, the main difference is that the filename is not part of the key, but part of the file's metadata. The key is similar to the primary key of a database table. You can change the 'name' field, but the ID usually stays the same.

    - + \ No newline at end of file diff --git a/file/derivation.html b/file/derivation.html index f5cc099b..91a44a0f 100644 --- a/file/derivation.html +++ b/file/derivation.html @@ -3,15 +3,15 @@ -Derivation | Rekalogika.DEV - +Derivation | Rekalogika.DEV +
    -

    Derivation

    This chapter describes the concept of file derivation and the pipe & filters +

    Derivation

    This chapter describes the concept of file derivation and the pipe & filter pattern applied to FileInterface.

    Derivation

    FileInterface supports what we call 'derivation'. A file can have one or more -derivation of itself. For example, an image file can have a thumbnail, medium, +derivations of itself. For example, an image file can have a thumbnail, medium, and large derivation. A derived file can also be derived further. For example, a thumbnail can be in the original aspect ratio, or square-cropped.

    FileInterface provides the method getDerivation() that returns a FilePointer to the derived file. Our File objects ensure that a derivation @@ -21,16 +21,16 @@ derivations.

    Derivation can be nested. Suppose the derived file above will be derived further with the derivation ID of 'square', then the derived file's key becomes:

    entity/ffa87ef3fc5388bc8b666e2cec17d27cc493d0c1/image/e5/80/72/6d/31337.d/100px.d/square
    caution

    Because each derivation step requires a round trip to the storage backend, it is not recommended to nest derivations too deep.

    Pipes & Filters Pattern

    Derivation can be used as the building block of filters. A filter is a service -that perform opportunistic creation and caching of a derived file from a source +that performs opportunistic creation and caching of a derived file from a source file.

    A filter can be applied to a FileInterface and does the following:

    1. Obtain the original file.
    2. Determine the derivation ID from the parameters provided by the caller. For example, if the caller wants to get a square thumbnail of an image, the filter can use the derivation ID like 'thumbnail-square'.
    3. Call FileInterface::getDerivation() to get a pointer to the derived file.
    4. Call FileRepository::get() to get the derived file.
      1. If the derived file does not exist, produce the derived file, and write to -the pointer.
      2. If the derived file exists and newer than the original file, return it.
      3. If the derived file exists and older than the original file, produce the -derived file, then overwrite the old derived file.

    The caller can then use the filter to create a modified version of the original +the pointer.

  • If the derived file exists and is newer than the original file, return it.
  • If the derived file exists and is older than the original file, produce +the derived file, then overwrite the old derived file.
  • The caller can then use the filter to create a modified version of the original file without having to worry about the details.

    We provide the package rekalogika/file-derivation to streamline the creation of filters within the Symfony framework.

    - + \ No newline at end of file diff --git a/file/file.html b/file/file.html index a36315f4..027570ac 100644 --- a/file/file.html +++ b/file/file.html @@ -4,20 +4,20 @@ Using File & FileRepository | Rekalogika.DEV - +

    Using File & FileRepository

    When using this framework, the user will primarily work with the FileRepositoryInterface and FileInterface objects.

    Working With the File Repository

    Create a file

    caution

    These methods overwrite the existing file if it already exists.

    use Rekalogika\Contracts\File\FileRepositoryInterface;
    use Rekalogika\File\FilePointer;

    /** @var FileRepositoryInterface $fileRepository */

    // Create a file from a string
    $file = $fileRepository->createFromString(
    new FilePointer('default', 'key'),
    'Hello World!'
    );

    // Create a file from a stream (resource or PSR-7 StreamInterface)
    $file = $fileRepository->createFromStream(
    new FilePointer('default', 'key'),
    $stream
    );

    // Create a file from a local file
    $file = $fileRepository->createFromLocalFile(
    new FilePointer('default', 'key'),
    '/tmp/foo.txt'
    );

    Get a file

    use Rekalogika\Contracts\File\FileRepositoryInterface;
    use Rekalogika\Contracts\File\Exception\File\FileNotFoundException;
    use Rekalogika\File\FilePointer;

    /** @var FileRepositoryInterface $fileRepository */

    // get() will throw an exception if the file is not found
    try {
    $file = $fileRepository->get(new FilePointer('default', 'key'));
    } catch (FileNotFoundException $e) {
    // File not found
    }

    // tryGet() will return null if the file is not found
    $file = $fileRepository->tryGet(new FilePointer('default', 'key'));

    // With a local file, you can also do it without using file repository:
    try {
    $file = new File('/path/to/file');
    } catch (FileNotFoundException $e) {
    // File not found
    }

    Delete a file

    use Rekalogika\Contracts\File\FileRepositoryInterface;
    use Rekalogika\File\FilePointer;

    /** @var FileRepositoryInterface $fileRepository */

    $fileRepository->delete(new FilePointer('default', 'key'));

    Copy and move a file

    use Rekalogika\Contracts\File\FileRepositoryInterface;
    use Rekalogika\File\FilePointer;

    /** @var FileRepositoryInterface $fileRepository */

    $newFile = $fileRepository->copy(
    new FilePointer('default', 'key'),
    new FilePointer('otherfilesystem', 'destinationkey')
    );

    $newFile = $fileRepository->move(
    new FilePointer('default', 'key'),
    new FilePointer('otherfilesystem', 'destinationkey')
    );
    tip

    You can also use a FileInterface as the origin or the destination -of the move or copy operation.

    Create a temporary file

    use Rekalogika\Contracts\File\FileRepositoryInterface;

    /** @var FileRepositoryInterface $fileRepository */

    $file = $fileRepository->createTemporaryFile();
    note

    The temporary file is represented by special TemporaryFile that will be +of the move or copy operation.

    Create a temporary file

    use Rekalogika\Contracts\File\FileRepositoryInterface;

    /** @var FileRepositoryInterface $fileRepository */

    $file = $fileRepository->createTemporaryFile();
    note

    The temporary file is represented by a special TemporaryFile that will be automatically deleted if it is unset or falls out of scope.

    Working With a File

    Reading the file's content

    use Rekalogika\Contracts\File\FileInterface;

    /** @var FileInterface $file */

    // As a string
    $string = $file->getContent();

    // As a stream
    $stream = $file->getContentAsStream();

    // getContentAsStream() returns a PSR-7 StreamInterface, to get a plain PHP
    // resource, call detach() on it
    $resource = $stream->detach();

    Writing to the file, replacing its content

    use Rekalogika\Contracts\File\FileInterface;

    /** @var FileInterface $file */

    // From a string
    $file->setContent('Hello World!');

    // From a stream or resource
    $file->setContentFromStream($resource);

    Renaming the file

    use Rekalogika\Contracts\File\FileInterface;

    /** @var FileInterface $file */

    $file->setName('my-photo.jpg');

    // If you omit the extension, the library will automatically choose the correct
    // extension based on the file's MIME type

    $file->setName('my-photo');
    $name = (string) $file->getName(); // my-photo.jpg

    // If you absolutely don't want an extension, you can set it directly to the
    // metadata

    $file->get(FileMetadataInterface::class)->setFileName('my-photo');
    $file->flush();

    // getName() returns FileNameInterface that provides several convenient methods
    // to get information about the filename

    $file->setName('foo.png');

    $name = (string) $file->getName(); // foo.png
    $fullName = $file->getName()->getFull(); // foo.png
    $baseName = $file->getName()->getBase(); // foo
    $extension = $file->getName()->getExtension(); // png
    $hasExtension = $file->getName()->hasExtension(); // true

    Saving to a local file

    use Rekalogika\Contracts\File\FileInterface;

    /** @var FileInterface $file */

    // Saves the file to /tmp/foo.txt
    $localFile = $file->saveToLocalFile('/tmp/foo.txt');

    // Saves the file to a temporary file
    $temporaryFile = $file->createLocalTemporaryFile();

    Media type (MIME type) handling

    use Rekalogika\Contracts\File\FileInterface;

    /** @var FileInterface $file */

    // Setting the MIME type is usally not necessary as the framework will
    // automatically detect media type
    $file->setType('image/jpeg'); // sets the media type to image/jpeg

    $type = (string) $file->getType(); // image/jpeg
    $type = $file->getType()->getName(); // image/jpeg
    $type = $file->getType()->getType(); // image
    $type = $file->getType()->getSubType(); // jpeg
    $type = $file->getType()->getCommonExtensions(); // ['jpg', 'jpeg', 'jpe']
    $type = $file->getType()->getExtension(); // jpg
    $type = (string) $file->getType()->getDescription(); // JPEG image

    File size & last modified time

    use Rekalogika\Contracts\File\FileInterface;

    /** @var FileInterface $file */

    // Main metadata
    $size = $file->getSize(); // file size in bytes
    $lastModified = $file->getLastModified(); // last modified time

    Image metadata

    use Rekalogika\Contracts\File\FileInterface;
    use Rekalogika\Contracts\File\Metadata\ImageMetadataInterface;

    /** @var FileInterface $file */

    $width = $file->get(ImageMetadataInterface::class)?->getWidth();
    $height = $file->get(ImageMetadataInterface::class)?->getHeight();

    // You can also use string identifiers, useful when specifying FQCNs is
    // unwieldy, like in Twig templates

    $width = $file->get('imageMetadata')?->getWidth();
    $height = $file->get('imageMetadata')?->getHeight();

    HTTP metadata

    use Rekalogika\Contracts\File\FileInterface;
    use Rekalogika\Contracts\File\Metadata\HttpMetadataInterface;

    /** @var FileInterface $file */

    // Setting the disposition value, will be used in the Content-Disposition header
    // when the file is downloaded
    $file->get(HttpMetadataInterface::class)?->setDisposition('attachment');
    $file->flush();

    // Getting all the HTTP headers that will be used when the file is downloaded
    $httpHeaders = $file->get(HttpMetadataInterface::class)?->getHeaders();

    Flushing metadata

    Updating metadata using a high-level method (those on FileInterface) will be saved automatically. But using a low-level method (under FileInterface::get()), you have to call flush() manually. You can take advantage of this so that multiple metadata updates are saved in a single round trip.

    use Rekalogika\Contracts\File\FileInterface;
    use Rekalogika\Contracts\File\Metadata\HttpMetadataInterface;

    /** @var FileInterface $file */

    // Each of the following will be flush automatically individually, and will
    // require two roundtrips to the storage backend
    $file->setType('image/jpeg');
    $file->setName('foo.jpg');

    // The following needs an explicit flush(). It will only require one roundtrip
    // to the storage backend.
    $file->get(FileMetadataInterface::class)?->setType('image/jpeg');
    $file->get(FileMetadataInterface::class)?->setName('foo.jpg');
    $file->flush();

    File Pointer & comparison

    use Rekalogika\Contracts\File\FileInterface;

    /** @var FileInterface $file */

    // get pointer from a FileInterface
    $filePointer = $file->getPointer();

    // determine if two File/FilePointer objects point to the same file
    $isEqual = $filePointer->isEqualTo($file);
    $isEqual = $file->isEqualTo($filePointer);
    $isEqual = $file1->isEqualTo($file2);
    $isEqual = $filePointer1->isEqualTo($filePointer2);
    - + \ No newline at end of file diff --git a/file/installation.html b/file/installation.html index bd2631bc..14a72fb4 100644 --- a/file/installation.html +++ b/file/installation.html @@ -4,7 +4,7 @@ Installation & Configuration | Rekalogika.DEV - + @@ -15,10 +15,10 @@ rekalogika/file-bundle for more information.

    Installation

    Make sure Composer is installed globally, as explained in the installation chapter -of the Composer documentation.

    Open a command console, enter your project directory and execute:

    composer require rekalogika/file-bundle

    Initialization

    In your application, initialize the file repository like the following example:

    use Rekalogika\File\FileFactory;
    use League\Flysystem\Filesystem;
    use League\Flysystem\Local\LocalFilesystemAdapter;

    $fileFactory = new FileFactory(
    filesystems: [
    'default' => new Filesystem(new LocalFilesystemAdapter('/var/storage')),
    ]
    );

    /** @var FileRepositoryInterface */
    $fileRepository = $fileFactory->getFileRepository();

    Read Flysystem documentation on how to initialize the filesystem. Once you have +of the Composer documentation.

    Open a command console, enter your project directory, and execute:

    composer require rekalogika/file-bundle

    Initialization

    In your application, initialize the file repository like the following example:

    use Rekalogika\File\FileFactory;
    use League\Flysystem\Filesystem;
    use League\Flysystem\Local\LocalFilesystemAdapter;

    $fileFactory = new FileFactory(
    filesystems: [
    'default' => new Filesystem(new LocalFilesystemAdapter('/var/storage')),
    ]
    );

    /** @var FileRepositoryInterface */
    $fileRepository = $fileFactory->getFileRepository();

    Read Flysystem documentation on how to initialize the filesystem. Once you have a Flysystem filesystem, you can pass it to our FileFactory. Then, use the FileFactory to create a FileRepositoryInterface instance.

    - + \ No newline at end of file diff --git a/file/intro.html b/file/intro.html index 40f9eb99..56357446 100644 --- a/file/intro.html +++ b/file/intro.html @@ -4,7 +4,7 @@ Introduction | Rekalogika.DEV - + @@ -13,17 +13,17 @@ with file objects in an object-oriented manner. A file object represents a file in a Flysystem filesystem. It can be a local file or a file in a cloud storage, the library lets you work with them in the same way.

    Features

    General Features

    • Rich, high-level abstraction of files built on top of Flysystem.
    • Abstractions for file name and media type (MIME type).
    • Caches and stores metadata in a sidecar file. Uniform metadata support across -all filesystems.
    • Uses the repository pattern for files.
    • Remote façade pattern in accessing metadata, improves performance with remote +all filesystems.
    • Uses the repository pattern for files.
    • Remote façade pattern in accessing metadata. Improves performance with remote filesystems. Two metadata queries require only one round trip.
    • Rich metadata support.
    • Option to use lazy-loading proxy for files.
    • Support for file derivations.
    • Separated contracts and implementation. Useful for enforcing architectural -boundaries. Your domain models doesn't have to depend on the framework.

    Interoperability Features

    • Adapters for Symfony HttpFoundation, Form, and Validator.
    • Adapter for OneupUploaderBundle.

    Components

    The File framework consists of several components.

    File classesFile classes
    • rekalogika/file: The core library. It provides the file abstraction and +boundaries. Your domain models don't have to depend on the framework.

    Interoperability Features

    • Adapters for Symfony HttpFoundation, Form, and Validator.
    • Adapter for OneupUploaderBundle.

    Components

    The File framework consists of several components.

    File classesFile classes
    • rekalogika/file: The core library. It provides the file abstraction and metadata support.
    • rekalogika/file-bundle: Integrates the library with Symfony.
    • rekalogika/file-association: Provides support for associating files with Doctrine entities.
    • rekalogika/file-contracts: Contains the interfaces and contracts used by the library.
    • rekalogika/file-derivation: Library for creating derived files.
    • rekalogika/file-image: Provides image resizing filter.
    • rekalogika/file-metadata-contracts: Contains additional interfaces describing file metadata.
    • rekalogika/file-oneup-uploader-bridge: Adapter for OneupUploaderBundle.
    • rekalogika/file-server: Temporary URL server for files.
    • rekalogika/file-symfony-bridge: Adapter for Symfony HttpFoundation, Form, and -Validator.

    License

    MIT

    Contributing

    This framework consists of multiple repositories splitted from a monorepo. Be -sure to submit issues and pull request to the +Validator.

    License

    MIT

    Contributing

    This framework consists of multiple repositories split from a monorepo. Be +sure to submit issues and pull requests to the rekalogika/file-src monorepo.

    - + \ No newline at end of file diff --git a/file/metadata.html b/file/metadata.html index 7f777872..c5c759c7 100644 --- a/file/metadata.html +++ b/file/metadata.html @@ -4,17 +4,17 @@ Metadata | Rekalogika.DEV - +
    -

    Metadata

    This chapter describes how file metadata is handled by this library.

    Primary Metadata

    Firstly, FileInterface has several methods that returns or sets what can be +

    Metadata

    This chapter describes how file metadata is handled by this library.

    Primary Metadata

    Firstly, FileInterface has several methods that return and set what can be considered metadata of the file:

    use Rekalogika\Contracts\File\FileInterface;

    /** @var FileInterface $file */

    // Returns the file's name
    $name = (string) $file->getName();

    // Returns the file's MIME type
    $mime = (string) $file->getType();

    // Returns the file's size in bytes
    $size = $file->getSize();

    // Returns the file's last modified time
    $lastModified = $file->getLastModified();

    Metadata Objects

    A FileInterface can also have several metadata objects associated with it. A metadata object is an object that represents a specific type of metadata of the file.

    These are the metadata objects that are currently implemented:

    • RawMetadataInterface: Represents the raw metadata object. It is a simple -key-value object. The value can be a string, integer, boolean or null.
    • FileMetadataInterface: Represents the metadata that every file has: name, -type, size and last modified time.
    • HttpMetadataInterface: Represents metadata used in HTTP responses. It is +key-value object. The value can be a string, integer, boolean, or null.
    • FileMetadataInterface: Represents the metadata that every file has: name, +type, size, and last modified time.
    • HttpMetadataInterface: Represents metadata used in HTTP responses. It is used when streaming the file to the client over HTTP.
    • ImageMetadataInterface: Contains metadata specific to images, including image dimension and orientation.

    Getting Metadata Objects

    The FileInterface has a get() method that returns an associated object of the file. The caller can use this method to get a specific metadata object of a @@ -25,17 +25,17 @@ using these methods, the caller must call flush() to persist the changes.

    use Rekalogika\Contracts\File\FileInterface;
    use Rekalogika\Contracts\File\Metadata\HttpMetadataInterface;

    /** @var FileInterface $file */

    // Each of the following will be flush automatically individually, and will
    // require two roundtrips to the storage backend
    $file->setType('image/jpeg');
    $file->setName('foo.jpg');

    // The following needs an explicit flush(). It will only require one roundtrip
    // to the storage backend.
    $file->get(FileMetadataInterface::class)?->setType('image/jpeg');
    $file->get(FileMetadataInterface::class)?->setName('foo.jpg');
    $file->flush();
    note

    Local files don't persist metadata. Changes in the metadata are only valid for the duration of the request. However, if the file is copied or moved to a non-local filesystem, the metadata will be copied and persisted by the -destination file.

    Low-Level Metadata Handling

    In a non local filesystem, the library stores a file's metadata in a sidecar +destination file.

    Low-Level Metadata Handling

    In a non-local filesystem, the library stores a file's metadata in a sidecar file in the JSON format. If the file key is foo/bar.txt, the metadata file key will be -foo/bar.txt.metadata.

    Rationale:

    • Supports all filesystem.
    • Uniform way of handling metadata with all filesystem.
    • Simpler administration. i.e. when copying between different filesystems.
    • Implements coarse-grained remote façade pattern to improve performance with remote filesystems.

    With the local filesystem, the library provides the same interface as above, but +foo/bar.txt.metadata.

    Rationale:

    • Supports all filesystems.
    • Uniform way of handling metadata with all filesystems.
    • Simpler administration. i.e. when copying between different filesystems.
    • Implements coarse-grained remote façade pattern to improve performance with remote filesystems.

    With the local filesystem, the library provides the same interface as above but does not save the metadata to a sidecar file. Instead, the metadata is determined from the file and stored in an in-memory cache. Any changes to the -metadata are not persisted and only valid in the current request, but will be +metadata are not persisted and are only valid in the current request but will be considered if the caller copies or moves the file to a non-local filesystem.

    The caller is expected to treat files in the local filesystem as transient -objects, and expected to copy or move the files to a non-local filesystem if -they wish to store the file.

    - +objects and expected to copy or move the files to a non-local filesystem if they +wish to store the file.

    + \ No newline at end of file diff --git a/file/proxy.html b/file/proxy.html index 0be2cc19..cf38e757 100644 --- a/file/proxy.html +++ b/file/proxy.html @@ -4,7 +4,7 @@ Lazy-Loading Proxy | Rekalogika.DEV - + @@ -15,7 +15,7 @@ found when you are trying to use it.

    Getting The Real File from a Proxy

    To get the real file from a proxy, you can call the static method FileProxy::getFile():

    use Rekalogika\Contracts\File\FileInterface;

    /** @var FileInterface $file */

    // $realFile will be a FileInterface object or null if it does not exist
    $realFile = FileProxy::getFile($file);
    note

    You can pass any FileInterface to FileProxy::getFile(). If the file is not a proxy, it will be returned as is.

    - + \ No newline at end of file diff --git a/index.html b/index.html index 39456aca..baa3f21e 100644 --- a/index.html +++ b/index.html @@ -4,13 +4,13 @@ Rekalogika.DEV - + - + \ No newline at end of file diff --git a/psr-16-simple-cache-bundle.html b/psr-16-simple-cache-bundle.html index 9e6c0d2b..cd429913 100644 --- a/psr-16-simple-cache-bundle.html +++ b/psr-16-simple-cache-bundle.html @@ -4,7 +4,7 @@ rekalogika/psr-16-simple-cache-bundle | Rekalogika.DEV - + @@ -12,14 +12,14 @@

    rekalogika/psr-16-simple-cache-bundle

    Enables PSR-16 Simple Cache services in Symfony projects. These were previously enabled in the older Symfony version but were removed in 4.3.

    Installation

    Make sure Composer is installed globally, as explained in the installation chapter -of the Composer documentation.

    Open a command console, enter your project directory and execute:

    composer require rekalogika/psr-16-simple-cache-bundle

    Usage

    Callers can simply wire in Psr\SimpleCache\CacheInterface. The service uses the same underlying pool used by Symfony's CacheInterface.

    use Psr\SimpleCache\CacheInterface;

    class SomeService
    {
    public function __construct(private CacheInterface $cache)
    {
    }

    public function doSomething()
    {
    $this->cache->set('foo', 'bar');
    }
    }

    Rationale

    We are using PSR-16 mostly as an expiring key-value storage. While PSR-6 and Symfony's CacheInterface are more powerful and easier to use for caching things, -we don't feel their interfaces are suitable for a key-value storage.

    Credits

    This package is just a service definition. The actual implementation is done by +we don't feel their interfaces are suitable for key-value storage.

    Credits

    This package is just a service definition. The actual implementation is done by the Symfony project; they just don't make the service available by default.

    - + \ No newline at end of file diff --git a/reconstitutor.html b/reconstitutor.html index 0c5a277c..952592b8 100644 --- a/reconstitutor.html +++ b/reconstitutor.html @@ -4,7 +4,7 @@ rekalogika/reconstitutor | Rekalogika.DEV - + @@ -15,7 +15,7 @@ the control to hydrate additional properties not handled by Doctrine, without having to deal with the peculiarities of Doctrine events and Unit of Work. Similar things also happen when the object is persisted to the database, or -removed.

    The most common case of this type of tasks is for handling file uploads, of +removed.

    The most common case of this type of task is for handling file uploads, of which many specialized libraries have already been written. But plenty of other cases exist:

    • A lazy-loading proxy that fetches the real resource using an API call.
    • Linking objects that are managed by different object managers, or non-Doctrine entities.

    These days we usually call the process hydration. Reconstitution is the term @@ -34,7 +34,7 @@ entities don't have to modify a Doctrine-managed property —like $lastUpdated— just to make sure the correct Doctrine event will be fired.

    Installation

    Make sure Composer is installed globally, as explained in the installation chapter -of the Composer documentation.

    Open a command console, enter your project directory and execute:

    composer require rekalogika/reconstitutor

    Suppose you have an Order object that stores a payment receipt in the paymentReceipt property:

    use Symfony\Component\HttpFoundation\File\File;
    use Symfony\Component\Uid\UuidV7;

    class Order
    {
    private string $id;
    private ?File $paymentReceipt = null;

    public function __construct()
    {
    $this->id = new UuidV7;
    }

    public function getId(): string
    {
    return $this->id;
    }

    public function getPaymentReceipt(): ?File
    {
    return $this->paymentReceipt;
    }

    public function setPaymentReceipt(File $paymentReceipt): void
    {
    $this->paymentReceipt = $paymentReceipt;
    }
    }

    During the fetching of the object from the database, Doctrine will instantiate -the object and hydrate $id and other properties that it manages. Afterwards, -it will be our reconstitutor's turn to handle the $paymentReceipt property. +the object and hydrate $id and other properties that it manages. Afterward, it +will be our reconstitutor's turn to handle the $paymentReceipt property. Similar things also happen when the object is persisted to the database, or removed.

    use Rekalogika\Reconstitutor\AbstractClassReconstitutor;
    use Symfony\Component\HttpFoundation\File\File;

    /**
    * @extends AbstractClassReconstitutor<Order>
    */
    final class OrderReconstitutor extends AbstractClassReconstitutor
    {
    /**
    * The class that this reconstitutor manages. It can also be a super class
    * or an interface.
    */
    public static function getClass(): string
    {
    return Order::class;
    }

    /**
    * When the object is being saved, we check if the paymentReceipt has been
    * just uploaded. If it is, we save it to a file.
    */
    public function onSave(object $order): void
    {
    $path = sprintf('/tmp/payment_receipt/%s', $order->getId());

    $file = $this->get($order, 'paymentReceipt');

    if ($file instanceof UploadedFile) {
    file_put_contents($path, $file->getContent());
    $this->set($order, 'paymentReceipt', new File($path));
    }
    }

    /**
    * When the object is being loaded from the database, we check if the
    * supposed payment receipt is already saved. If it is, then we load the
    * file to the property.
    */
    public function onLoad(object $order): void
    {
    $path = sprintf('/tmp/payment_receipt/%s', $order->getId());

    if (file_exists($path)) {
    $file = new File($path);
    } else {
    $file = null;
    }

    $this->set($order, 'paymentReceipt', $file);
    }

    /**
    * If the order is being removed, we remove the associated payment receipt
    * here.
    */
    public function onRemove(object $order): void
    {
    $path = sprintf('/tmp/payment_receipt/%s', $order->getId());

    if (file_exists($path)) {
    unlink($path);
    }
    }
    }

    Reconstitutor Abstract Class

    The example above uses AbstractClassReconstitutor where our target object is matched using the class provided by getClass(). There is also AbstractAttributeReconstitutor that operates on objects that have a specific PHP attribute.

    - + \ No newline at end of file diff --git a/temporary-url-bundle.html b/temporary-url-bundle.html index 52c34302..37097626 100644 --- a/temporary-url-bundle.html +++ b/temporary-url-bundle.html @@ -4,7 +4,7 @@ rekalogika/temporary-url-bundle | Rekalogika.DEV - + @@ -13,7 +13,7 @@ resource in a plain PHP object, and a service to turn it into a HTTP response. The framework handles the rest.

    Installation

    Make sure Composer is installed globally, as explained in the installation chapter -of the Composer documentation.

    Open a command console, enter your project directory and execute:

    composer require rekalogika/temporary-url-bundle
    - + \ No newline at end of file