A magic PHP framework. Build reactive web apps without writing HTML, CSS, or JavaScript! Powered by Tailwind, Alpine, Laravel, & Livewire.

Related tags

Frameworks malzahar
Overview

Malzahar

A magic PHP framework. Build reactive web apps without writing HTML, CSS, or JavaScript! Powered by Tailwind, Alpine, Laravel, & Livewire.

Requirements

  • PHP 8
  • Laravel 8
  • NPM

Installation

Create a new Laravel app:

laravel new my-app

Configure .env APP, DB, & MAIL values:

APP_*
DB_*
MAIL_*

Require Malzahar via composer:

composer require bastinald/malzahar

Run the install command:

php artisan malz:install

This will install & configure Tailwind & Alpine and create some example components to get you started.

Commands

Automatic Migrations

php artisan malz:migrate {--f} {--s} {--fs}

This will run the automatic migrations by comparing the migration methods of your models to the current database table structures and apply any necessary changes. Use the --f option for fresh, --s for seed, or --fs for both. Using automatic migrations is completely optional with Malzahar.

Making Blade Components

php artisan malz:blade {class}

This will generate a Blade abstraction component inside the app/Components/Blade folder. As with all generator commands, you may specify a sub folder using slashes or dot notation for the class.

Making Livewire Components

php artisan malz:livewire {class} {--f}

This will generate a reactive Livewire component inside the app/Components/Livewire folder. Use the --f option to make a full-page component with automatic routing included.

Making Models

php artisan malz:model {class}

This will generate an Eloquent model including a migration and definition method for use with the automatic migration command. It also creates a factory for the model.

Components

Blade Components

Blade components are used to abstract one or more HTML components into their own PHP class so that they can be reused and maintained with ease.

For example, let's say you need to make a reusable Button component for your forms:

namespace App\Components\Blade\Forms;

use Bastinald\Malzahar\Components\Blade;
use Bastinald\Malzahar\Components\Html;

class Button extends Blade
{
    public $color = 'blue';

    public function attributes()
    {
        return [
            'type' => 'button',
        ];
    }

    public function classes()
    {
        return [
            'text-white rounded-md shadow-sm px-4 py-2',
            'bg-' . $color . '-600 hover:bg-' . $color . '-700',
        ];
    }

    public function template()
    {
        return Html::button($this->slot)
            ->merge($this);
    }
}

The design behind these components works similar to standard Laravel blade components, except we have a few more features. Notice the attributes and classes methods. This is where you would specify default attributes and classes for the component. These are applied via the merge($this) method on the Html::button() component. You can also see custom properties being utilized e.g. $color in the example above. Properties are different from attributes. Also, notice the use of $this->slot in the component template. The slot is what is passed via the make() method parameters.

Now you can use this Button component inside any other component via the make() method:

class Login extends Livewire
{
    public $email;

    public function template()
    {
        return GuestLayout::make(
            Html::form(
                Input::make()
                    ->type('email')
                    ->placeholder(__('Email'))
                    ->error($this->error('email'))
                    ->wireModelDefer('email'),

                Button::make(__('Login'))
                    ->color('red')
                    ->type('submit')
                    ->class('w-full'),
            )->wireSubmitPrevent('login'),
        );
    }

Notice how we can still pass a color, type and class to the Button component via chained methods, which will be merged with whatever default properties, attributes and classes are specified inside the component class itself.

Chain methods on the component in order to set class properties, HTML attributes, and CSS classes via Tailwind. Just use the name of the property or attribute as the method name, and its parameter will be the value. Tailwind classes can be applied via the class method.

Livewire Components

Livewire components are reactive components used for making interactive partial and full page experiences.

Full page components should use a route and title method e.g.:

class Home extends Livewire
{
    public function route()
    {
        return Route::get('/home', static::class)
            ->name('home')
            ->middleware('auth');
    }

    public function title()
    {
        return __('Home');
    }

    public function template()
    {
        return AuthLayout::make(
            Html::h1($this->title())
                ->class('text-3xl font-bold mb-4'),

            Html::p(__('You are logged in!')),
        );
    }
}

This full page component would be accessible via the /home route.

For partial components, you can create any components you want to include inside of other Livewire components, and then include them via the make() method. You use the make() method to construct all of your custom Blade and Livewire components.

Let's say we created this simple partial component:

class Alert extends Livewire
{
    public $message;

    public function mount($message)
    {
        $this->message = $message;
    }

