轻量级的MVC风格的PHP Web应用框架

v1.1.14 2024-07-30 16:20 UTC

README

MVCish

轻量级的MVC风格的PHP Web应用框架


关于项目

这是一个非常轻量级的(因此可能功能较少)MVC框架,类似于Yii这样的东西。

它的存在是因为我继承了一个大型旧遗留系统,我需要迁移、更新和替换。我想要一个可以轻松修改的东西,将遗留页面逐步转换为新的MVC控制的页面。

因此,它有一些同时以多种不同方式运行的能力。

这一切都没有真正文档化,因为这是我为自己编写的。

它没有任何便捷的安装脚本或其他类似的东西。目前还没有。

我不鼓励任何人使用它。但嘿,这是一个自由的世界。如果你喜欢它,那就去试试吧。

我会尽量完善文档。

(返回顶部)

安装

	composer require auntiewarhol/mvcish

(返回顶部)

使用

(这里是从评论中汲取的一些东西。这只是个开始)

对于典型的单点入口使用,你的应用引导脚本可能是这样的

websiteroot/MyApp/myApp.php

<?php
  use \AuntieWarhol\MVCish\MVCish;


  # outside scope of MVCish but we recommend using this with csrf-magic
  # which can be used with beforeRender as shown below, so here's 
  # some setup for that, otherwise unrelated to MVCish

  if (php_sapi_name() != "cli") {

    // instruct PHP to use secure cookie if possible
    // honestly don't remember if for MVCish of csrf or why,
    // but leaving here for now at least...
    $secure = ((!empty($_SERVER['HTTPS'])) && $_SERVER['HTTPS'] !== 'off') ||
        (isset($_SERVER['SERVER_PORT']) && ($_SERVER['SERVER_PORT'] == 443)) ||
	    (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
	  ? true : false;
    session_set_cookie_params(
      ini_get('session.cookie_lifetime'),
      ini_get('session.cookie_path'),
      ini_get('session.cookie_domain'),
      $secure,
      true
    );

    // only do this after setting cookie params
    //function csrf_startup() {
      # configure csrf magic as needed
    //}
    require_once(__DIR__.'/../lib/csrf-magic-1.0.4/csrf-magic.php');
  }

  $MVCish = new \AuntieWarhol\MVCish\MVCish([
    'environment'  => ENV,
    'appDirectory' => __DIR__,

	// We recommend using Propel for the Model
    'MODEL' => [
      'NAMESPACE' => '\Models\\',
      'INIT'      => function($MVCish) {

        require_once __DIR__.'/Propel/generated-conf/config.php';

        if (!empty($serviceContainer)) {
          $serviceContainer->setLogger('defaultLogger', $MVCish->log('Propel'));
          $serviceContainer->setLogger('myApp', $MVCish->log());
        }

        // get the dummy connection config propel created,
        // replace with the values from our config

        $db = $MVCish->Config('DATABASE');
        $connection = \Propel\Runtime\Propel::getConnectionManager('myApp');
        // @phpstan-ignore-next-line
        $config = $connection->getConfiguration();
        // @phpstan-ignore-next-line
        $connection->setConfiguration(array_replace($config,[
          'dsn'      => 'mysql:host='.$db['HOST'].';dbname='.$db['NAME'],
          'user'     => $db['USER'],
          'password' => $db['PASS']
        ]));
      },
    ],

    'beforeRender' => function($MVCish) {

      if (function_exists('csrf_conf')) {
        if ($MVCish->View()->view() == 'html') {

          if (($name  = $GLOBALS['csrf']['input-name']) &&
            ($token = \BF\CSRFMagic::getTokens())
          ) {
            // stash csrf vars where Render can find them. Render will add them 
            // to body_attr, where our js can pick them up to add to ajax calls.
            $MVCish->options['CSRF'] = [
              'data-sectkn-name'  => $name,
              'data-sectkn-token' => $token
            ];
          }
        }
        else {
          //turn off csfr writing
          csrf_conf('rewrite',false);
        }
      }
      return true;
    }
  ]);

  // provide our Auth object to MVCish
  $MVCish->Auth(new \MyApp\Auth($MVCish));

  // If not included by another file, run now
  if(get_included_files()[0] == __FILE__) {
    $MVCish->Run();
  }
?>

遗留文件就地使用将与此类似。进入遗留文件,构建一个如上所示的$MVCish对象,但不是调用Run(),而是按以下描述调用runController()。遗留文件原来执行的任何逻辑都将移至作为参数传递给runController的闭包中执行。

#控制器 ********************

我们可能有一个或没有单点入口;如果有,我们就从URL中确定了您想要的控制器并运行它。否则,URL将您直接带到通常的PHP文件,该文件调用runController,并将'controller'作为一个闭包传递

$MVCish->runController(function() {
  #do controller stuff

  $response = //
    /*
		Response is super flexible. To do it proper, return a Response object:

		$response = $self->Response(); // $self in a controller is $MVCish.

		You can set things after:
		$response->success(true);
		$response->messageSuccess('It worked! Go you!');
		$response->data('something',$toPass);

		Or when creating:
		$response = $self->Response([
			'success'  => true,
			'messages' => ['success' => 'This also worked!'],
			'data'     => ['something' => $toPass,'somethingElse' => $toAlsoPass]
		]);

		In many cases, you'll just take the one you can get back from the form validator:

		$response = $self->Validator()->Response(
			// Validator is a whole other thing to document, but here's a taste...

			'title'        => ['required' => true, 'valid'=>['maxlength'=>100]],
			'instructions' => ['valid'=>['maxlength' =>	$self->Model('Foo')::MAX_FOO_INSTRUCTIONS]],
			'widgets'      => [
				'required' => $someCondition ? true : false,
				'valid'    => function($validator,&$value) use (&$self,&$User) {
					if (!is_array($value)) $value = [$value];
					if (count($value) == 1 && in_array($value[0],['none','all'])) return true;
					if ($self->Model('WidgetQuery')->filterByUser($User)->filterByActive(true)
						->filterByWidgetId($value)->count() == count($value)
					) return true;
					return false;
				},
				'missing' => "Please select which widgets will be used."
			],
			'writable' => ['defaulter' => 'boolfalse'],
			'expires' => [
				'default' => null,
				'valid'=>['date_format_to_iso' => 'm/d/Y'],
				'name'=>'Expiration Date'
			],
		]);

		(If all that passes the validator, $response->success() is now true, $response->valid() 
		will give you the validated form fields, etc. Otherwisem success would be false, 
		$response->missing() and $respons->invalid() will give you arrays of errored fields, etc.)

		---
		
		But you can also just send an array, with at minimum a 'success' key, eg:
		$response = ['success' => true];

		along with any other keys appropriate for the situation. 
		$response = ['success' => true, 'data' => ['something' => $toPass]];

		Or it could just be a bool, in which case we'll convert it
		$response = true;

		For that matter, we'll take any evaluates-true response you send.
		for example other than the typical array, you might send an object
		that can serialize itself for the json view.
		$response = $myJSONobject;

		Or even a text string that's the actual body of the response for the client.
		$response = "Not sure the use case, but there probably is one";
	*/

  return $response;
});

#模型 ************************

'模型'只有非常松散的耦合;只是查找请求的类,可以配置为自动添加命名空间的一部分。示例

$user = $MVCish->Model('\Models\UserQuery'); // returns '\Models\UserQuery'
$user = $MVCish->Model('UserQuery');         // same, if 'Models\' configured as MODEL_NAMESPACE

可以通过将model_initialize函数传递到MVCish选项中,在MVCish启动时为模型执行任何需要的设置工作。参见上面的myApp.php。

路线图:即将到来的更新将允许访问多个模型

#视图 ************************

当前定义的视图

  'html' => true, 'json' => true, 'stream' => true,
  'csv' => true, 'csvexcel' => true, 'xml' => true,
  'text' => true

如果配置的或默认的templateDirectory是MyApp/MVCish/templates,则默认在MyApp/MVCish/templates/controllers中查找主控制器模板,而片段或主模板可能位于MyApp/MVCish/templates/otherdirs中。

默认不使用主模板,只是渲染控制器模板。但子类可能希望定义一个主模板,将控制器HTML插入其中。

客户端应用程序可以使用(预先加载或可自动加载的)子类。这允许应用程序创建一个renderClass,用于准备模板数据或提供主模板。应用程序可以为每个可能不同的网站部分定义一个子类(主页与账户页面、管理页面等)。该类通过MVCish->options提供,因此如果只有一个,可以在app-config.php中提供,如果有多个,则控制器可以在Run选项中设置它所使用的类。该类可以定义如下

namespace \MyApp\Render;
class Account extends \AuntieWarhol\MVCish\View\Render {
   ...override/add methods as needed to render an Account template
}

然后控制器可以使用:$MVCish->Run(function($self){ ...我的控制器代码 },[ 'renderClass' => '\MyApp\Render\Account' ]);

路线图:将更新以使每个视图类型的对象都变得适当,这样客户端将更容易覆盖/扩展内置对象,或创建新的对象。

##授权 **********************************

MVCish不知道任何关于身份验证/授权的事情。但是,如果您在$MVCish->Auth()上设置一个对象(见上面的myApp.php),并且该对象有一个“授权”方法,我们将在运行控制器之前调用它,并将任何作为“授权”选项传递的内容传递给它。如果方法返回true,则表示授权。如果它返回false,我们将抛出一个未经授权的异常。您的对象还可以抛出自己的\AuntieWarhol\MVCish\Exception,如果您想控制消息(或者抛出Forbidden而不是Unauthorized等)。

MyApp/lib/Auth.php

<?php
  namespace MyApp;

  class Auth {
    public function Authorize($authorizeRoles) {
      #do whatever
      #if bad
      return false; #or throw exception
      #if ok
      return true;
    }
  }
}?>

