diff --git a/docs/docs/guides/tutorials/php.mdx b/docs/docs/guides/tutorials/php.mdx index cd0a1417e..1a4c82b86 100644 --- a/docs/docs/guides/tutorials/php.mdx +++ b/docs/docs/guides/tutorials/php.mdx @@ -4,6 +4,8 @@ sidebar_label: Symfony keywords: [php, symfony] --- +import EmbedGitHubFileContent from "@site/src/components/EmbedGitHubFileContent.tsx"; + # Using Hanko with the Symfony Framework In this guide we are going to explain how to use Hanko with the Symfony framework for PHP. As Symfony is a full-stack framework with many abstractions for authentication management already present, we try to integrate Hanko as seamlessly as possible. @@ -16,26 +18,26 @@ In this guide we are going to explain how to use Hanko with the Symfony framewor ## Creating and running the Symfony application Use the following command to create a new symfony application from the Symfony demo template. `` is a placeholder for the name of the application (and directory in which it will be located). You can freely choose a `` that suits your needs and even describes your application best. -``` +```bash symfony new --demo -``` +``` All following commands need to be run in the project directory so we move to this directory: -``` +```bash cd ``` To be able to work on the frontend parts of the project, we need to install all of its JavaScript dependencies first. As usual we use NPM for this job. -``` +```bash npm install ``` We can now start the Symfony development server integrated in the Symfony CLI which serves your application on a local port. -``` +```bash symfony serve ``` @@ -44,7 +46,7 @@ You can now access your demo application using the link in the commands output. ## Integrating Hanko Frontend Components To integrate the frontend components, we need to install the `@teamhanko/hanko-elements` package using NPM. -``` +```bash npm install @teamhanko/hanko-elements --save-dev ``` @@ -60,7 +62,9 @@ The placeholder `` would be your Hanko cloud instance ID. If you don't use H As we need to access the value of our new environment variable `HANKO_API_URL` somehow inside Twig templates, we chose to create a Twig-Extension: -`` + As you can see, there is a `string $hankoApiUrl` parameter in the constructor function of this class. As Symfony auto-discovers TwigExtensions and tags them correctly, our class is going to be loaded and injected into the Twig environment right away. Without "telling" the Symfony DI Container about the value for the `$hankoApiUrl` parameter, Symfony won't be able to instantiate our class. For service creation to work, we need to manually configure a service argument in `config/services.yaml `. @@ -73,7 +77,9 @@ App\Twig\HankoExtension: As the Symfony Demo Application uses Stimulus controllers with the Symfony UX stimulus-bridge for the original authentication forms, we adapt the `assets/controllers/login-controller.js` to load the `hanko-auth` custom element. -`` + As you can see, the adapted `login-controller` defines the stimulus values `hankoApiUrl` and `loginPath`. @@ -81,11 +87,13 @@ Those values are provided in the `templates/security/login.html.twig` using the There is also a stimulus target defined in the component and marked by the `stimulus_target` Twig helper function. -`` + The most important part of this template is the following: -```html +```twig
` + And has a dependency on three Composer packages which you need to install like this: @@ -185,17 +195,23 @@ As the `database_users` provider cannot provide a user when the user registers f The `hanko_users` provider has a custom service called `HankoUserProvider` associated to it, looking like this: -`` + It creates a new `HankoUser` object using the given `$identifier` previously set from the JWTs `sub` claim in the `HankoUserProvider`. When those steps are done, there is either a `HankoUser` or a normal `User` object set in the Symfony Security module. Depending on which type of User is currently authenticated, we can decide to just show a registration form and don't allow the user to go further using a custom `entry_point` in the `main` firewall part of the `security.yaml` configuration. -`` + Additionally we need to create a new `EventSubscriber` listening on all `KernelEvents::REQUEST` events to redirect users from every other URL than the registration URL back there. -`` + For the purpose of registering a new user, a new Controller method called `register` placed in the `SecurityController` of the Demo project is required looking like this: @@ -294,7 +310,9 @@ export default class extends Controller { The Stimulus targets used by the controller displayed above aren't set using the `stimulus_`-Twig helper functions but provided in the `UserType` Form-Type. -`` + ## Modifying the User entity and removing passwords from the application @@ -319,4 +337,6 @@ We also need to do some manual steps to allow users to log out of their account For this, another `EventSUbscriber` is required: -`` \ No newline at end of file + diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 82764d5c2..5b7bab6c1 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -156,6 +156,7 @@ const config = { prism: { theme: lightCodeTheme, darkTheme: darkCodeTheme, + additionalLanguages: ['php', 'bash'] }, }), }; diff --git a/docs/src/components/EmbedGitHubFileContent.tsx b/docs/src/components/EmbedGitHubFileContent.tsx new file mode 100644 index 000000000..39d973d5d --- /dev/null +++ b/docs/src/components/EmbedGitHubFileContent.tsx @@ -0,0 +1,96 @@ +import React, { FC } from "react"; +import CodeBlock from '@theme/CodeBlock'; + +function convertGitHubUrlToRaw(url: string): string { + const [user, repo, blob, branch, ...filePath] = url.replace("https://github.com/", "").split("/"); + const rawFileUrl = `https://raw.githubusercontent.com/${user}/${repo}/${branch}/${filePath.join("/")}`; + return rawFileUrl; +} + +function getFileExtension(url: string): string { + const filename = url.split("/").pop(); + return filename.slice((Math.max(0, filename.lastIndexOf(".")) || Infinity) + 1); +} + +function getFilePath(url: string): string { + return url.replace("https://github.com/", "").split("/").slice(4, Infinity).join("/"); +} + +type EmbedGitHubFileContentProps = { + url: string, + loadingComponent?: JSX.Element, + errorComponent?: JSX.Element, + onLoad?: () => void + onError?: (e: Error) => void +} + +const EmbedGitHubFileContent: FC = ({ + url, + loadingComponent, + errorComponent, + onLoad, + onError +}) => { + const [gitHubFileContent, setGitHubFileContent] = React.useState(""); + const [isLoading, setIsLoading] = React.useState(true); + const [errorOccurred, setErrorOccurred] = React.useState(false); + + const gitHubUrlRegEx = /^https:\/\/github\.com\/.*\/.*\/blob\/.*/; + if (!url.match(gitHubUrlRegEx)) { + throw new Error("Invalid URL format"); + } + + React.useEffect(() => { + setGitHubFileContent(""); + setIsLoading(true); + setErrorOccurred(false); + + const rawFileUrl = convertGitHubUrlToRaw(url); + + const fetchGitHubFileContent = async () => { + try { + const response = await fetch(rawFileUrl, { + headers: { + "Content-Type": "text/plain; charset=utf-8", + }, + }); + + if (!response.ok) { + setErrorOccurred(true); + onError(new Error()); + } else { + const text = await response.text(); + setGitHubFileContent(text); + setIsLoading(false); + onLoad(); + } + } catch (err) { + setErrorOccurred(true); + onError(err); + } + }; + + fetchGitHubFileContent(); + }, [url, onLoad, onError]); + + if (errorOccurred) { + return errorComponent; + } + + if (isLoading) { + return loadingComponent; + } + + return ( + {gitHubFileContent} + ); +} + +EmbedGitHubFileContent.defaultProps = { + loadingComponent:

loading...

, + errorComponent:

an error occured.

, + onLoad: () => {}, + onError: (e) => console.log(e) +}; + +export default EmbedGitHubFileContent;