    public function template()
    {
        return Html::div(
            Html::p(__($this->message))
                ->class('font-bold mb-4'),

            Html::button(__('Change Message'))
                ->class('bg-blue-600 text-white px-4 py-2')
                ->wireClick('changeMessage'),
        );
    }

    public function changeMessage()
    {
        $this->message = 'I am a changed message.';
    }
}

Now we can include, and even declare mounted properties for this Alert component in our other Livewire components via make():

class Home extends Livewire
{
    public function template()
    {
        return AuthLayout::make(
            Html::h1($this->title())
                ->class('text-3xl font-bold mb-4'),

            Alert::make()->message('Hello, world!'),
        );
    }

Notice how we can pass a message property to the mount() method via a magic message() method that is available to us dynamically. Malzahar works similarly for Blade, Livewire, and HTML components. You're a wizard, Harry!

Oh, and if you need to grab validation errors, you can use the $this->error() method inside your Livewire component:

use App\Components\Blade\Forms\Button;
use App\Components\Blade\Forms\Input;
use App\Components\Blade\Layouts\GuestLayout;
use Bastinald\Malzahar\Components\Html;
use Bastinald\Malzahar\Components\Livewire;

class Login extends Livewire
{
    public $email, $password;

    public function template()
    {
        return GuestLayout::make(
            Html::form(
                Input::make()
                    ->type('email')
                    ->placeholder(__('Email'))
                    ->error($this->error('email'))
                    ->wireModelDefer('email'),

                Input::make()
                    ->type('password')
                    ->placeholder(__('Password'))
                    ->error($this->error('password'))
                    ->wireModelDefer('password'),

                Button::make(__('Login'))
                    ->type('submit')
                    ->class('w-full'),
            )->wireSubmitPrevent('login'),
        );
    }

    public function rules()
    {
        return [
            'email' => ['required', 'email'],
        ];
    }

    public function login()
    {
        $this->validate();

        // attempt to log the user in
    }

HTML Components

HTML components are the building blocks for all of your other components. The syntax is similar to other Malzahar components, and we can add any attributes we want via magically chained methods.

All HTML components can be made by using their tag as the constructor method, and attributes for said tag come after:

public $email;

public function template()
{
    return Html::div(
        Html::h1(__('Hello, world')),

        Html::input()
            ->type('email')
            ->placeholder(__('Email'))
            ->wireModelDefer('email'),

        Html::p(__('Look ma, no hands!'))
            ->class('text-blue-600 font-bold'),
    );
}

If you notice, you'll see that the parameters for the constructing method contain the slot (or content) for that HTML element. See how the div in the example above contains an h1, input, and p inside of it. Same goes for the h1 element, it has some translated text inside it.

For chained attribute methods, just specify an HTML element attribute name, and it's value as the parameter. Look at the Html::input() example above, we have given it a type of email, etc.

HTML components also support Livewire and Alpine methods as well. In the example above, you can see the use of wireModelDefer('email'), which actually translates to wire:model.defer="email" in the rendered HTML. This is used to bind the input value to the $email property when an action is performed (defer).

Same concept goes for Alpine. Let's say we wanted to add some Tailwind transitions to a dropdown:

Html::div(
    Html::button('Open Dropdown')
        ->xOnClick('open = true'),

    Html::div('Dropdown Body')
        ->xShow('open')
        ->xTransitionEnter('transition ease-out duration-100')
        ->xTransitionEnterStart('transform opacity-0 scale-95')
        ->xTransitionEnterEnd('transform opacity-100 scale-100')
        ->xTransitionLeave('transition ease-in duration-75')
        ->xTransitionLeaveStart('transform opacity-100 scale-100')
        ->xTransitionLeaveEnd('transform opacity-0 scale-95')
        ->xOnClickAway('open = false')
        ->class('absolute bg-white p-3')
)->xData('{ open: false }'),

Notice how Livewire attributes start with wire, and the Alpine attributes start with x. Malzahar is smart enough to format these attributes to their proper syntax when being rendered to actual HTML on compile.

Dynamic Components

Sometimes you will need to use third party blade components in your Malzahar app. Fortunately, this package makes this very simple via the Dynamic class.

For example, let's say I installed the Laravel Honey package. Normally, to include this component inside one of my views, I would use something like this:

<x-honey recaptcha />

Now we can't use actual HTML with Malzahar, so what do we do? We use the Dynamic class with a magic constructor method:

Dynamic::honey(),

Passing attributes to the dynamic component is as simple as adding a chained method:

Dynamic::honey()->recaptcha(),

It all works similarly to other Malzahar components. With dynamic components, the constructor method is the name of the component itself, and attributes are passed the same way you would with HTML or other components.

Check out the NavLink example that was created when you installed Malzahar. You can even see Dynamic components utilizing the merge($this) method inside a custom Blade component:

class NavLink extends Blade
{
    public $icon, $route, $title;

