jhuddle/framework

极简PHP框架

v0.1.1 2023-10-26 22:20 UTC

This package is auto-updated.

Last update: 2024-09-27 00:25:42 UTC


README

需要PHP 8.1.0或更高版本

极简PHP,呃,框架。

哦不,又是一个...

这是一个合理的观点 - 但这个框架确实非常小,非常实用,保证。

有多小?

不到10 KB。你几乎感觉不到它的存在。

那么,我该如何开始呢?

让我们从最基本的地方开始;这是一个非常好的开始地方。从你的项目根目录

  • 使用 Composer 安装它

    composer require jhuddle/framework
  • 然后,举例来说,创建以下文件

    templates/layout.php

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title><?= $title ?></title>
      </head>
      <body>
        <header>
          <h1><?= $title ?></h1>
        </header>
        <main>
          <?= $content ?>
        </main>
      </body>
    </html>

    templates/partials/list.php

    <ul>
      <?php foreach($items as $item): ?>
        <li><?= $item ?></li>
      <?php endforeach; ?>
    </ul>

    public/index.php

    <?php
    
    // Adjust paths to match your project structure:
    
    require_once __DIR__ . '/../vendor/autoload.php';
    require_once __DIR__ . '/../app/app.php';

    app/app.php

    <?php
    
    // Instantiate app and define it as a constant:
    
    define('app', new \Framework\App([
      'view' => dir('../templates'),
    ]));
    
    // Declare routes:
    
    app->route->get('/',
      function () {
        print app->view('layout', [
          'title' => "Home page",
          'content' => "This is the home page.",
        ]);
      }
    );
    
    app->route->get('/hello/<name?>',
      fn ($name = 'world') => print app->view('layout', [
        'title' => "Hello, $name",
        'content' => null,
      ])
    );
    
    app->route->get('/goodbye/<goodbye?string>/<times:int>',
      fn ($times, $goodbye = 'goodbye') => print app->view('layout', [
        'title' => "Goodbye...",
        'content' => str_repeat(
          "<p>So long, farewell, auf Wiedersehen, $goodbye!</p>",
          $times
        ),
      ])
    );
    
    app->route->get('/favorite-things',
      fn () => print app->view('layout', [
        'title' => "A few of my favorite things",
        'content' => app->view('partials/list', [
          'items' => [
            "raindrops on roses",
            "whiskers on kittens",
            "bright copper kettles",
            "warm woolen mittens",
            "brown paper packages tied up with strings",
          ],
        ]),
      ])
    );
    
    app->route->post('/apply-now',
      function () {
        $data = json_decode(app->input);
        $name = htmlspecialchars(@$data->name);
        $role = htmlspecialchars(@$data->role);
        if ($name && $role) {
          print app->view('layout', [
            'title' => "Thank you, $name",
            'content' => "Your application for the role of $role is being considered."
          ]);
        } else {
          http_response_code(400);  // Bad Request
          print app->view('layout', [
            'title' => "Sorry",
            'content' => "There was a problem with your application; please try again."
          ]);
        }
      }
    );
    
    // Fallback:
    
    http_response_code(404);  // Not Found
    print '<h1>404 Not Found :(</h1>';
  • 启动一个指向 public/index.php 的Web服务器

    • 对于开发,最简单的方法是运行内置服务器
      php -S localhost:8000 -t public
    • 对于生产环境,你可能需要设置 nginxApache
  • 然后,从你的基本URL(例如 https://:8000),在浏览器中选择尝试一些路由

    /
    /hello
    /hello/captain
    /goodbye/5
    /goodbye/adieu/3
    /goodbye/goodnight
    /favorite-things
    

    或使用 curl

    curl localhost:8000/apply-now -d '{"name": "Maria", "role": "Governess"}'

请注意,在每种情况下,路由处理方法 get()post() - 或 put()patch()delete(),无论你需要什么 - 都接受两个参数

  • 路由模式,可能包含尖括号内的参数:这些参数可以用 ? 使其可选,并/或使用PHP标量类型名称作为语法提示(尽管出于安全原因,不会自动执行类型强制/URL解码)
  • 回调函数,具有与路由模式中对应的命名参数,其中可以使用应用程序的 input 属性来检查请求正文

希望你现在可能已经知道它是如何工作的了...

是的,看起来很简单 - 那么 'view' => dir('../templates') 是怎么回事呢?

这告诉你的 App 实例在哪里可以找到生成网站HTML的PHP模板;框架不期望任何特定的文件夹结构,所以你需要告诉它从哪个基本文件夹开始查找。实际上,数组键甚至不必命名为 view:你可以称它为 templatehtml 或你喜欢的任何其他名称!你甚至可以有多个数组键,每个键都指向不同的基本文件夹。

无论你选择什么名称,都会用来创建创建 的方法,这些类会包含传递给它的路径建议的文件,并提取可选的变量名称和值数组以供该文件使用,例如

<?php

define('app', new \Framework\App([
  'layout' => dir('../templates/layouts'),
  'partial' => dir('../templates/partials'),
]));

print app->layout('song/lyrics', [
  'title' => "The Lonely Goatherd",
  'content' => implode("<br>", [
    "High on a hill was a lonely goatherd,",
    app->partial('yodel'),
    "Loud was the voice of the lonely goatherd,",
    app->partial('yodel'),
    "Folks in a town that was quite remote heard",
    app->partial('yodel'),
    "Lusty and clear from the goatherd's throat, heard",
    app->partial('yodel'),
  ]),
]);

但这还不止这些!除了包裹在 dir() 中的基本文件夹,你可以在任何时间向应用程序实例添加/覆盖任何内容

<?php

define('app', new \Framework\App([
  'notes' => ["Do", "Re", "Mi",],
  'lineFrom' => fn ($a, $b) => "$a - $b,\n",
]));

print "When you read you begin with A-B-C.\n";
print "When you sing you begin with " . implode("-", app->notes) . ".\n";

app->use([
  'notes' => ["Do", "Re", "Mi", "Fa", "So", "La", "Ti",],
  'memory_aids' => [
    "a deer, a female deer",
    "a drop of golden sun",
    "a name I call myself",
    "a long, long way to run",
    "a needle pulling thread",
    "a note to follow So",
    "a drink with jam and bread",
  ],
]);

foreach (array_combine(app->notes, app->memory_aids) as $note => $memory_aid) {
  print app->lineFrom($note, $memory_aid);
}
print "That will bring us back to " . app->notes[0] . "!";

可爱。它还能做什么?

它不仅处理请求,还可以发起请求

<?php

define('app', new \Framework\App());

app->route->get('/html',
  fn () => print app->request->get(
    'https://github.com/jhuddle/framework/blob/main/README.md'
  )
);

app->route->get('/json/<data:string>',
  function ($data) {
    $round_trip = app->request->post(
      'https://httpbin.org/anything',
      [
        'Content-Type' => 'text/plain',
        'X-Powered-By' => 'jhuddle/framework',
      ],
      $data
    );
    print "<h1>" . $round_trip->headers['Content-Type'] . "</h1>";
    print "<pre>" . $round_trip . "</pre>";
  }
);

幕后,routerequest 对象以与上面相同的方式加载到应用程序中——因此,如果您想使用不同的实现,可以在构造函数或使用 use() 方法中覆盖它们。

同样适用于 db —— 如果执行上下文中存在正确的环境变量,框架将基于 DBMS 的值建立 PDO 连接,使用 DB_HOSTDB_PORTDB_NAMEDB_USERDB_PASS 作为凭证。内置实现包含了一些常见的辅助方法,用于处理预编译语句和事务,但您可以根据需要用另一个实现来覆盖 db

<?php

// Using default implementation with environment variables
// `DBMS=mariadb` + `DB_` credentials (see above):

define('app', new \Framework\App());

$something_good = app->db->execute(
  '
    SELECT thing
    FROM youth INNER JOIN childhood ON youth.person_id = childhood.person_id
    WHERE thing LIKE ?
    LIMIT 1
  ',
  ['%good%']
);

// Switching to SQLite:

app->use([
  'db' => new \PDO('sqlite:nothing.sqlite')
]));

