neemzy / patchwork
PHP 5.4+ 全栈 Web 框架
Requires
- neemzy/patchwork-core: ~1.0
- neemzy/share-extension: dev-master
Requires (Dev)
- behat/mink-extension: ~2.0@dev
- behat/mink-goutte-driver: ~1.1
- behat/mink-selenium2-driver: ~1.2
- phpunit/phpunit: ~4.4
This package is auto-updated.
Last update: 2024-08-26 15:37:46 UTC
README
Patchwork 是一个 PHP 5.4+ 全栈 Web 框架,其主要目的是提供构建 网站 的简约而完整的起点。
目录
哲学
Patchwork 致力于解决 Web 开发的一个子集:为对等公司构建小到中等规模的网站,无论是仅展示数据还是提供一些用户交互(如博客或在线商店)。当大型框架过于沉重且老式 CMS 无法提供所需的灵活性时,Patchwork 就会提供帮助。
Patchwork 不会为每个问题提供另一个不完整的解决方案,而是汇集了最好的 Web 开发工具,并处理样板代码。定义你的数据模型,设置你的业务逻辑,润色 UI,让客户快乐,轻松完成。
Patchwork 在许多类似的项目中精心打造,并将以同样的方式继续发展:实用、简单、易于使用、效率高。
圆桌讨论
包管理
后端
-
Silex:基于 Symfony 组件构建的微型框架,它提供依赖注入容器和基本 Web 应用程序结构。它随 Patchwork 一起提供,并附带多个服务提供商。
-
Twig:Symfony 的 de facto 模板引擎,在 Silex 中默认使用。它随 Patchwork 一起提供,并附带多个扩展。
-
Swift Mailer:Symfony 的 de facto 邮件发送器,在 Silex 中默认使用。
-
RedBean:一种 ORM,其特点是它在编写代码时会自己创建数据库模式。它与 Silex 的集成由一个专门的服务提供商处理,以便将其作为实例而不是静态外观提供。
-
Monolog:PHP 的标准记录器。
-
Environ:一个环境管理器,允许通过布尔回调(通常基于主机名检测)定义环境,并执行特定于环境代码。
-
Image Workshop:一个图像库,用于调整图像大小和裁剪图像。
-
PHPUnit:PHP 的重要单元测试工具之一。
前端
-
gulp:一个用于资产处理和优化的任务运行器,它允许在开发过程中进行实时编译和重新加载。除了以下列出的库外,它还使用了Autoprefixer、CSSO、JSHint、UglifyJS以及图像压缩和字体转换工具。
-
LESS:一个CSS预处理器。
-
Browserify:一个前端JavaScript依赖管理器,允许使用CommonJS的
require
方法以帮助保持良好的代码结构。 -
Bootstrap:由LESS驱动的Twitter HTML/CSS框架,用于构建后台用户界面。
-
Behat和Mink:功能测试工具,可以引导浏览器使用您的应用。运行JavaScript测试需要使用Selenium和PhantomJS。
结构和安装
所有可能以某种方式处理继承/分发的代码(包括PHP类、基本的LESS样式表和NicEdit,一个JavaScript WYSIWYG编辑器)都包含在框架的核心中,可作为单独的包使用。框架的存储库本身主要是一个关于披萨的示例应用,旨在帮助您快速入门。
开始的最佳方式是使用Composer,它将克隆该存储库并安装所有必需的依赖项(请确保首先安装了Composer和NPM)
composer create-project neemzy/patchwork [directory]
安装依赖项后,安装程序将询问您是否要从存储库中删除Git历史记录。您应该同意这一请求,因为您正在构建自己的项目,而不是为该项目做出贡献!
然后您需要检查几个步骤
var/db
和var/log
应该可以被您的Web服务器用户写入。- 您的Web服务器/虚拟主机应该配置正确。在
public
目录中提供了一个示例.htaccess
文件,如果您使用的是Apache。 - 对于开发,应该全局安装gulp(
npm install -g gulp
)。 - 同样,对于开发,还需要安装SQLite和相应的PHP扩展(在Debian和Ubuntu上为
sudo apt-get install sqlite php5-sqlite
)。
此时,您可能想要调整一些配置设置
- 在
gulpfile.js
和behat.yml
中设置您的应用根URL。 - 在
composer.json
和app/config/settings/common.yml
中定义您项目的PHP命名空间。 - 在后者文件中,编辑您应用标题和简短描述。
最后,在应用根目录下运行gulp
,这将使您的浏览器打开您上面选择的URL,并启动一个实时加载服务器,然后开始编码!
目录结构
以下是对Patchwork目录结构的快速浏览,以便您首先了解其工作方式。以下表示安装时的目录结构状态,并不强制要求您在细节上完全遵循它,因为大多数内容可以通过适当的配置进行不同的设置。
app : application data and code
|-- assets : raw assets (LESS stylesheets, JS modules...)
|-- config : YAML configuration files
|-- i18n : translations
|-- settings : per-environment application settings
|-- src : PHP code
|-- Controller : business logic classes
|-- Model : entity classes
|-- tests : automated tests
|-- functional : Behat tests
|-- unit : PHPUnit tests
|-- views : Twig templates
public : web root
|-- assets : compiled assets (CSS stylesheets, single JS file...)
|-- upload : user-uploaded files
var : runtime-specific files
|-- db : SQLite development databases
|-- log : log files
引导文件
拼布的主文件位于 app/bootstrap.php
。此文件由 public/index.php
包含,以引导 Silex 应用程序,然后后者运行。它负责服务和控制器绑定以及一些额外配置。
请注意,以下解释的主题在大多数情况下已在初始设置中预先配置,这里详细说明是为了清晰(帮助您根据需要调整它们)。
设置应用程序的基础路径
使用 $app['base_path'] = dirname(__DIR__);
来公开应用程序的基础路径(FileModel
需要它进行文件上传)。
定义环境
在 Silex 应用程序中注册 Neemzy\Silex\Provider\EnvironServiceProvider
实例,并向其提供 Environment
实例。
use Neemzy\Environ\Environment; use Neemzy\Silex\Provider\EnvironServiceProvider; $app->register( new EnvironServiceProvider( [ 'dev' => new Environment( function () { return preg_match('/localhost|192\.168/', $_SERVER['SERVER_NAME']); }, function () { // development-specific code executed when the app runs } ), 'prod' => new Environment( function () { return true; }, function () { // production-specific code executed when the app runs } ) ] ) ); echo($app['environ']->get()); // 'dev' or 'prod' echo(+$app['environ']->is('prod')); // '0' or '1'
在示例设置中,prod
环境将错误处理器绑定到应用程序(使用 app/views/front/partials/error.twig
模板渲染错误页面)并使用 ETags 创建请求缓存。它还禁用了 $app['debug']
参数。
配置
配置参数是从 app/config/settings
读取的 YAML 文件中读取的,最佳实践是为每个环境定义一个文件(见上面),如果需要,所有这些文件都包含并扩展 common.yml
文件。
它将全部通过 $app['config']
可用,为每个配置级别添加一个数组维度。
toto: tata: tutu: titi # $app['config']['toto']['tata']['tutu'] contains "titi"
翻译
翻译也是从 YAML 文件中读取的,这次来自 app/config/i18n
。由于它们被馈送到一个 Symfony\Component\Translation\Translator
实例,因此最终属于特定的域(允许 Silex 自动检索它们以翻译验证错误消息或日期格式化等)。因此,拼布允许您轻松地正确加载翻译,通过命名您的文件 [domain].[locale].yml
,例如 validators.fr.yml
。
不带域的文件名(如 en.yml
)将加载到默认域,并可能用于通用翻译。
后端开发
视图
视图由 Twig 处理。需要记住的主要事情是您可以通过 app
变量在模板中访问应用程序,这可以让您公开几乎任何您可能需要的东西。Twig 扩展也定义在 app/bootstrap.php
中。
您的 app/views
目录的推荐目录结构如下
admin : back-office templates
|-- [model name] : pages for a model
|-- list.twig : list template
|-- post.twig : form template
|-- layout.twig : back-office layout
front : front-office templates
|-- includes : reusable components (often linked to a stylesheet and maybe a JS module)
|-- partials : pages
|-- layout.twig : front-office layout
资产路径
拼布引入了 Simple Twig Asset Extension
<script src="{{ asset('js/script.js') }}"></script>
模型
模型类(位于 app/src/Model
)必须扩展 Neemzy\Patchwork\Model\Entity
。请参阅 app/src/Model/Pizza.php
以获取一个简短但有效示例。
RedBean
Entity
类本身扩展了 RedBean_SimpleModel
,使模型具有 ORM 功能。因此,数据库中的表名与类名相同(小写)。
RedBean 的配置(包括连接信息以及模型类命名空间)在 app/bootstrap.php
中的服务提供程序注册中完成。
验证
验证约束是通过 public static function loadValidatorMetadata(ClassMetadata $metadata)
定义的。您必须使用 getter 约束 并相应地定义模型成员的getter,因为不应直接定义属性(以便 RedBean 的通用getter执行其魔法)。这样做还可以让您控制经过验证的数据。
use Neemzy\Patchwork\Model\Entity; use Neemzy\Patchwork\Model\FileModel; class Pizza extends Entity { use FileModel; // (see below) // This method is only used for validation public function getName() { return $this->name; } // It allows us to adapt it if required public function getImage() { return $this->getFilePath('image', true); // absolute path } public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addGetterConstraint('name', new Assert\NotBlank()); $metadata->addGetterConstraint('image', new Assert\Image()); } }
特性
拼布通过 PHP 特性使用一种穷人的事件调度系统来共享常见的实体功能。它通过启用在类使用的特性之间调度方法调用的可能性来工作,通过同名的函数实现
$pizza = $app['redbean']->dispense('pizza'); // Let's assume the Pizza model uses FileModel, SortableModel and TogglableModel traits (see below) $pizza->dispatch('example'); // will call fileExample, sortableExample and togglableExample methods, if they exist // Some dispatching is already pre-handled through RedBean's hooks $app['redbean']->store($pizza); // will dispatch "update" and call fileUpdate, sortableUpdate and togglableUpdate $app['redbean']->trash($pizza); // will dispatch "delete" and call fileDelete, sortableDelete and togglableDelete
如您所理解,由 dispatch
调用的方法通常由特质引入,但在需要时仍可由目标类覆盖。
一些特质是现成的,但如果需要,您也可以自己实现。
FileModel
此特质支持文件操作
- 在文件上传时(如果模型的一个字段包含
Symfony\Component\HttpFoundation\File\UploadedFile
实例),如果验证通过,将其移动到永久位置并生成其名称 - 在模型删除时,删除相关文件
您模型中的文件字段获取器应公开文件的完整路径,但应返回 $this->getFilePath($field, true)
;
ImageModel
此特质扩展了 FileModel
(一旦您使用此特质,则不需要再使用 use
语句),并通过检测 MaxWidth
和 MaxHeight
验证约束,添加了自动图像缩放支持。
SlugModel
此特质为您的模型添加了一个 slug
字段,它包含基于 getSluggable
方法的返回值的 URL 有效标识符,或者如果后者产生空字符串,则为表名附加的 ID(这是其默认行为)
// The Pizza model has no getSluggable method $pizza = $app['redbean']->dispense(); $pizza->name = 'La Grandiosa Margarita!'; $app['redbean']->store($pizza); // Pizza's id is 12 echo($pizza->slug); // 'pizza-12' // Now, the Pizza model has a getSluggable method that returns the pizza's name $pizza = $app['redbean']->dispense(); $pizza->name = 'La Grandiosa Margarita!'; $app['redbean']->store($pizza); echo($pizza->slug); // 'la-grandiosa-margarita'
它还公开了一个 slugify
方法,在依赖的数据已更改但模型尚未更新时,可以重新生成 slug。
SortableModel
此特质为您的模型添加了一个 position
字段,它在插入、删除(通过更新同辈来保持计数)和当然,移动时会自动计算。
$pizza1 = $app['redbean']->dispense('pizza'); $app['redbean']->store($pizza1); echo($pizza1->position); // '1' $pizza2 = $app['redbean']->dispense('pizza'); $app['redbean']->store($pizza2); echo($pizza2->position); // '2' $pizza1->move(); // Move down echo($pizza1->position); // '2' echo($pizza2->position); // '1' $pizza1->move(true); // Move up echo($pizza1->position); // '1' echo($pizza2->position); // '2' $pizza1->move(true); // Moving the first up does not affect anything echo($pizza1->position); // '1' $pizza2->move(); // Neither does moving the last down echo($pizza2->position); // '2' $app['redbean']->trash($pizza1); echo($pizza2->position); // '1'
TimestampModel
此特质为您的模型添加了 created
和 updated
datetime
字段,并按其名称赋予它们值。
TogglableModel
此特质为您的模型添加了一个 active
布尔字段,可以通过 toggle
方法切换,并在插入时根据 getDefaultState
方法赋予值,该方法最初产生 false
,但可以在您的模型类中覆盖。
// The Pizza model has a getDefaultState method which returns true $pizza = $app['dispense']->pizza(); echo(+$pizza->active); // '1' $pizza->toggle(); echo(+$pizza->active); // '0' $pizza->toggle(false); // You can force the given state echo(+$pizza->active); // '0'
控制器
路由
Patchwork 中的控制器是普通的 Silex 控制器,您只需将回调绑定到 URL/HTTP 方法对即可。您可以在 app/bootstrap.php
中将它们“挂载”到 URL 端点。
use Neemzy\Patchwork\Controller\FrontController; $app->mount( '/', new FrontController() );
前端控制器
FrontController
类将一些基本路由绑定到应用的根路径。
/
:渲染app/views/front/partials/home.twig
模板/robots.txt
:根据环境渲染通用的robots.txt
文件/admin
:将用户重定向到app/config/settings/common.yml
文件中定义的路由(在admin.root
中)
实体控制器
EntityController
类描述了一个专门用于管理某个模型(其构造函数接受一个表名作为参数)的控制器。
此类是抽象的,并由 AdminController
扩展,后者将 HTTP 身份验证机制绑定到自己并公开 CRUD GUI 路由。
use Neemzy\Patchwork\Controller\AdminController; $app->mount( '/', new AdminController('pizza') );
您还可以从它继承以自定义其行为。要这样做,您只需声明一个 connect
方法。
public function connect(Application $app) { $ctrl = parent::connect($app); // Bind your own routes on top of the parent ones return $ctrl; }
最后,您还可以通过扩展抽象类本身来创建自己的实体控制器(例如,如果您需要 RSSController
或类似的东西)。
第三方包
将新包添加到您的应用程序就像运行 composer require [package]
(可能还需要使用 --dev
选项,具体取决于包是否将在生产中使用)一样简单。
前端开发
工作流程
当不带参数运行 gulp
时,它会运行一个 workflow
任务,该任务处理您的资源并启动一个由模板或配置文件中的文件更改或直接在资产中(这也会重建它)触发的 livereload 服务器。
编码完成后,关闭 livereload 服务器并运行 gulp --dist
以生成可用于生产的资源。
样式表
您可以选择使用 LESS 或纯 CSS。
您的 app/assets/less
目录的推荐结构如下
front : front-office stylesheets
|-- imports : generale purpose styles and scaffolding
|-- fonts.less : font configuration (see below)
|-- mixins.less : helper mixins
|-- variables.less : style variables
|-- modules : reusable components
|-- main.less : main stylesheet that includes both directories' contents
admin.less : back-office stylesheet
主样式表
查看 app/assets/less/front/main.less
了解核心样式表的使用方法
- 它包括
reset.less
,可以在浏览器间统一样式。 - 它包括一个本地的
variables.less
文件,扩展了核心的相同名称文件(见下文)。 - 它包括一个本地的
fonts.less
文件来定义字体(见下文)。 - 它包括一个本地的
mixins.less
文件,扩展了核心的相同名称文件(见下文)。
变量
以下为核心定义的(且可覆盖)变量
@max-width; // Maximum page width, above which no more responsive styles will be applied (and the content will not enlarge more) @min-width; // Minimum page width, under which no more responsive styles will be applied (and the content will not narrow more) @background; // Default background color @font-family; // Default text font @font-size; // Default text size @font-color; // Default text color @placeholder-color; // HTML5 placeholder text color @phone-max; // "Phone" responsive screen category maximum width @phone-only; // Media query targetting "phone" screens only (from @min-width to @phone-max) @hybrid-max; // "Hybrid" responsive screen category maximum width @hybrid-up; // Media query targetting "hybrid" screens and larger @hybrid-only; // Media query targetting "hybrid" screens only (from @phone-max + 1 to @hybrid-max) @hybrid-down; // Media query targetting "hybrid" screens and narrower @tablet-max; // "Tablet" responsive screen category maximum width @tablet-up; // Media query targetting "tablet" screens and larger @tablet-only; // Media query targetting "tablet" screens only (from @hybrid-max + 1 to @tablet-max) @tablet-down; // Media query targetting "tablet" screens and narrower @desktop-max; // "Desktop" responsive screen category maximum width @desktop-up; // Media query targetting "desktop" screens and larger @desktop-only; // Media query targetting "desktop" screens only (from @tablet-max + 1 to @desktop-max) @desktop-down; // Media query targetting "desktop" screens and narrower @large-only; // Media query targetting "large" screens only (from @desktop-max + 1 to infinity) @hi-density; // Minimum pixel density ratio to be considered "high" @retina; // Media query targetting "high density" screens
因此,您可以编写如 @media screen and @large-only, @retina
这样的媒体查询。
混合
以下混合可用
.appearance(@value); // shortcut for appearance CSS rule with and without prefixes (not handled by Autoprefixer) .hide-responsive(@query); // apply display: none; to the element when the media query is truthy .container(); // makes the element a centered block with a width of @max-width .mono-height(@height); // sets height and line-height to given value, useful for vertical centering .text-size-adjust(@value); // shortcut for text-size-adjust CSS rule with and without prefixes (not handled by Autoprefixer)
JavaScript
您的 app/assets/js
目录应包含一个 main.js
文件,这将是您前端代码的入口点。它可以通过 require
方法使用其他文件,只要这些文件公开了 module.exports
属性,根据 CommonJS 模块定义。
然后,gulp 将将所有内容编译成一个单独的 public/assets/js/main.js
文件。
您可以使用提供的 domqueryall 在查询 DOM 的多个元素时获取一个数组而不是一个 NodeList
。
(function ($, $$) { 'use strict'; // $('#one-element'); // $$('.multiple-elements'); }) (document.querySelector.bind(document), require('domqueryall'));
图片
图片将从 app/assets/img
复制到 public/img
,并在生产模式下运行时进行压缩。
字体
TTF 字体将从 app/assets/font
复制到 public/font
,并由 gulp 在 EOT 和 WOFF 格式中进行转换。
Webfont 处理得到了 Patchwork 的 LESS 混合的帮助。
// Automatically declare the font with multiple file formats (here, /bebasneue.(ttf|eot|woff)/) @font-face { .font-face(BebasNeue, bebasneue); } // Set font-family and reset font-weight and font-style, to avoid rendering issues with some browsers .font-reset(BebasNeue);
后台办公室
在后台办公室,Patchwork 依赖于 Twitter 的 Bootstrap 来构建 CRUD 接口。查看 app/views/admin/pizza
以获取工作示例。
第三方包
向您的应用程序添加新包就像运行 npm install [package] --save-dev
一样简单。您将始终使用此选项,因为生产 JS 代码将始终由您自己的文件组成,其中包含第三方代码,因此永远不会以“原样”部署此类代码。
还可以安装额外的 gulp 插件,通过编辑 gulpfile.js
来增强前端构建过程。
测试
单元
PHPUnit 类应位于 app/tests/unit
并使用 [App]\Tests
命名空间。然后,您只需在应用程序的根目录中运行 phpunit
来运行测试。
PHPUnit 的配置是通过 phpunit.xml
文件完成的。
功能
Behat 功能应位于 app/tests/functional
,上下文类位于 bootstrap
,它是后者的子目录。已提供了一个示例上下文类,并扩展了 Neemzy\Patchwork\Tests\FeatureContext
,这为 Mink 添加了一些词汇。
Then wait 5 seconds
Then take a screenshot
Then ".togglable" element should be visible
Then ".togglable" element should be hidden
Then ".togglable" element should have class "togglable--hidden"
Then ".togglable" element should not have class "togglable--hidden"
测试通过 BrowserKit 运行,当功能以 @javascript
前缀时,使用 PhantomJS。
部署
PHP 依赖
在您的生产服务器上运行 composer install --no-dev -o
(或在持续集成构建期间)以仅获取应用程序实际使用的包,并生成一个优化的自动加载器。
资产
您不需要在您的生产服务器上编译资产。这不是它的角色。这就是为什么,在其基本设置中,Patchwork 不在 .gitignore
中忽略 public/assets
文件夹。然后您可以版本控制已编译(编译并压缩)的资产,以便部署(如果您版本控制编译资产,则最好只提交这些)。
如果您使用持续集成(更好),则可以安全地 .gitignore
public/assets
,并由您的构建处理生产就绪资产编译。
致谢
本文由 neemzy 撰写。您可以通过以下PHP软件包了解我的作品,这些软件包在Patchwork中得到了应用:
- patchwork-core:Patchwork的核心文件
- environ:轻量级环境管理器
- environ-service-provider:Silex微框架的Environ服务提供商
- redbean-service-provider:Silex微框架的RedBean ORM服务提供商
- share-extension:提供社交分享链接的Twig扩展
贡献和拉取请求非常欢迎 :)