    public function attributes()
    {
        return [
            'name' => 'heroicon-o-' . $this->icon,
        ];
    }

    public function classes()
    {
        return [
            'w-6 h-6',
            'text-gray-600 hover:text-black' => Route::currentRouteName() != $this->route,
            'text-blue-600' => Route::currentRouteName() == $this->route,
        ];
    }

    public function template()
    {
        return Html::a(
            Dynamic::icon()->merge($this),
        )->href(route($this->route))->title($this->title);
    }
}

Statements

If Statement

Conditional if statements with Malzahar are easy:

Statement::if($this->label,
    fn() => Html::label($this->label),
),

You can also use elseif and else as chained methods for your if statement:

public $color = 'blue';

public function template()
{
    return Statement::if($this->color == 'red',
        fn() => Html::h1(__('The color is red!'))
            ->class('text-red-600'),
    )->elseif($this->color == 'blue',
        fn() => Html::h2(__('The color is blue!'))
            ->class('text-blue-600'),
    )->else(
        fn() => Html::h3(__('The color is something else!')),
    );
}

Notice how the first parameter of if is the condition, and every closure after is what will be rendered if the statement passes.

Each Statement

Each statements, or loops as they're often called, allow you to iterate through a result set and display things per iteration.

For example, let's say we want to spit out a list of our users names:

Statement::each(User::all(),
    fn(User $user) => Html::div(e($user->name)),
)->empty(
    fn() => Html::p('No users found.'),
),

The first parameter of the each statement is the collection or array. The second parameter is a callable function with the item, and optionally the key. You can use the empty method to show something if no results are found.

Also, notice how the e function is used here to escape the users name. Normally, you'd use the {{ $user->name }} syntax in a view file, which literally just calls the e function when compiled.

Need to use the keys of the items? No problem:

Statement::each(['red' => '#ff0000', 'green' => '#00ff00'],
    fn($hexCode, $colorName) => Html::dl(
        Html::dt($colorName),
        Html::dd($hexCode),
    )->class('mb-4'),
),

If you have questions or need help, please use the Github issues and I will respond ASAP. Thank you for checking out this package and happy coding!

Comments
  • [Conversation]Jetstream Support

    [Conversation]Jetstream Support

    Opening this as a conversion to discuss steps needed to support Jetstream.

    This is support I've added in a personal project, not my fork of Malzahar.

    I've installed and customized all the default models (via publishing stubs) to support Jetstream:

    • User (Laravel)
    • Team (Jetstream)
    • Membership (Jetstream)
    • TeamInvitation (Jetstream)
    • PersonalAccessToken (Sanctum)

    Added support to UserSeeder to create teams for each user that's seeded:

    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        User::factory()->withPersonalTeam()->create([
            'email' => '[email protected]',
        ]);
        
        User::factory(29)->withPersonalTeam()->create();
    }
    

    The problem I'm seeing is that there are major conflicts with the native Jetstream system since we've changed configuration values for the default Livewire that's installed.

    Because all the components and controllers for Jetstream come directly from the package itself and aren't publishable, and Jetstream exports it's own routes, everything from Malzahar is overwritten and not accessed.

    Getting a lot of this error:

    Livewire\Exceptions\CorruptComponentPayloadException Livewire encountered corrupt data when trying to hydrate the [laravel.jetstream.http.livewire.update-profile-information-form] component. Ensure that the [name, id, data] of the Livewire component wasn't tampered with between requests. http://jetmalz.test/user/profile

    I think we'll need to create a separate package that includes local components to replace all the Jetstream default components and controllers.

    Let me know your thoughts.

    opened by ghost 12
  • [Question] Priorities

    [Question] Priorities

    Kevin do you have a roadmap or anything?

    I know you're working on crud and auth.

    Maybe a slack channel or should I just make issues for things I think would be improvements?

    If I had some direction on things you have in mind I can start making those happen.

    Thanks