$query = app->db->query('
  SELECT thing
  FROM youth INNER JOIN childhood ON youth.person_id = childhood.person_id
  WHERE thing IS NULL
');

$nothing = $query->fetchAll(\PDO::FETCH_OBJ);

还有 session,这是一个非常简单的内置 PHP 会话管理函数的包装器——默认情况下,除非将 false 作为第二个参数传递给 App 构造函数,否则框架将启动会话。

好的,听起来不错——这就是全部了吗?

是的,但也不完全是这样!我们已经接触到了构成此框架的六个小类,但您还可以用它们做更多的事情...

  • 您使用 dir() 创建的类可以存储在变量中,并使用 use() 方法扩展,就像应用程序本身一样。

    templates/mantra.php

    <p>I have <?= $attribute ?> in <?= $item ?>!</p>

    app/app.php

    <?php
    
    define('app', new \Framework\App([
      'view' => dir('../templates'),
    ]));
    
    $confidence = app->view('mantra', [
      'attribute' => 'confidence',
      'item' => 'me',
    ]);
    
    app->route->get('/confidence/<item>',
      fn ($item) => print $confidence->use(['item' => $item])
    );
  • 这些类也可以被调用!您可以使用这一事实来简化路由回调。

    templates/hills.php

    <p>The hills are alive with the sound of <?= $noise ?>...</p>

    app/app.php

    <?php
    
    define('app', new \Framework\App([
      'view' => dir('../templates'),
    ]));
    
    app->route->get('/sound/<noise?>',
      app->view('hills', ['noise' => 'music'])
    );
  • 模板也可以创建和调用这些类。

    templates/layout.php

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title><?= $title ?></title>
      </head>
      <body>
        <header>
          <?= app->view('partials/header', ['title' => $title]) ?>
        </header>
        <main>
          <?= $content ?>
        </main>
        <footer>
          <?= app->view('partials/footer') ?>
        </footer>
      </body>
    </html>

    templates/partials/header.php

    <h1><?= $title ?></h1>

    templates/partials/footer.php

    <aside>Fun fact: not the national anthem of Austria.</aside>

    app/app.php

    <?php
    
    define('app', new \Framework\App([
      'view' => dir('../templates'),
    ]));
    
    app->route->get('/edelweiss',
      app->view('layout', [
        'title' => "Edelweiss",
        'content' => "<p>Every morning you greet me!<p>",
      ])
    );
  • 可以将路由方法链接在一起,并且/或者可以添加前缀。

    <?php
    
    class RelationshipController {
    
      public static function depend() {
        print "I'll depend on you";
      }
    
      public static function takeCare() {
        print "I'll take care of you";
      }
    
      public static function prepare(string $men) {
        $sanitized_men = htmlspecialchars(urldecode($men));
        print "$sanitized_men - what do I know of those?";
      }
    
    }
    
    define('app', new \Framework\App());
    
    app->route->prefix('sixteen')
      ->get('/seventeen', [RelationshipController::class, 'depend'])
      ->get('/eighteen', [RelationshipController::class, 'takeCare'])
      ->get('/<men:string>', [RelationshipController::class, 'prepare'])
    ;

可能还有许多其他尚未想到的用例的可用模式...这个项目的目标是编写最小的、最少偏见、最可扩展和最有用的 PHP 框架,所以从现在起,就靠您和您的想象力了!

太棒了,谢谢!我如何贡献?

您对这个框架的最佳贡献就是简单地使用它,并报告任何问题...如果您用它构建了酷炫的东西,请通过在 Projects 文件中添加指向您的 GitHub 仓库的链接来让我知道。我会很高兴看到您的成果。🙂