Make Laravel and Storyblok work together beautifully.

Overview

Use Storyblok’s amazing headless CMS in way that feels familiar to Laravel developers

Latest Version on Packagist Build Status Quality Score Total Downloads Twitter

This package allows you to use fantastic Storyblok headless CMS with the amazing Laravel PHP framework. It’s designed to try and feel natural to Laravel developers and part of the ecosystem whilst also converting Storyblok’s API JSON responses into something powerful with minimal effort.

Key Features

  • Pages from Storyblok mapped to PHP Pages classes giving access to the nested content (Blocks) and meta data for SEO, OpenGraph and more.
  • Each Storyblok component is automatically transformed into a PHP class using a simple naming convention - just match your class and component names.
  • NEW! All fields in your components are converted to a Field PHP class allowing you to manipulate their data. The package automatically detects common types like rich text fields, assets and markdown.
  • Asset fields are converted to Assets classes allowing you to manipulate them as required.
  • Blocks and fields know where they sit in relation to their ancestors and CSS classes can be created to help your styling.
  • The structure of the JSON data is preserved but super powered making it simple to loop over in your views.
  • It’s simple to link to the Storyblok visual composer by including one view and calling a method for each block in your Blade.
  • Request ‘Folders’ of content such as a list of articles or a team of people.
  • Feels like Laravel - use date casting and accessors exactly as you would with models.
  • Richer Typography with PHP Typography baked in.

Documentation

Read the full docs

Contribute to the docs

Future plans

  • More transformations of content
  • Better support for more components types
  • Better image transformation
  • Cache expensive transformations
  • And more…

Testing

The tests are mostly up-to-date and cover the majority of the code. A few areas that would require hitting the Storyblok API are not tested. If you have experience mocking API please feel free to contribute tests.

Changelog

See it here

Contributing

Please feel free to help expand and improve this project. Currently it supports most of the basic usage for block, fields and content. It would be great to add more advanced features and transformations or simply fix bugs.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

Laravel Package Boilerplate

This package was generated using the Laravel Package Boilerplate.