    opened by ghost 12
  • [Bug] Elseif in IfStatement Not working

    [Bug] Elseif in IfStatement Not working

    In the Home component I created a property for $color and then a constructor to set it

    public string $color;
    
    public function __construct()
    {
        $this->color = 'blue';
    }
    

    Then using your example if statement from the README in my template function I placed this:

    Statement::if($this->color == 'red',
        Html::h1(__('The color is red!'))
            ->class('text-red-600'),
    )->elseif($this->color == 'blue',
        Html::h2(__('The color is blue!'))
            ->class('text-blue-600'),
    )->else(
        Html::h3(__('The color is something else!')),
    )
    

    When rendering the page I get

    The color is something else!
    

    Perhaps this isn't a bug and just needs to return earlier?

    opened by ghost 12
  • [Question] Resource Methods to Separate Components?

    [Question] Resource Methods to Separate Components?

    Hey Kevin,

    I'm really digging this package so far ... I don't know if you saw my comment on YouTube, but I'm currently testing on Octane and it runs fantastically so far. The fact that your service provider doesn't bind anything to the container certainly helps.

    I'm assuming that the way you have components built, I'll need to create a separate component for each resourceful controller method.

    ie: given I have model for Product in a products table:

    App\Components\Livewire\Products\Index.php
    App\Components\Livewire\Products\Show.php
    App\Components\Livewire\Products\Edit.php
    App\Components\Livewire\Products\Store.php
    App\Components\Livewire\Products\Update.php
    App\Components\Livewire\Products\Destroy.php
    App\Components\Livewire\Products\Restore.php
    

    I very much like this approach anyway as I prefer an ADR style framework where my action would be responsible for just receiving a request, then passing that request to the domain layer to be handled, and returning a payload to a responder.

    Will you be accepting pull requests? I feel like just from this initial look there are some improvements that can be made, interfacing, publishing stubs, adding a dedicated config file so that config settings in the provider can be configured by the user and pulled into the commands.

    Honestly it's really surprising how much this package accomplishes with very little code.

    It's very impressive my friend.

    Let me know if I can collaborate with you on this.

    Great job!

    opened by ghost 8
  • Fresh Install Package Not Found

    Fresh Install Package Not Found

    Fresh install gives [InvalidArgumentException] Could not find a matching version of package bastinald/malzahar. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (dev).

    opened by ChefBrett 2
  • [Question] Type for items prop in EachStatement::class

    [Question] Type for items prop in EachStatement::class

    I'm done with type declarations and docblocks across the package with the exception of the EachStatement

    Currently the $items prop is able to accept either an array or a Collection

    I'm wondering if it wouldn't be better to declare this as a Collection since we're using Laravel. This would require a user to wrap any raw array in collect() before passing it in, but I think having it as typed declaration is better than worrying about this one tiny extra step.

    I'm gonna submit the pull request this way and document it, if you don't want it that way then of course you can edit the request.

    opened by ghost 1
  • Proposed updates for 1.0.4

    Proposed updates for 1.0.4

    You can see the proposed changes in the diff and I listed them on the new changelog file.

    I didn't link the changelog to releases/tags but I can add that later if you like.

    Everything has been tested by hand, I don't know if you're planning on writing tests?

    But everything should work.

    Please test.

    opened by ghost 0
  • Fresh install gives view issue

    Fresh install gives view issue

    Bastinald\Malzahar\Components\Blade::attribute(): Return value must be of type Bastinald\Malzahar\Traits\AttributesAndClasses, App\Components\Blade\Forms\Input returned

