PHPackages                             oscarotero/folk - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. oscarotero/folk

ActiveLibrary[HTTP &amp; Networking](/categories/http)

oscarotero/folk
===============

Universal CMS

v3.7.4(4y ago)91.5k2[1 issues](https://github.com/oscarotero/folk/issues)1MITPHPPHP ^7.2|^8.0CI failing

Since Jan 4Pushed 4y ago3 watchersCompare

[ Source](https://github.com/oscarotero/folk)[ Packagist](https://packagist.org/packages/oscarotero/folk)[ Docs](https://github.com/fol-project/fol)[ RSS](/packages/oscarotero-folk/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (29)Versions (75)Used By (1)

FOLK
====

[](#folk)

The universal CMS

[![Scrutinizer Code Quality](https://camo.githubusercontent.com/7ffe488b0bfffba94c91dc52e09056d4b115a83d7044240d252570c6461c29fc/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f6f736361726f7465726f2f666f6c6b2f6261646765732f7175616c6974792d73636f72652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/oscarotero/folk/?branch=master)

This is a framework-agnostic CMS that you can use to edit the content of your site. It works with any kind of websites, no matter if the content is stored in a database, yml files, json, etc.

Demo:

[![List view](https://raw.githubusercontent.com/oscarotero/folk/master/list-screenshoot.png)](https://raw.githubusercontent.com/oscarotero/folk/master/list-screenshoot.png)[![Edit view](https://raw.githubusercontent.com/oscarotero/folk/master/edit-screenshoot.png)](https://raw.githubusercontent.com/oscarotero/folk/master/edit-screenshoot.png)

Requirements
------------

[](#requirements)

- PHP &gt;= 7
- Composer

Installation
------------

[](#installation)

This package is installable and autoloadable via Composer as [oscarotero/folk](https://packagist.org/packages/oscarotero/folk).

```
composer require oscarotero/folk

```

Entities
--------

[](#entities)

The entities are classes to manage "things". It can be a database table, a file, a directory with files, etc. They must implement `Folk\Entities\EntityInterface` (or extend `Folk\Entities\AbstractEntity`). Let's see an example of an entity using a database table.

```
namespace MyEntities;

use Folk\SearchQuery;
use Folk\Formats\FormatFactory;
use Folk\Formats\Group;
use Folk\Entities\AbstractEntity;

/**
 * Entity to manage the posts
 */
class Posts extends AbstractEntity
{
    public $title = 'Posts';
    public $description = 'These are the posts of the blog';

    /**
     * List the posts
     *
     * @return array [id => data, ...]
     */
    public function search(SearchQuery $search): array
    {
        $query = 'SELECT * FROM posts';

        if ($search->getPage() !== null) {
            $limit = $search->getLimit();
            $offset = ($search->getPage() * $limit) - $limit;
            $query .= " LIMIT {$offset}, {$limit}";
        }

        $pdo = $this->admin->get('pdo');
        $result = $pdo->query($query);

        $data = [];

        while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
            $data[$row['id']] = $row;
        }

        return $data;
    }

    /**
     * Create a new post
     *
     * @return mixed The post id
     */
    public function create(array $data)
    {
        $pdo = $this->admin->get('pdo');

        $statement = $pdo->prepare('INSERT INTO posts (title, text) VALUES (:title, :text)');
        $statement->execute([
            ':title' => $data['title'],
            ':text' => $data['text'],
        ]);

        return $pdo->lastInsertId();
    }

    /**
     * Read a post
     *
     * @return array
     */
    public function read($id): array
    {
        $pdo = $this->admin->get('pdo');

        $statement = $pdo->prepare('SELECT * FROM posts WHERE id = ? LIMIT 1');
        $statement->execute([$id]);

        return $statement->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * Update a post
     */
    public function update($id, array $data)
    {
        $pdo = $this->admin->get('pdo');

        $statement = $pdo->prepare('UPDATE posts SET title = :title, text = :text WHERE id = :id LIMIT 1');
        $statement->execute([
            ':title' => $data['title'],
            ':text' => $data['text'],
            ':id' => $data['id'],
        ]);
    }

    /**
     * Delete a post
     */
    public function delete($id)
    {
        $pdo = $this->admin->get('pdo');

        $statement = $pdo->prepare('DELETE FROM posts WHERE id = ? LIMIT 1');
        $statement->execute([$id]);
    }

    /**
     * Returns the data scheme used by the posts.
     */
    public function getScheme(FormatFactory $factory): Group
    {
        return $factory->group([
            'title' => $factory->text()
                ->maxlength(200)
                ->label('The post title'),

            'text' => $factory->html()
                ->label('The body'),
        ]);
    }

    /**
     * Returns the label of a row.
     * (used in autocomplete searches, select, etc)
     */
    public function getLabel($id, array $data): string
    {
        return sprintf('%s (%d)', $data['title'], $id);
    }
}
```

Getting started
---------------

[](#getting-started)

There's some predefined entities that you can extend and configure, for example to use with [simplecrud](https://github.com/oscarotero/simple-crud), or to save the content in yaml or json files, etc.

Once your entities are created, let's make them to run:

```
use Folk\Admin;

use Entities\Posts;

//Create a Admin instance passing the root path and the http uri:
$uri = new Zend\Diactoros\Uri('http://my-site.com/admin');
$admin = new Admin(__DIR__, $uri);

//Set the pdo instance:
$admin['pdo'] = new PDO('mysql:dbname=database;charset=UTF8');

//Add set your entities classes
$admin->setEntities([
    Posts::class
]);

//Run the web (using PSR-7 request/responses)
$request = Zend\Diactoros\ServerRequestFactory::fromGlobals();
$emitter = new Zend\Diactoros\Response\SapiEmitter();

$response = $admin($request);
$emitter->emit($response);
```

As you can see, this is a simple example with a simple mysql table. But the interface is flexible enought to work with any kind of data.

To know how to work with the scheme, visit [form-manager](https://github.com/oscarotero/form-manager/) project.

###  Health Score

38

—

LowBetter than 84% of packages

Maintenance18

Infrequent updates — may be unmaintained

Popularity23

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity83

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 99.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~30 days

Recently: every ~355 days

Total

72

Last Release

1605d ago

Major Versions

1.x-dev → v2.0.02016-10-03

v2.5.0 → v3.0.02017-01-10

PHP version history (4 changes)v1.0.0PHP &gt;=5.5.0

v3.0.0PHP ^7.0

v3.7.2PHP ^7.2

v3.7.4PHP ^7.2|^8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/e99df56b617f4af8cf0556a51a0ca20c7420104920c57d7e9eab093f92bb744f?d=identicon)[oscarotero](/maintainers/oscarotero)

---

Top Contributors

[![oscarotero](https://avatars.githubusercontent.com/u/377873?v=4)](https://github.com/oscarotero "oscarotero (184 commits)")[![filisko](https://avatars.githubusercontent.com/u/8798694?v=4)](https://github.com/filisko "filisko (1 commits)")

---

Tags

agnostic-to-frameworkscmscontent-managementpsr-7cmscrudadmin

###  Code Quality

Code StylePHP CS Fixer

### Embed Badge

![Health badge](/badges/oscarotero-folk/health.svg)

```
[![Health](https://phpackages.com/badges/oscarotero-folk/health.svg)](https://phpackages.com/packages/oscarotero-folk)
```

###  Alternatives

[middlewares/fast-route

Middleware to use FastRoute

96191.1k15](/packages/middlewares-fast-route)[middlewares/negotiation

Middleware to implement content negotiation

47442.1k11](/packages/middlewares-negotiation)[middlewares/trailing-slash

Middleware to normalize the trailing slash of the uri path

32506.3k11](/packages/middlewares-trailing-slash)[middlewares/payload

Middleware to parse the body of the request with support for json, csv and url-encode

32466.8k17](/packages/middlewares-payload)[mezzio/mezzio-authentication-oauth2

OAuth2 (server) authentication middleware for Mezzio and PSR-7 applications.

28483.0k2](/packages/mezzio-mezzio-authentication-oauth2)[mezzio/mezzio-authentication

Authentication middleware for Mezzio and PSR-7 applications

121.6M26](/packages/mezzio-mezzio-authentication)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
