Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Paper Laravel Package

jacobjoergensen/laravel-paper

Laravel Paper adds flat-file drivers to Eloquent for Laravel 12+ (PHP 8.4+). Point a model to a content directory and query Markdown or JSON files with familiar Eloquent APIs—no database, schema, or custom connection. Uses attributes + a trait.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require jacobjoergensen/laravel-paper
    

    No additional configuration is needed beyond Laravel 12+ and PHP 8.4+.

  2. First Model: Create a model in app/Models with the required attributes:

    use JacobJoergensen\LaravelPaper\Attributes\{ContentPath, Driver};
    use JacobJoergensen\LaravelPaper\Paper;
    
    #[Driver('markdown')] // or 'json'
    #[ContentPath('content/posts')]
    class Post extends Model
    {
        use Paper;
    }
    
    • ContentPath: Directory where files are stored (relative to storage/app).
    • Driver: File format (markdown or json).
  3. First File: Create a file in storage/app/content/posts/hello-world.md:

    ---
    title: Hello World
    ---
    Content here...
    

    The filename (hello-world) becomes the primary key.

  4. First Query:

    $post = Post::find('hello-world'); // Returns Post model
    $posts = Post::all(); // Returns Collection of Post models
    

Where to Look First

  • Model Attributes: Focus on [Driver] and [ContentPath]—these define the core behavior.
  • File Structure: Files must be in storage/app/{content-path}.
  • YAML Frontmatter: For Markdown, metadata is parsed from YAML frontmatter (e.g., --- key: value ---).
  • JSON Schema: For JSON, the file must be valid JSON with a top-level object.

First Use Case: Static Site Content

Replace a database-backed blog with flat files:

  1. Store posts in storage/app/content/posts/ as .md files.
  2. Use Post::latest()->take(5) to fetch recent posts in a controller.
  3. Render with Blade: @foreach($posts as $post) {{ $post->title }}: {{ $post->content }} @endforeach.

Implementation Patterns

Workflows

1. CRUD Operations

  • Create: Save a model to a file:
    $post = new Post(['title' => 'New Post']);
    $post->save(); // Writes to `storage/app/content/posts/{slug}.md`
    
    • Slug defaults to Str::slug($title) if not set.
  • Update: Modify attributes and call save().
  • Delete: $post->delete() removes the file.
  • Find: $post = Post::find('slug') or Post::where('title', 'like', '%Laravel%').

2. File Format Handling

  • Markdown:
    • Frontmatter (YAML) is parsed into model attributes.
    • Content is stored in content attribute (e.g., $post->content).
    • Example:
      ---
      title: Guide
      excerpt: Short preview
      ---
      # Full Content
      ...
      
  • JSON:
    • Entire file is parsed as JSON.
    • Example file (storage/app/content/posts/guide.json):
      {
          "title": "Guide",
          "content": "Full content...",
          "excerpt": "Preview"
      }
      

3. Querying

  • Use Eloquent methods:
    Post::where('published', true)->get();
    Post::where('tags', 'contains', 'laravel')->get();
    Post::orderBy('date', 'desc')->take(3);
    
  • Supports where, orWhere, orderBy, limit, offset, etc.
  • Note: Only attributes defined in the file can be queried.

4. Events and Observers

  • Use Eloquent observers for side effects:
    Post::observe(PostObserver::class);
    
    class PostObserver {
        public function saved(Post $post) {
            // Trigger after save (e.g., generate sitemap)
        }
    }
    

5. Relationships

  • Define relationships as usual:
    class Post extends Model {
        public function comments() {
            return $this->hasMany(Comment::class);
        }
    }
    
  • Related models must also use Paper trait.

6. File Management

  • Custom Slugs: Override getRouteKeyName() or set slug explicitly:
    $post->slug = 'custom-slug';
    $post->save();
    
  • File Permissions: Ensure storage/app/content is writable:
    chmod -R 775 storage/app/content
    

Integration Tips

1. With Laravel Scout

  • Index flat-file models for search:
    class Post extends Model {
        public function shouldBeSearchable() {
            return true;
        }
    }
    
  • Configure scout in config/scout.php to use a local driver.

2. With Laravel Nova

  • Use the Paper models in Nova resources.
  • Nova will reflect the file structure but won’t edit files directly (use Nova’s built-in CRUD).

3. With Laravel Forge/Laravel Vapor

  • Deploy storage/app/content to object storage (e.g., S3) for scalability:
    #[ContentPath('s3://my-bucket/content/posts')]
    class Post extends Model { ... }
    
  • Requires league/flysystem-s3v3 and configuration in config/filesystems.php.

4. With Laravel Livewire

  • Real-time updates:
    public function updatedTitle() {
        $this->save(); // Persists changes to file
    }
    

5. Validation

  • Validate attributes before saving:
    protected static function booted() {
        static::saving(function ($model) {
            $model->validateOnly(['title', 'content']);
        });
    }
    

6. Testing

  • Use PaperTestCase (if provided) or mock the filesystem:
    use Illuminate\Foundation\Testing\RefreshDatabase;
    
    class PostTest extends TestCase {
        use RefreshDatabase;
    
        public function test_post_creation() {
            $post = new Post(['title' => 'Test']);
            $post->save();
            $this->assertFileExists(storage_path('app/content/posts/test.md'));
        }
    }
    

Gotchas and Tips

Pitfalls

1. Filesystem Permissions

  • Issue: FileNotFoundException or PermissionDeniedException.
  • Fix: Ensure storage/app/content is writable:
    chmod -R 775 storage/app/content
    
  • Debug: Check storage/log/laravel.log for filesystem errors.

2. YAML Parsing Errors

  • Issue: Markdown files with invalid YAML frontmatter throw Symfony\Component\Yaml\Exception\ParseException.
  • Fix:
    • Validate YAML with yamlint.
    • Use double quotes for strings with special characters:
      ---
      title: "Post with 'quotes'"
      ---
      

3. JSON Schema Mismatches

  • Issue: JSON files must be valid JSON objects. Empty files or arrays cause JsonException.
  • Fix: Ensure files contain a top-level object:
    {} // Valid
    [] // Invalid
    

4. Case Sensitivity in Filenames

  • Issue: Post::find('Hello-World') fails if the file is hello-world.md.
  • Fix: Use consistent slug casing (e.g., Str::slug()).

5. Caching Quirks

  • Issue: Changes to files aren’t reflected immediately due to Laravel’s model caching.
  • Fix: Clear the cache after updates:
    Artisan::call('cache:clear');
    
  • Better: Use Model::unguard() in tests or disable caching for development:
    config(['paper.cache' => false]);
    

6. Relationship Loading

  • Issue: Eager loading relationships fails if related files don’t exist.
  • Fix: Use with() cautiously or handle missing files:
    $post = Post::with('comments')->find('slug');
    if (!$post->comments->isEmpty()) { ... }
    

7. Large Files

  • Issue: Performance degrades with large Markdown/JSON files.
  • Fix:
    • For JSON: Store large content in separate files and reference them.
    • For Markdown: Use content attribute for body and store metadata separately.

Debugging

1. Enable Debugging

  • Set config(['paper.debug' => true])
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky