Yoyo
Yoyo is a full-stack PHP framework to create rich, dynamic interfaces using server-rendered HTML. You keep on writing PHP and let Yoyo make your creations come alive.
Install / Use
/learn @clickfwd/YoyoREADME
Yoyo
Yoyo is a full-stack PHP framework that you can use on any project to create rich dynamic interfaces using server-rendered HTML.
With Yoyo, you create reactive components that are seamlessly updated without the need to write any Javascript code.
Yoyo ships with a simple templating system, and offers out-of-the-box support for Blade, without having to use Laravel, and Twig.
Inspired by Laravel Livewire and Sprig, and using htmx.
🚀 Yoyo Demo Apps
Check out the Yoyo Demo App to get a better idea of what you can build with Yoyo. It showcases many different types of Yoyo components. You can also clone and install the demo apps:
Documentation
- How it Works
- Installation
- Updating
- Configuring Yoyo
- Creating Components
- Rendering Components
- Properties
- Actions
- View Data
- Computed Properties
- Events
- Redirecting
- Component Props
- Query String
- Loading States
- Using Blade
- Using Twig
- License
How it Works
Yoyo components are rendered on page load and can be individually updated, without the need for page-reloads, based on user interaction and specific events.
Component update requests are sent directly to a Yoyo-designated route, where it processes the request and then sends the updated component HTML partial back to the browser.
Yoyo can update the browser URL state and trigger browser events straight from the server.
Below you can see what a Counter component looks like:
Component class
# /app/Yoyo/Counter.php
<?php
namespace App\Yoyo;
use Clickfwd\Yoyo\Component;
class Counter extends Component
{
public $count = 0;
protected $props = ['count'];
public function increment()
{
$this->count++;
}
}
Component template
<!-- /app/resources/views/yoyo/counter.php -->
<div>
<button yoyo:get="increment">+</button>
<span><?php echo $count; ?></span>
</div>
Yes, it's that simple! One thing to note above is the use of the protected property $props. This indicates to Yoyo that the count variable, which is not explicitly available within the template, should be persisted and updated in every request.
Installation
Install the Package
composer require clickfwd/yoyo
Phalcon Framework Installation
For phalcon, you need to add di
$di->register(new \Clickfwd\Yoyo\YoyoPhalconServiceProvider());
and you need to add router:
$router->add('/yoyo', [
'controller' => 'yoyo',
'action' => 'handle',
]);
and you should create a controller and inherit from Clickfwd\Yoyo\PhalconController class.
Updating
After performing the usual composer update, remember to also update the yoyo.js script per the Load Assets instructions.
Configuring Yoyo
It's necessary to bootstrap Yoyo with a few configuration settings. This code should run when rendering and updating components.
use Clickfwd\Yoyo\View;
use Clickfwd\Yoyo\ViewProviders\YoyoViewProvider;
use Clickfwd\Yoyo\Yoyo;
$yoyo = new Yoyo();
$yoyo->configure([
'url' => '/yoyo',
'scriptsPath' => 'app/resources/assets/js/',
'namespace' => 'App\\Yoyo\\'
]);
// Register the native Yoyo view provider
// Pass the Yoyo components' template directory path in the constructor
$yoyo->registerViewProvider(function() {
return new YoyoViewProvider(new View(__DIR__.'/resources/views/yoyo'));
});
'url'
Absolute or relative URL that will be used to request component updates.
'scriptsPath'
The location where you copied the yoyo.js script.
'namespace'
This is the PHP class namespace that will be used to discover auto-loaded dynamic components (components that use a PHP class).
If the namespace is not provided or components are in different namespaces, you need to register them manually:
$yoyo->registerComponents([
'counter' => App\Yoyo\Counter::class,
];
You are required to load the component classes at run time, either using a require statement to load the component's PHP class file, or by including your component namespaces in you project's composer.json.
Anonymous components don't need to be registered, but the template name needs to match the component name.
Dependency Injection Container
Yoyo includes a built-in container and automatically detects Laravel's illuminate/container if installed.
Simple dependency injection:
class UserProfile extends Component
{
public function mount(UserRepository $users, $userId)
{
$this->user = $users->find($userId);
}
}
For advanced container features, install illuminate/container (automatically detected):
composer require illuminate/container
Using a custom PSR-11 container:
use Clickfwd\Yoyo\ContainerResolver;
ContainerResolver::setPreferred($myContainer);
Load Assets
Find yoyo.js in the following vendor path and copy it to your project's public assets directory.
/vendor/clickfwd/yoyo/src/assets/js/yoyo.js
To load the necessary scripts in your template add the following code inside the <head> tag:
<?php yoyo_scripts(); ?>
Creating Components
Dynamic components require a class and a template. When using the Blade and Twig view providers, you can also use inline views, where the component markup is returned directly in the component's render method.
Anonymous components allow creating components with just a template file.
To create a simple search component that retrieves results from the server and updates itself, create the component template:
// resources/views/yoyo/search.php
<form>
<input type="text" name="query" value="<?php echo $query ?? ''; ?>">
<button type="submit">Submit</button>
</form>
Yoyo will render the component output and compile it to add the necessary attributes that makes it dynamic and reactive.
When you submit the form, posted data is automatically made available within the component template. The template code can be expanded to show a list of results, or an empty state:
<?php
$query = $query ?? '';
$entries = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
$results = array_filter($entries, function($entry) use ($query) {
return $query && strpos($entry, $query) !== false;
});
?>
<form>
<input type="text" name="query" value="<?php echo $query; ?>">
<button type="submit">Submit</button>
</form>
<ul>
<?php if ($query && empty($results)): ?>
<li>No results found</li>
<?php endif; ?>
<?php foreach ($results as $entry): ?>
<li><?php echo $entry; ?></li>
<?php endforeach; ?>
</ul>
The $results array can be populated from any source (i.e. database, API, etc.)
The example can be converted into a live search input, with a 300ms debounce to minimize the number of requests. Replace the form tag with:
<input yoyo:on="keyup delay:300ms changed" type="text" name="query" value="<?php echo $query; ?>" />
The yoyo:on="keyup delay:300ms change" directive tells Yoyo to make a request on the keyup event, with a 300ms debounce, and only if the input text changed.
Now let's turn this into a dynamic component using a class.
# /app/Yoyo/Search
<?php
namespace App\Yoyo;
use Clickfwd\Yoyo\Component;
class Search extends Component
{
public $query;
protected $queryString = ['query'];
public function render()
{
$query = $this->query;
// Perform your database query
$entries = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
$results = array_filter($entries, function($entry) use ($query) {
return $query && stripos($entry, $query) !== false;
});
// Render the component view
return $this->view('search',['results' => $results]);
}
}
And the template:
<!-- /app/resources/views/yoyo/search.php -->
<input yoyo:on="keyup delay:300ms changed" type="text" name="query" value="<?php echo $query; ?>" />
<ul yoyo:ignore>
<?php if ($query && empty($results)): ?>
<li>No results found</li>
<?php endif; ?>
<?php foreach ($results as $entry): ?>
<li><?php echo $entry; ?></li>
<?php endforeach; ?>
</ul>
A couple of things to note here that are covered in more detail in other sections.
- The component class includes a
queryStringproperty that tells Yoyo to automatically include the queryString values in the browser URL after a component update. If you re-load the page with thequeryvalue in the URL, you'll automatically see the search results on the page. - Yoyo will automatically make available component class public properties as template variables. This allows using
$this->queryto access the search keyword in the component and$queryin the template.
When you compare this search example to the counter example at the beginning, you can see that there are no action methods (i.e. increment, decrement). A component update will always default to the render method, unless an action is specified via one of the method attributes (i.e. yoyo:get, yoyo:post, etc.). In that case, the action method always runs before the render method.