    opened by iamlasse 5
Releases(1.0.7)
Owner
null
🍃Termwind allows you to build unique and beautiful PHP command-line applications, using the Tailwind CSS API

Termwind allows you to build unique and beautiful PHP command-line applications, using the Tailwind CSS API. In short, it's like Tailwind CSS, but for the PHP command-line applications.

Nuno Maduro 1.8k Jan 3, 2023
Low-code Framework for Web Apps in PHP

Agile UI - User Interface framework for Agile Toolkit Agile Toolkit is a Low Code framework written in PHP. Agile UI implement server side rendering e

Agile Toolkit 404 Jan 8, 2023
Leaf is a PHP framework that helps you create clean, simple but powerful web apps and APIs quickly and easily.

Leaf is a PHP framework that helps you create clean, simple but powerful web apps and APIs quickly and easily. Leaf introduces a cleaner and much simpler structure to the PHP language while maintaining it's flexibility. With a simple structure and a shallow learning curve, it's an excellent way to rapidly build powerful and high performant web apps and APIs.

Leaf Framework 706 Jan 3, 2023
Hamtaro - the new web framework for front-end / back-end development using Php and Javascript.

Hamtaro framework About Technologies Controllers Components Commands Front-end development Getting Started About Hamtaro is the new web framework for

Phil'dy Jocelyn Belcou 3 May 14, 2022
Production ready Dockerfile for Octane powered Laravelish web services and microservices

Laravel Octane Dockerfile A pretty configurable and production ready multi-stage Dockerfile for Octane powered Laravelish web services and microservic

Exalab.co 245 Dec 26, 2022
A powerful yet easy-to-use PHP micro-framework designed to help you build dynamic and robust Web applications - fast!

A powerful yet easy-to-use PHP micro-framework designed to help you build dynamic and robust Web applications - fast! Condensed in a single ~65KB file

Bong Cosca 2.6k Dec 30, 2022
TrailLamp is a lightweight, easy-to-use Php MVC framework that can be used to build web applications and REST APIs.

TrailLamp Introduction TrailLamp is a lightweight, easy-to-use Php MVC framework that can be used to build web applications and REST APIs. Installatio

Etorojah Okon 14 Jun 10, 2022
A Small modular PHP framework Build for web applications

MagmaCore__ This project is in active development. So is evolving constantly. As soon project gets to stable point. Due notice will be given Composer

Ricardo Miller 10 Sep 2, 2022
Bugsnag notifier for the Symfony PHP framework. Monitor and report errors in your Symfony apps.

Bugsnag exception reporter for Symfony The Bugsnag Notifier for Symfony gives you instant notification of errors and exceptions in your Symfony PHP ap

Bugsnag 43 Nov 22, 2022
A simple PHP MVC framework without extra files and codes that you don't need

Welcome to (SPM) Simple PHP MVC, just what you need! This is a simple PHP MVC framework without extra files and codes that you don't need.

Van Hudson Galvoso 5 Sep 17, 2022
This Slim Framework middleware will compile LESS CSS files on-the-fly using the Assetic library

This Slim Framework middleware will compile LESS CSS files on-the-fly using the Assetic library. It supports minification and caching, also via Asseti

Gerard Sychay 22 Mar 31, 2020
FlyCubePHP is an MVC Web Framework developed in PHP and repeating the ideology and principles of building WEB applications, embedded in Ruby on Rails.

FlyCubePHP FlyCubePHP is an MVC Web Framework developed in PHP and repeating the ideology and principles of building WEB applications, embedded in Rub

Anton 1 Dec 21, 2021
PHP slim framework middleware to minify HTML output

slim-minify Slim middleware to minify HTML output generated by the slim PHP framework. It removes whitespaces, empty lines, tabs beetween html-tags an

Christian Klisch 35 Oct 31, 2021
Javascript Minifier built in PHP

JShrink JShrink is a php class that minifies javascript so that it can be delivered to the client quicker. This code can be used by any product lookin

Robert Hafner 25 Oct 8, 2022
JShrink is a php class that minifies javascript so that it can be delivered to the client quicker

JShrink JShrink is a php class that minifies javascript so that it can be delivered to the client quicker. This code can be used by any product lookin

Tedious Developments 677 Nov 16, 2022
Rori-PHP is custom non production web application framework inspired by Laravel syntax

Rori-PHP is custom non production web application framework inspired by Laravel syntax. A web framework provides a structure and starting point for your application allowing you to focus on creating something amazing.

UnknownRori 5 Jul 28, 2022
💫 Vega is a CLI mode HTTP web framework written in PHP support Swoole, WorkerMan / Vega 是一个用 PHP 编写的 CLI 模式 HTTP 网络框架,支持 Swoole、WorkerMan

Mix Vega 中文 | English Vega is a CLI mode HTTP web framework written in PHP support Swoole, WorkerMan Vega 是一个用 PHP 编写的 CLI 模式 HTTP 网络框架,支持 Swoole、Work

Mix PHP 46 Apr 28, 2022
Yii 2 widget for the Froala WYSIWYG HTML Editor.

Yii Framework Froala WYSIWYG HTML Editor Yii 2 widget for Froala Wysiwyg editor. Installation The preferred way to install this extension is through c

Froala 99 Sep 21, 2022