路线图:我不认为Auth是MVC框架应该负责的事情,但我可以理解一个应用框架应该比MVC更大,并且应该处理Auth。将探索添加插件功能,也许会创建一个PHPAuth (https://github.com/PHPAuth/PHPAuth"php::auth) 插件。或者将其作为即将到来的安装脚本的一部分,也许吧。

(返回顶部)

路线图

我过去几周一直非常忙碌,我认为我已经修复了90%我知道的笨拙的和/或未完成的事情。如上所述,我在模型和视图上还有一些工作要做,但我认为这就是清理和前期工作的全部。

我即将将其用于第二个项目的首次尝试,我将尝试使用此过程开发安装/构建启动站点的脚本,并改进文档。

完成这些事情后,我认为这可能已经准备好供公众使用了,如果有人愿意尝试它的话。

未来计划,如我之前提到的,我认为插件架构可能是下一步,以添加我认为不是MVC框架责任,但可能是应用框架责任的东西。Auth是第一个大项目,然后可能是HTML构建工具?

(样板)请参阅开放问题以获取所有建议的功能(以及已知问题)的完整列表。

(返回顶部)

贡献

您可能不应该使用这个,更不用说贡献了。但如果你坚持,我将按照这个模板提供的说明留下,因为,为什么不呢。

--

如果您有改进这个项目的建议,请分叉存储库并创建一个拉取请求。您也可以简单地创建一个带有“增强”标签的问题。别忘了给这个项目加星!再次感谢!

  1. 分叉项目
  2. 创建您的功能分支(git checkout -b feature/AmazingFeature
  3. 提交您的更改(git commit -m '添加一些AmazingFeature'
  4. 推送到分支(git push origin feature/AmazingFeature
  5. 打开拉取请求

(返回顶部)

许可证

在MIT许可证下分发。有关更多信息,请参阅LICENSE.txt

(返回顶部)

联系

项目链接:https://github.com/auntiewarhol/mvcish

(返回顶部)