Comments
  • Having trouble with the basics

    Having trouble with the basics

    This is almost certainly down to a lack of experience with Laravel, but I'm having trouble getting started.

    I've got a StoryBlok space setup with a ton of content imported into it.

    I've got laravel and laravel-storyblok installed, and serving on localhost.

    Environment variables are set, and by the errors I'm getting, it's definitely trying to hit the storyblok API. I've got the catch-all router in place.

    I've gone back to basics, created the file /resources/views/storyblok/pages/page.blade.php which looks like this:

    <main>
    <header>
        <h1>@{{ $story->title }}</h1>
    </header>
    
    <section>
    <?php dd($story); ?>
    </section>
    </main>
    

    I've created a page called 'test' at the root of Storyblok, using the 'page' component, which has two fields defined - title and text.

    But the @{{ $story->title }} just displays {{ $story->title}} in the browser.

    The dd does give me a result:

    App\Storyblok\Page {#1180 ▼
      +_componentPath: array:1 [▶]
      -block: App\Storyblok\Block {#1090 ▶}
      -story: array:22 [▼
        "name" => "test"
        "created_at" => "2021-11-17T10:41:40.966Z"
        "published_at" => "2021-11-17T10:42:25.116Z"
        "alternates" => []
        "id" => 86019624
        "uuid" => "761d11ac-c8bd-4eee-ae1f-6e3dbdc920bf"
        "content" => array:4 [▼
          "_uid" => "fbb0f60a-3c57-456e-84c3-8b86f02aeae0"
          "body" => array:1 [▼
            0 => array:4 [▼
              "_uid" => "62cd6c66-4d78-4ee5-af0f-e17b231fabda"
              "title" => "My big test"
              "component" => "text"
              "_editable" => "<!--#storyblok#{"name": "text", "space": "127485", "uid": "62cd6c66-4d78-4ee5-af0f-e17b231fabda", "id": "86019624"}-->"
            ]
          ]
          "component" => "page"
          "_editable" => "<!--#storyblok#{"name": "page", "space": "127485", "uid": "fbb0f60a-3c57-456e-84c3-8b86f02aeae0", "id": "86019624"}-->"
        ]
        "slug" => "test"
        "full_slug" => "test"
        "default_full_slug" => null
        "sort_by_date" => null
        "position" => -40
        "tag_list" => []
        "is_startpage" => false
        "parent_id" => 0
        "meta_data" => null
        "group_id" => "c3210eaf-a185-46ea-b7c5-a54424b3fcc9"
        "first_published_at" => "2021-11-17T10:42:25.116Z"
        "release_id" => null
        "lang" => "default"
        "path" => null
        "translated_slugs" => []
      ]
      +liveContent: []
      #_meta: array:5 [▶]
    }
    

    Only slugs in Storyblok root produce this kind of result, everything else errors out completely - usually because the API call is using the slug without the 'starts_with'. For example:

    Storyblok\ApiException
    An HTTP Error has occurred! - An HTTP Error has occurred! - Client error: `GET http://api.storyblok.com/v1/cdn/stories/blog?token=xxxxx&version=draft&cache_version=1637149023` resulted in a `404 Not Found` response: {"stories":["This record could not be found"]}
    

    If I hit the API directly in the browser with a 'starts_with=blog/' parameter, then I get a full API response.

    So definitely I'm missing something here, and if the answer is "learn more Laravel", then that's perfectly acceptable answer!

    opened by kbrookes 16
  • Enable (rendering of) inline components within the RichText field

    Enable (rendering of) inline components within the RichText field

    Hey, again thanks for the nice package!

    Storyblok allows to insert components within the RichText field [1]. But from what i understood the provided storyblok/richtext-resolver for PHP does not handle them. Anyways, i needed to render inline components and came up with the following code. Maybe its a common enough use-case to be added to the core of this package.

    I found no way to overwrite the existing RichText Field Class, so i added a Custom Field and named it accordingly in Storyblok. Maybe it would be nice if one could also overwrite the built-in fields to apply changes globally based on the field type vs the field name.

    <?php
    
    namespace App\Storyblok\Fields;
    
    use Riclep\Storyblok\Fields\RichText as BaseRichText;
    use Storyblok\RichtextRender\Resolver;
    use Riclep\Storyblok\Block;
    
    class Richtextfield extends BaseRichText
    {
    	/**
    	 * Converts the data to HTML when printed
    	 *
    	 * @return string
    	 */
    	public function __toString()
    	{
            $richtextResolver = new Resolver();
    
            $out = "";
            // Loop through all nodes from the RichText Field
            // Either Render them normally
            // Or if type is 'blok' create a block an add the rendered content to ouput
            foreach ($this->content['content'] as $node) {
                if ($node['type'] == 'blok' && isset($node['attrs']['body']) && is_array($node['attrs']['body'])){
                    foreach ($node['attrs']['body'] as $blockContent) {
                        $block = new Block($blockContent, $this->block()->parent());
                        $out .= $block->render();
                    }
                } else {
                    $out .= $richtextResolver->render(["content" => [$node]]);
                }
            }
            return $out;
            // Original
    		// return $richtextResolver->render($this->content);
    	}
    }
    

    [1] https://www.storyblok.com/docs/richtext-field#components-inside-the-richtext-field

    opened by papoms 8
  • Question about serializing blocks

    Question about serializing blocks

    Question

    Hey there 👋 .

    This is more of a question than a feature request or bug ticket.

    Let's say you have a block that you are going to serialize (via json_encode or the like). This package uses the magic __get() function to get attributes out of the private $fields array behind the scenes, so the JSON encoded object only has a few public properties (resolve relations and whatnot).

    Is there a native way to get the full class serialized with all of the Storyblok attributes included? If there isn't, would that be something you want this package to handle?

    Background

    A lot of times we are using Storyblok to render our data, but then there are some frontend manipulations that we want to do as well. To accomplish our goals, sometimes we need to encode the Storyblok object into javascript to be used on the client.

    Example

    If I use the laravel @json directive on any component, the output will look similar to this:

    {"_autoResolveRelations":false,"_resolveRelations":[],"_filterRelations":true,"_componentPath":["page","page","full-section","button"]}
    

    But if I @dd that same instance, I get something like this:

    App\Storyblok\Blocks\Button {[#1588 ▼]
      +_autoResolveRelations: false
      +_resolveRelations: []
      +_filterRelations: true
      +_componentPath: array:4 [[▶]()]
      #_casts: []
      -_fields: Illuminate\Support\Collection {[#1589 ▼]()
        #items: array:15 [[▼]()
          "url" => Riclep\Storyblok\Fields\UrlLink {[#1592 ▶]()}
          "size" => "normal"
          "text" => "Rawr"
        ]
        #escapeWhenCastingToString: false
        // other attributes
      }
    }
    

    Notice that the component has data like size and text in it, but it doesn't show up in the serialized instance.

    opened by chaseconey 4
  • Cache ignores $page in paginated fetching

    Cache ignores $page in paginated fetching

    Thanks for the awesome library.

    Referring to line: https://github.com/RicLeP/laravel-storyblok/blob/master/src/Folder.php#L165

    If we have the following two pages we get the same results back, the second from the cache.

    // example.org/some-content?page=1
    $requestSomeContent = new Folder();
    $requestSomeContent->slug('some-content');
    $requestSomeContent->perPage(10);
    $requestSomeContent->settings([
        'is_startpage' => false,
        'filter_query' => [],
    ]);
    
    // yields cache key:
    // folder-some-content-4893ccccb8f122d63a21febbbb000000
    
    // example.org/some-content?page=2
    $requestSomeContent = new Folder();
    $requestSomeContent->slug('some-content');
    $requestSomeContent->perPage(10);
    $requestSomeContent->settings([
      'is_startpage' => false,
      'filter_query' => [],
    ]);
    
    // yields cache key:
    // folder-some-content-4893ccccb8f122d63a21febbbb000000
    

    Note on the second request (page=2), the cache key constructed doesn't account for the current page which gives it the same key, meaning we get the cached data back.

    I believe the cache key should include $page in the cache key if set.

    If I disable the cache (STORYBLOK_CACHE=false) it works as expected. Looking through telescope I can confirm the cache keys aren't accounting for $page.

    opened by chrisk-7777 3
  • Dealing with pagination

    Dealing with pagination

    Hi @RicLeP, I was wondering how you'd deal with paginating through the StoryBlok API when there's more than 100 articles in a 'collection'.

    E.G. my client has a blog with several hundred articles - I couldn't see a process for iterating through all of the blog posts - just the first 100.

    opened by kbrookes 3
  • Resolve global reference

    Resolve global reference

    Hey Richard! We have a global reference that we want to resolve. Is there a workflow to handle this?

    For more information you can read here: https://www.storyblok.com/tp/global-components-references

    Have a nice weekend!

    opened by WASDLauterbach 3
  • Call to undefined method App\Storyblok\Block::flatten()

    Call to undefined method App\Storyblok\Block::flatten()

    Overview

    Looks like the latest version broke something in the editor-bridge. I see a function called flatten was removed, but not seeing any obvious references.

    Error

    Error
    Call to undefined method App\Storyblok\Block::flatten() (View: /.../projectname/vendor/riclep/laravel-storyblok/src/resources/views/editor-bridge.blade.php)
    

    Steps to Reproduce

    • Upgrade from previous version to version 2.6.0
    • Create a new piece of content inside Storyblok (or anything that enables the editor bridge)
    opened by chaseconey 3
  • feat: add new sync command

    feat: add new sync command

    Overview

    This PR introduces a new laravel-storyblok CLI command:

    php artisan ls:sync
    

    This command will allow the syncing of property fields of one or all of the components in a given project.

    Process

    This command will look at all blocks in a project (path configurable) and then pull all components from the Storyblok management API and try and update all of the properties non-destructively. You can also optionally pass a component name in the same format as the make command to update only the fields for that component.

    Context

    We are starting to use Storyblok in a fairly large project and found maintaining the fields for all components to be quite a drag. We are using your package and found it the perfect place to integrate this type of functionality as it already relies on the Management API for some operations.

    Screenshots

    Command Signature

    image

    Syncing 1 Component

    php artisan ls:sync Grid
    

    image

    Syncing All Components

    php artisan ls:sync
    

    image

    opened by chaseconey 3
  • [Bug] Multi-option Parsing

    [Bug] Multi-option Parsing

    Overview

    Hello there 👋 .

    We seem to be encountering a bug in one of the recent updates of your package. We upgraded from 2.15.0 to 2.19.2 and parsing of multi-option fields is no longer working.

    After doing a bit more research the bug seems to have been introduced in 2.17.0.

    Steps to Reproduce

    • Create a page
    • Add a new field that is a muti-option with a source as self
    • Add some options
    • Add field to page
    • Inspect draft response, and you should see something like:
    {
        "story": {
            "content": {
                "_uid": "1515771c-3ec4-4922-8921-9607eda522e6",
                "body": [
                    {
                        "_uid": "29914472-2a98-428d-868c-56c30207d608",
                        "component": "test-component",
                        "test_field": [
                            "20",
                            "10",
                            "30"
                        ],
                    }
                ]
            }
        }
    }
    

    Expected Result

    I expect in the component TestComponent to have access to a field called test_field that would be an array.

    Actual Result

    When accessing test_field, null is returned.

    opened by chaseconey 2
  • Detecting broken images to prevent calling transform()

    Detecting broken images to prevent calling transform()

    A bit of an edge case here, but I've got a StoryBlok instance where we imported thousands of articles into the system, and we have a ton of broken images.

    The client's gradually going through and fixing them, but we've gone live anyway - so there's a lot of older pages that just error out when we use $image->transform().

    My current method of detecting if an image exists or not is to do a check on $story->image->filename.

    However with broken images imported into Storyblok, there's still a filename (which is helping my client to figure out which images need to be added back in).

    I could probably do a check on the $image->copyright or $image->alt fields - these are null on broken images, and if not utilised are empty.

    But I thought it might be a better option to be able to check the transformer for the extension type - which will return null on these images.

    As I said, very much an edge case, but I'd be interested to hear your thoughts on a solve.

    opened by kbrookes 2
  • Undefined Variable after Generating Block

    Undefined Variable after Generating Block

    Looks like a recent change to the generator command stub changed the variable in the blade file from $content to $block. Now when generating a fresh block with the latest version of the library, you get this error:

    Undefined variable: block (View: /projects/www.bankrate.com/resources/views/storyblok/blocks/teaser.blade.php)
    
    opened by chaseconey 2
Releases(2.19.3)
  • 2.19.3(Dec 7, 2022)

  • 2.19.0(Dec 2, 2022)

    Updated Storyblok\Client to 2.3.0 Added support for language and fullback language

    Full Changelog: https://github.com/RicLeP/laravel-storyblok/compare/2.18.0...2.19.0

    Source code(tar.gz)
    Source code(zip)
  • 2.18.0(Nov 29, 2022)

    • Fixed: stub-views command now skips tabs
    • Added: stub-views command now uses Block class for $block typehint in Blade if it exists
    • Added: stub-views command now adds transform() default code for images
    • Added: $_defaults property to Block class
    • Added: Artisan commend for making folders
    • Folder’s can now have their cache-key prefix set
    • Folders sort order and field can now be separately set
    • Folders have asc() and desc() methods for setting sort order
    • Folders have toArray() method
    • Many folder methods now return $this for chaining
    • Updated Embed/Embed to version 4
    Source code(tar.gz)
    Source code(zip)
  • 2.17.0(Nov 22, 2022)

    Added the ability to specify the class to be used when resolving related stories.

    Class properties have had type added - any classes that extend these classes will need to be updated to match. For example if you’re using protected $_resolveRelations = ['something] you’ll need to change it to protected array $_resolveRelations = ['something].

    Source code(tar.gz)
    Source code(zip)
  • 2.16.0(Nov 16, 2022)

  • 2.15.0(Oct 18, 2022)

  • 2.14.1(Oct 13, 2022)

  • 2.14.0(Oct 13, 2022)

    This update extracts certain functionality into external packages to keep the core purer. The CssClasses and AppliesTypography traits have been moved and need to be installed separately if required.

    • Moved AppliesTypography to its [own package](https://github.com/RicLeP/laravel-storyblok-typography)
      
    • Moved CssClasses to its [own package](https://github.com/RicLeP/laravel-storyblok-css)
      
    • Added fieldsReady() method to blocks called after fields are processed but before any self initialising traits are called
      
    • Ignition updated to Spatie version
      
    Source code(tar.gz)
    Source code(zip)
  • 2.13.1(Oct 13, 2022)

  • 2.13.0(Sep 9, 2022)

    • Updated Storyblok php client to v2.2, this had the effect of moving from v1 to v2 of their content API. This should be a seemless upgrade
    • Updated to Storyblok Richtext Renderer v2
    • Added support for selecting Storyblok API region in the config file
    • Dropped PHP 7.4 support as it’s EOL soon
    Source code(tar.gz)
    Source code(zip)
  • 2.12.6(Jul 29, 2022)

  • 2.12.5(Jul 21, 2022)

    Added hasChildBlock() to Blocks with ‘blok’ field types.

    Full Changelog: https://github.com/RicLeP/laravel-storyblok/compare/2.12.4...2.12.5

    Source code(tar.gz)
    Source code(zip)
  • 2.12.0(Jul 21, 2022)

  • 2.11.0(Jul 21, 2022)

    • Add with() method to Fields allowing data to be passed in
    • Block’s render() method can now take an array of additional data that is passed to the view
    • Folders no longer default to 10 stories - they will load the maximum possible Set $perPage = 10 on your folders to restore the old functionality
    Source code(tar.gz)
    Source code(zip)
  • 2.10.0(Jul 21, 2022)

  • 2.9.0(Jul 21, 2022)

  • 2.8.0(Jul 21, 2022)

    • Support for the new Storyblok image service URL format
    • Image transformations now use a driver class meaning more services can be supported
    Source code(tar.gz)
    Source code(zip)
  • 2.7.0(Jul 21, 2022)

  • 2.6.0(Jul 21, 2022)

  • 2.5.0(Jul 21, 2022)

  • 2.4.0(Jul 21, 2022)

  • 2.3.0(Jul 21, 2022)

  • 2.2.0(Jul 21, 2022)

  • 2.1.0(Jul 21, 2022)

  • 2.0.0(Jul 21, 2022)

    • A complete rewrite of the entire package
    • Added new Field classes (previously everything was a Block)
    • Automatically detect common field types such as richtext and assets and convert them into the appropiate format.
    • Remove the need to manually specify markdown and richtext fields
    • Lots of tweaks and bug fixes
    Source code(tar.gz)
    Source code(zip)
Owner
Richard Le Poidevin
Laravel, VueJs, CSS Storyblok Ambassador: https://www.storyblok.com/ Host: https://thecuriosityofachild.com/
Richard Le Poidevin
This Kirby V3 Plugin brings snippets and blueprints together in one place. It includes useful tools that completely changing the way you work with Kirby: Fast and well organized.

Kirby Components Overview Do you love to make awesome projects with Kirby CMS? Do you also find it difficult to switch between snippets and blueprints

Roman Gsponer 6 May 31, 2023
A PHP Library To Make Your Work Work Easier/Faster

This Is A Php Library To Make Your Work Easier/Faster,

functionality 2 Dec 30, 2022
Running Laravel and React stacks together using Vite and InertiaJS on Docker.

Laravel-Vite-Docker Running Laravel and React stacks together using Vite and InertiaJS on Docker. Explore project's blog » Report Bug · Request Featur

Elvin Lari 6 Dec 22, 2022
Another initiative where patient in need of Blood and recovered patients willing to donate Blood can come together under one platform and connect with each other.

This is yet another initiative where patient in need of Blood and recovered patients willing to donate Blood can come together under one platform and connect with each other.

Rohit Tiwari 1 May 5, 2022
This library brought ReactPHP and QueryList together

ReactPHP QueryList This library brought ReactPHP and QueryList together. Installation composer require ahmard/reactphp-querylist Usage Playing with Q

Ahmad Mustapha 5 Nov 19, 2021
Safely break down arrays or objects, and put them back together in new shapes.

traverse/reshape traverse() and reshape() are companion functions that safely break down arrays or objects and put them back together in new shapes. t

Alley Interactive 2 Aug 4, 2022
The swiss army knife for Magento developers, sysadmins and devops. The tool provides a huge set of well tested command line commands which save hours of work time. All commands are extendable by a module API.

netz98 magerun CLI tools for Magento 2 The n98 magerun cli tools provides some handy tools to work with Magento from command line. Build Status Latest

netz98 758 Dec 28, 2022
Easily create and work with code snippets from PHP

code-snippets Easily create and work with code snippets from source code files of any type in PHP. The original code this package is based on was borr

Permafrost Software 8 Sep 4, 2022
When the player is killed and dies, the sound will work

KillDeathSound | v1.0.0 ✔️ When the player is killed and dies, the sound will work ✔️ ☝ Features When the player is killed and dies, the sound will wo

Noob MCBG 4 Jun 27, 2022
Joole Reflector - used to work with the properties of objects, their changes and merges

Joole Reflector allows you to change protected, as well as private properties of an object.

Ravil Sitdikov 1 May 7, 2022
PHP shells that work on Linux OS, macOS, and Windows OS.

PHP Reverse Shell Just a little refresh on the popular PHP reverse shell script pentestmonkey/php-reverse-shell. Credits to the original author! Works

Ivan Šincek 294 Jan 5, 2023
This package provides a simple and intuitive way to work on the Youtube Data API. It provides fluent interface to Youtube features.

Laravel Youtube Client This package provides a simple and intuitive way to work on the Youtube Data API. It provides fluent interface to Youtube featu

Tilson Mateus 6 May 31, 2023
Creating data transfer objects with the power of php objects. No php attributes, no reflection api, and no other under the hook work.

Super Simple DTO Creating data transfer objects with the power of php objects. No php attributes, no reflection api, and no other under the hook work.

Mohammed Manssour 8 Jun 8, 2023
"Whatever you do, work at it with all your heart." - Apostole Paul, probably, maybe.

Apostolos Whatever you do, work at it with all your heart. - Apostole Paul Also remember: Sigmund Freud says in Civilization and Its Discontents that

Stephan Meijer 7 Jan 14, 2022
Beanstalk is a simple, fast work queue.

beanstalkd Simple and fast general purpose work queue. https://beanstalkd.github.io/ See doc/protocol.txt for details of the network protocol. Please

Beanstalkd 6.3k Jan 6, 2023
PHP 7+ Payment processing library. It offers everything you need to work with payments: Credit card & offsite purchasing, subscriptions, payouts etc. - provided by Forma-Pro

Supporting Payum Payum is an MIT-licensed open source project with its ongoing development made possible entirely by the support of community and our

Payum 1.7k Dec 27, 2022
This composer plugin is a temporary implementation of using symbolic links to local packages as dependencies to allow a parallel work process

Composer symlinker A Composer plugin to install packages as local symbolic links. This plugin is a temporary implementation of using symbolic links to

Pierre Cassat 18 Nov 9, 2021
Firebird-PHP: A library created to meet a work need when handling a Firebird database

Firebird-PHP: A library created to meet a work need when handling a Firebird database

Philipe  Lima 3 Jun 27, 2022
Laminas\Text is a component to work on text strings

laminas-text This package is considered feature-complete, and is now in security-only maintenance mode, following a decision by the Technical Steering

Laminas Project 38 Dec 31, 2022