教你成为全栈工程师(Full Stack Developer) 十六-网站主页设计

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

找到默认主页入口

 

我们知道页面入口都是配置在路由中的,我们来看下app/config/routing.yml发现没有“/”的路由,但是我们发现了这么几句:

app:
    resource: "@AppBundle/Controller/"
    type:     annotation

 

annotation的意思是“注解”,也就是说这一部分路由配置放在了注释里面,而资源在@AppBundle/Controller/,那么我看下src/AppBundle/Controller/目录

[root@centos7vm mywebsite]# ls src/AppBundle/Controller/
BlogController.php  DefaultController.php

 

这里面的BlogController.php是我们自己写的,没有什么annotation的东东。那我们直接看下src/AppBundle/Controller/DefaultController.php,这个文件是在创建工程时自动生成的

 

<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {
        // replace this example code with whatever you need
        return $this->render('default/index.html.twig', array(
            'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
        ));
    }
}

这里面的

    /**
     * @Route("/", name="homepage")
     */

意思是说声明了一个路由配置,路由的名字叫做homepage,路由的路径是"/",当被访问时要执行DefaultController::indexAction方法,至此我们又学会了一种路由配置方法annotation,但个人不推荐这种配置,因为配置分散,不方便统一管理

 

我们把这段代码改的单纯一点:

    public function indexAction(Request $request)
    {
        return $this->render('default/index.html.twig');
    }

 

主页模板

 

清掉app/Resources/views/default/index.html.twig,并改成如下:

{% extends "base.html.twig" %}
{% block body %}
<div class="row jumbotron">
    <div class="col-md-1 col-xs-1"></div>
    <div class="col-md-10 col-xs-10"><h1>Welcome Big Data ITors!</h1></div>
    <div class="col-md-1 col-xs-1"></div>
</div>
{% endblock body %}

 

浏览下主页

 

我们熟悉的内容又回来啦

 

在主页中展示科目列表

 

下面我们把Subject的分类展示在主页中,首先我们在管理后台创建这样几个Subject:

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

其中的Photo是你上传的封面图片的地址,举个例子,在

 

上传一个image后,在图片列表中点击

 

找到

这个地址,并把这个地址填写到Subject的Photo中就行了

 

 

修改src/AppBundle/Controller/DefaultController.php,改成:

    public function indexAction(Request $request)
    {
        $this->subjectRepository = $this->getDoctrine()->getRepository('AppBundle:Subject');
        $subjects = $this->subjectRepository->findAll();
        return $this->render('default/index.html.twig', array(
            'subjects' => $subjects
        ));
    }

 

修改app/Resources/views/default/index.html.twig,如下:

{% extends "base.html.twig" %}
{% block body %}
<div class="row jumbotron">
    <div class="col-md-1 col-xs-1"></div>
    <div class="col-md-10 col-xs-10"><h1>Welcome Big Data ITors!</h1></div>
    <div class="col-md-1 col-xs-1"></div>
</div>
<div class="row">
    <div class="col-sm-1 col-xs-1"></div>
    {% for subject in subjects %}
    <div class="col-sm-2 col-xs-12">
        <a href="{{ path('blog_list', {'subjectId':subject.id}) }}" class="thumbnail">
            <img src="{{ subject.photo }}" alt="{{ subject.name }}">
            <div class="caption">
                <h3>{{ subject.name }}</h3>
                <p>{{ subject.introduce }}</p>
            </div>
        </a>
    </div>
    {% endfor %}
    <div class="col-sm-1 col-xs-1"></div>
</div>
<br />
<br />

{% endblock body %}

 

见证奇异的时刻到了,打开http://172.16.142.130/app_dev.php/,你会不禁哇塞一下,高大上的内容出来啦:

 

 

是不是很漂亮的说

 

下一节我们继续改进我们的主页内容,让他更专业一些

教你自定义bash的命令补全

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

complete命令介绍

 

linux和mac系统自带的complete命令非常强大,用法例如complete -F _ps ps表示当我们输入ps命令后会执行_ps这个函数来做补全,虽然强大但不容易使用,需要一些辅助工具帮我们做一些封装的工作

 

安装bash-completion

 

bash-completion是封装了complete命令的一套脚本(也为我们写好了一些常用命令的补全,但还不够)

mac系统安装方法:

brew install bash-completion

centos/rhel系统安装方法:

yum install bash-completion

其他linux系统安装方法类似

 

安装后会在/etc(brew安装的会在/usr/local/etc)下某个目录中生成bash_completion.sh文件和bash_completion.d目录,其中bash_completion.sh里面封装了一些有用的函数,需要加到.bash_profile里加载,而bash_completion.d目录则都是自定义的补全方法

 

在.bash_profile中添加

. /etc/bash_completion

具体路径随bash-completion安装目录修改

 

自定义补全参数

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

比如我希望在输入ps+空格后按TAB键自动帮我补全aux,则只需要在bash_completion.d目录创建一个文件,假如命名为ps,内容如下:

_ps()
{
    COMPREPLY="aux"
    return 0
} &&
complete -F _ps ps

 

重新登录bash后执行ps+空格+TAB键,是不是自动补全成ps aux?

解释:这里的_ps是一个函数,complete -F _ps ps表示当我输入ps时会按照_ps函数来指明补全逻辑,这里的COMPREPLY就是回写补全字符串的变量

 

按提示补全

 

比如我希望在输入ps+空格+a+TAB自动补全aux,在输入ps+空格+u+TAB自动补全ux,意思就是要根据参数的前缀来确定补全哪种参数,那么内容改成如下:

_ps()
{
    local cur
    _get_comp_words_by_ref cur
    case $cur in
        a*)
        COMPREPLY="aux"
            return 0
            ;;
        u*)
        COMPREPLY="ux"
            return 0
            ;;
    esac
    return 0
} &&
complete -F _ps ps

解释:这里的_get_comp_words_by_ref是读取已经输入的前缀,判断$cur如果前缀是a,就补全aux,如果前缀是u就补全ux

教你成为全栈工程师(Full Stack Developer) 十五-做一个完美的管理后台侧边栏

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

把管理后台据为己有

 

我们的管理后台左上角logo部分显示的内容是这样的:

 

是不是有点low了?我想改个名字叫做“后台管理系统”怎么办呢?非常简单,打开app/config/parameters.yml,添加如下配置:

sonata_admin:
    title: 后台管理系统

 

重新打开后台界面看到什么了?

 

简直太简单了

 

侧栏快捷入口

 

sonata-admin都是通过block组织的,所以侧栏也可以通过配置block修改,我们修改app/config/config.yml中的sonata_block配置组,改成:

sonata_block:
    default_contexts: [cms]
    blocks:
        # enable the SonataAdminBundle block
        sonata.admin.block.admin_list:
            contexts: [admin]
        sonata.admin.block.search_result:
            contexts: [admin]
        sonata.user.block.menu:    # used to display the menu in profile pages
        sonata.user.block.account: # used to display menu option (login option)

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

并添加如下:

sonata_admin:
    templates:
        layout: SonataAdminBundle::my_layout.html.twig

 

我们创建我们自己的模板文件app/Resources/SonataAdminBundle/views/my_layout.html.twig,内容如下:

{% extends 'SonataAdminBundle::standard_layout.html.twig' %}
{% block side_bar_after_nav %}
    <br/>
    <p class="text-center"><a href="{{ path('homepage') }}">前往首页</a></p>
{% endblock %}

看下效果:

 

 

在这里你可以任意定制你自己的菜单

教你成为全栈工程师(Full Stack Developer) 十四-实现漂亮的网站管理后台

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

重组BlogPost编辑界面

 

打开src/AppBundle/Admin/BlogPostAdmin.php文件,修改configureFormFields方法,增加几行,像下面的样子:

 

        $formMapper
            ->with('Content', array('class' => 'col-md-9'))
            ->add('title', 'text')
            ->add('body', 'ckeditor', array('autoload' => true))
            ->add('create_time', 'sonata_type_date_picker', array(
                'format'=>'yyyy-MM-dd HH:mm:ss',
                'dp_default_date'        => date('Y-m-d H:i:s'),))
            ->end()
            ->with('Meta data', array('class' => 'col-md-3'))
            ->add('subject', 'sonata_type_model', array(
                'class' => 'AppBundle\Entity\Subject',
                'property' => 'name',
            ))
            ->add('category', 'sonata_type_model', array
                'class' => 'AppBundle\Entity\Category',
                'property' => 'name',
            ))
            ->end();

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

看下BlogPost编辑界面:

 

 

我们把有关分类的项目单独作为一列放到右侧,并且占据相对狭窄的宽度,是不是看起来井井有条了?

 

原理讲解

 

我们其实只增加了这样几行:

            ->with('Content', array('class' => 'col-md-9'))
    ……
            ->end()
    ……
            ->with('Meta data', array('class' => 'col-md-3'))
    ……
            ->end()

 

这是一种布局方式,with和end成对出现,它表示把其间的项目组织成一个组布局在一起,Content和Meta data都是组显示的名称,而col-md-9和col-md-3是bootstrap框架的内容,表示两个组放到一行里,列的宽度比例是9:3,有关bootstrap的详细内容后面再详细讨论

教你成为全栈工程师(Full Stack Developer) 十三-用表关联结构实现类目管理

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

以终为始

 

想象一下我们希望有怎么样的分类,比如你是一个音乐爱好者,那么你的博客可能会分成:摇滚、流行、古典……;如果你是一个体育爱好者,那么可能会分成:篮球赛事、户外运动、健身……;如果是一个IT爱好者,可能会分成:大数据、数学知识、信息检索……。那么首先我们需要的是一个类目表。

 

创建src/AppBundle/Entity/Subject.php这个model,如下:

<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
 * Subject
 *
 * @ORM\Table()
 * @ORM\Entity
 */
class Subject
{
    /**
     * @ORM\OneToMany(targetEntity="BlogPost", mappedBy="subject")
     */
    private $blogPosts;
    public function __construct()
    {
        $this->blogPosts = new ArrayCollection();
    }
    public function getBlogPosts()
    {
        return $this->blogPosts;
    }
/**
 * @var integer
 *
 * @ORM\Column(name="id", type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
private $id;
/**
 * @var string
 *
 * @ORM\Column(name="name", type="string", length=255)
 */
private $name;
/**
 * @var string
 *
 * @ORM\Column(name="photo", type="string", length=255)
 */
private $photo;
/**
 * @var string
 *
 * @ORM\Column(name="introduce", type="string", length=255)
 */
private $introduce;
/**
 * @return string
 */
public function getIntroduce()
{
    return $this-&gt;introduce;
}
/**
 * @param string $introduce
 */
public function setIntroduce($introduce)
{
    $this-&gt;introduce = $introduce;
}
/**
 * Get id
 *
 * @return integer
 */
public function getId()
{
    return $this-&gt;id;
}
/**
 * Set name
 *
 * @param string $name
 * @return Subject
 */
public function setName($name)
{
    $this-&gt;name = $name;
    return $this;
}
/**
 * Get name
 *
 * @return string
 */
public function getName()
{
    return $this-&gt;name;
}
/**
 * Set photo
 *
 * @param string $photo
 * @return Subject
 */
public function setPhoto($photo)
{
    $this-&gt;photo = $photo;
    return $this;
}
/**
 * Get photo
 *
 * @return string
 */
public function getPhoto()
{
    return $this-&gt;photo;
}

}

 

 

这里的private $blogPosts;是一个一对多(OneToMany)的关系类型,@ORM声明了targetEntity="BlogPost", mappedBy="subject",表示$blogPosts只能存储BlogPost类型的数据,并且这些数据都是以BlogPost::subject来做hash的,那么就需要BlogPost必须有subject成员变量,下面我们来添加,修改src/AppBundle/Entity/BlogPost.php,添加:

    /**
     * @ORM\ManyToOne(targetEntity="Subject", inversedBy="blogPosts")
     */
    private $subject;

 

并添加get和set方法:

    public function setSubject(Subject $subject)
    {
        $this->subject = $subject;
    }
    public function getSubject()
    {
        return $this->subject;
    }

 

这里的private $subject;是一个多对一(ManyToOne)的关系类型,@ORM声明了targetEntity="Subject", inversedBy="blogPosts",表示$subject存储的是Subject类型的数据,并和Subject::blogPosts有对应关系

 

添加SubjectAdmin管理类,添加src/AppBundle/Admin/SubjectAdmin.php文件,内容如下:

<?php
namespace AppBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
class SubjectAdmin extends Admin
{
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper->add('name', 'text')
            ->add('introduce', 'text')
            ->add('photo', 'text');
    }
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper->add('name');
    }
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper->addIdentifier('name')
            ->add('introduce')
            ->add('photo');
    }
}

 

执行如下语句来创建subject表和更新blogpost表:

[root@centos7vm mywebsite]# php app/console doctrine:schema:update --force

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

修改app/config/services.yml注册admin.subject服务,在services组添加:

    admin.subject:
        class: AppBundle\Admin\SubjectAdmin
        arguments: [~, AppBundle\Entity\Subject, ~]
        tags:
            - { name: sonata.admin, manager_type: orm, label: Subject }

 

这时打开http://172.16.142.130/app_dev.php/admin看到多出了subject的管理界面:

 

我们添加一个subject,如下:

保存后,看到我们生成了一个subject数据:

 

这里的photo先随便填一个,以后再用到这个字段

 

如果你打算新建一个blogpost来指定一个subject,那么会发现,在blogpost的编辑界面没有选择科目的地方,这时怎么办呢?我们还需要修改一下BlogPostAdmin管理类,修改src/AppBundle/Admin/BlogPostAdmin.php,如下:

        $formMapper
            ->add('title', 'text')
            ->add('body', 'ckeditor', array('autoload' => true))
            ->add('subject', 'sonata_type_model', array(
                'class' => 'AppBundle\Entity\Subject',
                'property' => 'name',
            ))
            ->add('create_time', 'sonata_type_date_picker', array(
                'format'=>'yyyy-MM-dd HH:mm:ss',
                'dp_default_date'        => date('Y-m-d H:i:s'),));

 

这时打开blogpost编辑界面看到:

 

 

多出了subject选择框

 

我们新建一篇看下:

 

列表中列出了这篇blog,但是没有看到类目呢?这是因为我们没有配置显示subject字段,继续修改src/AppBundle/Admin/BlogPostAdmin.php的configureListFields方法,内容如下:

        $listMapper
            ->addIdentifier('title')
            ->add('subject.name')
            ->add('createTime')
            ;

 

这时候再看blog列表:

 

 

成功啦

 

下面的任务就留给你自己:用同样的方式来添加category,用来表示另一个层级的分类,比如:原创、转载、学习笔记……,这样你的文章可以是:“大数据”类别下的“原创”文章。

 

提示一下:

第一步添加如下文件:src/AppBundle/Entity/Category.php、src/AppBundle/Admin/CategoryAdmin.php

第二步修改如下文件:src/AppBundle/Entity/BlogPost.php、src/AppBundle/Admin/BlogPostAdmin.php

第三步修改如下配置:app/config/services.yml

第四步更新数据库:php app/console doctrine:schema:update --force

 

最终效果如下:

 

添加博客页面

 

 

博客列表页面

教你成为全栈工程师(Full Stack Developer) 十二-SonataAdmin管理后台轻松配置

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

特殊字段的困扰

 

上节中我们编辑一个blogpost时是这样的:

 

这里的body本应该有图文并茂的内容,但是我们只能输入一些纯文本,createTime的填写也非常不方便,下面我们来解决这个问题

 

ckeditor插件支持

 

首先安装MediaBundle扩展和SonataFormatterBundle扩展,执行:

[root@centos7vm mywebsite]# composer require sonata-project/media-bundle "2.3.*"
[root@centos7vm mywebsite]# composer require sonata-project/formatter-bundle "2.3.*"

注册bundle,修改app/AppKernel.php,添加注册如下:

            new Ivory\CKEditorBundle\IvoryCKEditorBundle(),
            new Sonata\FormatterBundle\SonataFormatterBundle(),
            new Knp\Bundle\MarkdownBundle\KnpMarkdownBundle(),
            new Sonata\MediaBundle\SonataMediaBundle(),
            new JMS\SerializerBundle\JMSSerializerBundle(),
            new Sonata\EasyExtendsBundle\SonataEasyExtendsBundle(),
            new Application\Sonata\MediaBundle\ApplicationSonataMediaBundle(),

 

修改app/config/config.yml,把doctrine配置组改成如下的样子:

doctrine:
    dbal:
        driver:   pdo_mysql
        host:     "%database_host%"
        port:     "%database_port%"
        dbname:   "%database_name%"
        user:     "%database_user%"
        password: "%database_password%"
        charset:  UTF8
        types:
            json: Sonata\Doctrine\Types\JsonType
    orm:
        auto_generate_proxy_classes: "%kernel.debug%"
        entity_managers:
            default:
                mappings:
                    AppBundle:
                        type:      ~
                        dir:       "Entity"
                        prefix:    "AppBundle\Entity"
                        is_bundle: ~

并添加如下配置:

sonata_formatter:
    formatters:
        markdown:
            service: sonata.formatter.text.markdown
            extensions:
                - sonata.formatter.twig.control_flow
                - sonata.formatter.twig.gist
                - sonata.media.formatter.twig
        text:
            service: sonata.formatter.text.text
            extensions:
                - sonata.formatter.twig.control_flow
                - sonata.formatter.twig.gist
                - sonata.media.formatter.twig
        rawhtml:
            service: sonata.formatter.text.raw
            extensions:
                - sonata.formatter.twig.control_flow
                - sonata.formatter.twig.gist
                - sonata.media.formatter.twig
        richhtml:
            service: sonata.formatter.text.raw
            extensions:
                - sonata.formatter.twig.control_flow
                - sonata.formatter.twig.gist
                - sonata.media.formatter.twig
        twig:
            service: sonata.formatter.text.twigengine
            extensions: [] # Twig formatter cannot have extensions
    ckeditor:
        templates:
            browser: 'SonataFormatterBundle:Ckeditor:browser.html.twig'
            upload: 'SonataFormatterBundle:Ckeditor:upload.html.twig'
sonata_media:
    default_context: default
    db_driver: doctrine_orm # or doctrine_mongodb, doctrine_phpcr
    contexts:
        default:  # the default context is mandatory
            providers:
                - sonata.media.provider.dailymotion
                - sonata.media.provider.youtube
                - sonata.media.provider.image
                - sonata.media.provider.file
            formats:
                small: { width: 100 , quality: 70}
                big:   { width: 500 , quality: 70}
    cdn:
        server:
            path: /uploads/media # http://media.sonata-project.org/
    filesystem:
        local:
            directory:  %kernel.root_dir%/../web/uploads/media
            create:     false
ivory_ck_editor:
    default_config: default
    configs:
        default:
            filebrowserBrowseRoute: admin_sonata_media_media_ckeditor_browser
            filebrowserImageBrowseRoute: admin_sonata_media_media_ckeditor_browser
                # Display images by default when clicking the image dialog browse button
            filebrowserImageBrowseRouteParameters:
                provider: sonata.media.provider.image
            filebrowserUploadRoute: admin_sonata_media_media_ckeditor_upload
            filebrowserUploadRouteParameters:
                provider: sonata.media.provider.file
                    # Upload file as image when sending a file from the image dialog
            filebrowserImageUploadRoute: admin_sonata_media_media_ckeditor_upload
            filebrowserImageUploadRouteParameters:
                provider: sonata.media.provider.image
                context: my-context # Optional, to upload in a custom context

修改app/config/routing.yml,添加如下内容:

gallery:
    resource: '@SonataMediaBundle/Resources/config/routing/gallery.xml'
    prefix: /media/gallery
media:
    resource: '@SonataMediaBundle/Resources/config/routing/media.xml'
    prefix: /media

 

 

OK, 清缓存(不行就rm -rf app/cache/*)

[root@centos7vm mywebsite]# php app/console cache:clear

安装ckeditor静态文件到web目录:

[root@centos7vm mywebsite]# php app/console assets:install web

 

这时重新打开http://172.16.142.130/app_dev.php/admin,你应该会看到多出了下面的板块

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

这是用来管理图片视频等信息的板块,但尚未配置好还不能用,暂时也不需要,我们继续来看我们需要的内容,还是点BlogPost的link_add,看效果:

 

这回body可以用来编辑图文并茂的内容啦

 

但虽然这里可以编辑文本格式,但是如果要上传图片还不能用,我们再来准备些东西

 

执行:

[root@centos7vm mywebsite]# php app/console sonata:easy-extends:generate --dest=src SonataMediaBundle

如果执行成功,会自动生成src/Application/Sonata/MediaBundle,说明我们成功生成了我们自定义的MediaBundle,但是我们一行代码都不需要写

 

在app/AppKernel.php中注册

            new Sonata\IntlBundle\SonataIntlBundle(),
            new Application\Sonata\MediaBundle\ApplicationSonataMediaBundle(),

 

修改app/config/config.yml,把doctrine的orm组改成如下:

    orm:
        auto_generate_proxy_classes: "%kernel.debug%"
        entity_managers:
            default:
                mappings:
                    ApplicationSonataMediaBundle: ~
                    SonataMediaBundle: ~
                    AppBundle:
                        type:      ~
                        dir:       "Entity"
                        prefix:    "AppBundle\Entity"
                        is_bundle: ~

更新数据库表:

[root@centos7vm mywebsite]# php app/console doctrine:schema:update --force

 

创建图片视频等上传目录:

[root@centos7vm mywebsite]# mkdir web/uploads
[root@centos7vm mywebsite]# mkdir web/uploads/media
[root@centos7vm mywebsite]# chmod -R 0777 web/uploads

 

好,大功告成,我们试一下上传图片吧,点击ckeditor(上面的编辑器)的图片按钮

 

点击上传选择文件

 

 

选好文件,点上传到服务器后,显示

 

 

这时已经把图片上传到服务器的web/uploads/media目录下了,点确定就可以插入到我们要编辑的内容里了

 

ckeditor还有很多丰富的内容可以配置,具体可以参考官方文档,比如代码高亮显示,像下面的样子:

 

漂亮的时间选择器

 

下面我们来解决时间类型数据的填写问题,我们现在的时间选择是这样的难看难用:

 

我们来介绍一种sonata_type_date_picker类型的时间选择器,它是SonataCore中自带的组件

 

在app/config/config.yml的twig组下添加模板配置:

    form:
        resources:
            - 'SonataCoreBundle:Form:datepicker.html.twig'

修改src/AppBundle/Admin/BlogPostAdmin.php的configureFormFields方法,如下:

        $formMapper
            ->add('title', 'text')
            ->add('body', 'ckeditor', array('autoload' => true))
            ->add('create_time', 'sonata_type_date_picker', array(
                'format'=>'yyyy-MM-dd HH:mm:ss',
                'dp_default_date'        => date('Y-m-d H:i:s'),));

 

在管理后台重新编辑一个blog看效果:

 

 

教你成为全栈工程师(Full Stack Developer) 十一-轻松搭建网站管理后台

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

sonata介绍

 

sonata扩展是symfony2众多扩展中应用最广泛的扩展之一,它的主要功能是帮你建立一个强大的管理后台,除此之外还有很多附加功能你可以深入挖掘,官方文档在https://sonata-project.org/bundles/admin/2-3/doc/index.html

 

composer扩展管理工具

 

为了安装symfony2的扩展,我们需要一个composer工具,它的安装方法(参考https://getcomposer.org/download/,以下命令如若失效,请以官方最新文档为准)为连续执行以下命令:

[root@centos7vm ~]# php -r "readfile('https://getcomposer.org/installer');" > composer-setup.php
[root@centos7vm ~]# php -r "if (hash_file('SHA384', 'composer-setup.php') === '7228c001f88bee97506740ef0888240bd8a760b046ee16db8f4095c0d8d525f2367663f22a46b48d072c816e7fe19959') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
[root@centos7vm ~]# php composer-setup.php
[root@centos7vm ~]# php -r "unlink('composer-setup.php');"

然后你会发现当前目录下有一个composer.phar文件,我们把它移动到/usr/local/bin下并重命名:

[root@centos7vm ~]# mv composer.phar /usr/local/bin/composer

ok,安装完成,执行

[root@centos7vm ~]# composer

看下是否输出帮助信息

 

安装sonata-admin

 

先看一下工程目录下的composer.json这个文件,这里面记录了当前工程都依赖哪些扩展包及其版本信息,这个文件一般我们不去编辑,当用composer更新扩展包时会自动更新这个文件

执行:

[root@centos7vm mywebsite]# composer require sonata-project/admin-bundle "2.3.*"

安装sonata的admin-bundle,因为admin-bundle会依赖一些其他bundle,所以安装过程可能会比较慢,需要耐心等待,当输出有:

 [OK] All assets were successfully installed.

说明安装完成

 

为了让admin-bundle能操作我们的数据库,还需要安装doctrine-orm-admin-bundle,执行:

[root@centos7vm mywebsite]# composer require sonata-project/doctrine-orm-admin-bundle "2.3.*"

 

安装了sonata扩展,相当于往vendor目录里拷贝了一批文件,但实际上我们还没有真正使用上,如果要使用起来,需要修改app/AppKernel.php把要使用的组件注册进来才能生效(所有的symfony2组件的安装都需要这个过程),修改app/AppKernel.php中的registerBundles()函数,在$bundles = array(...最后添加:

            new Sonata\CoreBundle\SonataCoreBundle(),
            new Sonata\BlockBundle\SonataBlockBundle(),
            new Knp\Bundle\MenuBundle\KnpMenuBundle(),
            new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
            new Sonata\AdminBundle\SonataAdminBundle(),

注:如果某个bundle已经注册过,则不需要重复注册

 

配置sonata-admin

 

sonata-admin的接口都是基于SonataBlockBundle,按block组织在一起的,所以必须先告诉blockbundle,sonata-admin这个block的存在,所以修改app/config/config.yml

添加如下配置(注意缩进):

sonata_block:
    default_contexts: [cms]
    blocks:
        # enable the SonataAdminBundle block
        sonata.admin.block.admin_list:
            contexts: [admin]

block配置完还需要给admin-bundle指定路由,这样才能通过url访问,admin-bundle是有自己的一套路由配置的,我们只需要加载进来即可,修改app/config/routing.yml,添加如下内容:

admin_area:
    resource: "@SonataAdminBundle/Resources/config/routing/sonata_admin.xml"
    prefix: /admin

OK,清缓存:

[root@centos7vm mywebsite]# php app/console cache:clear
[root@centos7vm mywebsite]# php app/console assets:install

访问http://172.16.142.130/app_dev.php/admin看看(这里的172.16.142.130是我的虚拟机ip,需要换成你的ip,另外如果访问不了可以尝试手工清缓存rm -rf app/cache/*),效果如下:

 

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

 

创建BlogPost的后台管理

 

创建src/AppBundle/Admin/BlogPostAdmin.php文件,内容如下:

<?php
namespace AppBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
class BlogPostAdmin extends Admin
{
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->add('title', 'text')
            ->add('body', 'text')
            ->add('create_time', 'datetime');
    }
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper
            ->addIdentifier('title')
            ->add('createTime')
            ;
    }
}

 

创建了BlogPostAdmin还不能让symfony2知道,所以还需要注册一下,修改app/config/services.yml,在services组中添加服务:

    admin.blog_post:
        class: AppBundle\Admin\BlogPostAdmin
        arguments: [~, AppBundle\Entity\BlogPost, ~]
        tags:
            - { name: sonata.admin, manager_type: orm, label: Blog post }

 

这样还不够,还需要配置好有关自定义Admin类的路由,修改app/config/routing.yml,添加:

_sonata_admin:
    resource: .
    type: sonata_admin
    prefix: /admin

 

OK,重新打开http://172.16.142.130/app_dev.php/admin看到如下:

 

点开"link_list"看到了什么?

 

 

(*@ο@*) 哇~,是我们手工在数据库里添加进去的那一行哎!

 

点击“link_add"进去添加一条记录试试,我们添加如下一行:

 

点击”btn_create_adn_return_to_list“后就成功写到了数据库里啦

 

 

到此,我们已经实现了对数据库表的增删改查的管理功能

教你成为全栈工程师(Full Stack Developer) 十-MVC模式中的Model使用方法

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

创建一个model定义

 

创建src/AppBundle/Entity/BlogPost.php文件并编写如下内容:

 

<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
 * BlogPost
 *
 * @ORM\Table()
 * @ORM\Entity
 */
class BlogPost
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @var string
     *
     * @ORM\Column(name="title", type="string", length=255)
     */
    private $title;
    /**
     * @var string
     *
     * @ORM\Column(name="body", type="text")
     */
    private $body;
    /**
     * @var \DateTime
     *
     * @ORM\Column(name="create_time", type="datetime")
     */
    private $createTime;
    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }
    /**
     * Set title
     *
     * @param string $title
     * @return BlogPost
     */
    public function setTitle($title)
    {
        $this->title = $title;
        return $this;
    }
    /**
     * Get title
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }
    /**
     * Set body
     *
     * @param string $body
     * @return BlogPost
     */
    public function setBody($body)
    {
        $this->body = $body;
        return $this;
    }
    /**
     * Get body
     *
     * @return string
     */
    public function getBody()
    {
        return $this->body;
    }
    /**
     * @return \DateTime
     */
    public function getCreateTime()
    {
        return $this->createTime;
    }
    /**
     * @param \DateTime $createTime
     */
    public function setCreateTime($createTime)
    {
        $this->createTime = $createTime;
    }
}

BlogPost这个model从含义上表达了一篇博客,从实现上表达了数据库表的一行,有id、title、body、createTime几个属性,其中id是数据库的主键(自增1,不需要setId方法),title是博客的标题,body是博客内容,createTime是博客创建时间

 

下面我们就要自动化地根据这个model定义生成数据库表,在此之前,我们要把数据库配置好

 

安装和配置mysql见《RHEL7或centos7安装mysql5.7方法和配置

 

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

配置网站数据库连接

 

修改app/config/parameters.yml文件,按照你自己的数据库配置改成如下:

 

parameters:
    database_host: 127.0.0.1
    database_port: 3306
    database_name: mywebsite
    database_user: root
    database_password: shareditor@126.COM
    mailer_transport: smtp
    mailer_host: 127.0.0.1
    mailer_user: null
    mailer_password: null
    secret: 1a0cb131fb193436d0f6ce467f2d8b6c7c5b02da

mailer部分和secret不知道是做什么的就保持不动

注意:这里我们设置了一个数据库名字:mywebsite,后面会创建这个数据库

 

下面该是隆重介绍app/console工具的时候了,这是symfony2工程都会带的一个自动化工具,直接执行

[root@centos7vm mywebsite]# php app/console

可以看帮助信息,它的功能包括:自动管理缓存、配置、路由、entity等

 

这次我们用它来初始化数据库,执行

[root@centos7vm mywebsite]# php app/console doctrine:database:create

通过mysql查看发现已经建立了mywebsite数据库

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| mywebsite          |
| performance_schema |
| sys                |
+--------------------+

还记得之前我们之前创建的entity吗?对,就是那个class BlogPost,现在我们用自动化工具把它直接映射成mysq的一张表,执行

[root@centos7vm mywebsite]# php app/console doctrine:schema:update --force

通过mysql查看发现已经成功建立了

mysql> use mywebsite;
mysql> show tables;
+---------------------+
| Tables_in_mywebsite |
+---------------------+
| blog_post           |
+---------------------+
mysql> desc blog_post;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| title       | varchar(255) | NO   |     | NULL    |                |
| body        | longtext     | NO   |     | NULL    |                |
| create_time | datetime     | NO   |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

而且表的列和BlogPost中的成员变量一致,(*@ο@*) 哇~amazing!有木有

 

下面我们手工往数据库里插入一行数据

mysql> insert into blog_post(title,body,create_time) values('这是标题','这是内容',now());

 

model的读取

 

修改src/AppBundle/Controller/BlogController.php中的showAction方法如下:

    public function showAction(Request $request)
    {
        $blogPostRepository = $this->getDoctrine()->getRepository('AppBundle:BlogPost');
        $blogposts = $blogPostRepository->findAll();
        return $this->render('blog/show.html.twig', array('title' => $blogposts[0]->getTitle(), 'content' => $blogposts[0]->getBody()));
    }

这里面的getRepository('AppBundle:BlogPost')就是隐藏了和数据库的交互细节,暴露给你的就是$blogPostRepository这个对象,直接可以做各种find操作,直接能拿到blog_post表的数据

findAll是取出所有结果,所以我用了$blogposts[0]来只拿第一个结果演示

 

我们还要修改一下app/Resources/views/blog/show.html.twig如下:

{% extends "base.html.twig" %}
{% block title %}博客内容{% endblock title %}
{% block body %}
<div class="row jumbotron">
    <div class="col-md-1 col-xs-1"></div>
    <div class="col-md-10 col-xs-10"><h1>{{ title }}</h1></div>
    <div class="col-md-1 col-xs-1"></div>
</div>
<div class="row">
    <div class="col-md-1 col-xs-1"></div>
    <div class="col-md-10 col-xs-10"><h4>{{ content }}</h4></div>
    <div class="col-md-1 col-xs-1"></div>
</div>
{% endblock body %}

模板里面展示{{ title }}和{{ content }},对应的就是array('title' => $blogposts[0]->getTitle(), 'content' => $blogposts[0]->getBody())取出来的title和content

 

现在看下网页的效果吧:

 

上面展示了model的读取流程:数据库表=>model类实例=>controller透传=>前端展示

model的写入方式也类似,后面章节会通过更高级的功能来继续介绍

RHEL7或centos7安装mysql5.7方法和配置

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

http://dev.mysql.com/downloads/repo/yum/下载Red Hat Enterprise Linux 7对应的rpm包,我下载的是5.7版,文件名mysql57-community-release-el7-8.noarch.rpm,你也可以下载其他版本

执行

[root@centos7vm ~]# yum localinstall mysql57-community-release-el7-8.noarch.rpm

导入本地yum库,然后执行

[root@centos7vm ~]# sudo yum install mysql-community-server

安装mysql

mysql默认配置文件在/etc/my.cnf,看下内容(未展示注释)

 

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

可以看到默认数据放在/var/lib/mysql下,log在/var/log/mysqld.log

我们保持默认路径不动

执行

[root@centos7vm ~]# service mysqld start

启动mysqld服务

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

通过mysql -V查看版本,如果你安装的是5.7及以上版本,那么是无法mysql直接登录的,它会在root目录下自动产生一个随机密码文件(如果没有生成这个文件,可以到/var/log/mysqld.log中查找password关键词,也能找到)

[root@centos7vm ~]# cat .mysql_secret

看它的内容,如"8l<RM%psS3jl",那么

[root@centos7vm ~]# mysql -u root -p

回车后输入密码登录

登录后先要修改密码,密码要设置的足够复杂(包含大小写字母、数组、符号,还要不成单词),否则会提示错误,如:

mysql> SET PASSWORD = 'shareditor@126.COM';

现在可以看数据库内容了:

mysql> show databases;

执行

mysql> use mysql
mysql> select Host, User from user;
+-----------+-----------+
| Host      | User      |
+-----------+-----------+
| localhost | mysql.sys |
| localhost | root      |
+-----------+-----------+

看下用户配置,这里面的Host表示了授权可登录的主机,localhost表示只有本机才能访问,这是为了安全考虑,但是开发过程中为了方便,我们放开权限,方法如下:

mysql> update user set host = '%' where user = 'root';
mysql> FLUSH PRIVILEGES;

 

现在我们用我们自己的机器来登录试一下,假如虚拟机ip是:

[mycentos@localhost:/ $] mysql -h 172.16.142.130 -u root -p

输入密码后可以登录成功,说明授权成功

 

至此,mysql安装配置完成

教你成为全栈工程师(Full Stack Developer) 九-让模板文件帮你快速开发网页

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

再做一张页面

 

新增/data/httpdir/mywebsite/app/Resources/views/blog/show.html.twig文件,内容如下:

 

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>博客内容</title>
<!-- 新 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<!-- 可选的Bootstrap主题文件(一般不用引入) -->
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</head>
<body>
<div class="row bg-primary">
    <div class="col-sm-1 col-xs-1"></div>
    <div class="col-sm-11 col-xs-11"><h1><a href="" style="text-decoration: none;color: white;">MyWebSite</a></h1></div>
</div>
<div class="row jumbotron">
    <div class="col-md-1 col-xs-1"></div>
    <div class="col-md-10 col-xs-10"><h1>我的第一篇博客</h1></div>
    <div class="col-md-1 col-xs-1"></div>
</div>
</body>
</html>

在/data/httpdir/mywebsite/app/config/routing.yml中新增如下内容(注意缩进):

blog_show:
    path:     /blogshow/
    defaults: { _controller: AppBundle:Blog:show }

 

在/data/httpdir/mywebsite/src/AppBundle/Controller/BlogController.php的BlogController类中增加一个新方法,如下:

    public function showAction(Request $request)
    {
        return $this->render('blog/show.html.twig');
    }

 

这时打开http://172.16.142.130/app_dev.php/blogshow/可以看到一张新网页(注意:这里的172.16.142.130对应改成你虚拟机的ip)

 

那么这时候你会发现show.html.twig和list.html.twig除了<title>标签里的内容和body里的一个div内容不同之外完全一样,如果一个网站有成百上千张网页都大体相同,你会想到什么?对!把一样的部分包装成一个组件,使用它的网页把他include进来,但是html是一层一层标签的嵌套结构,include适合面向函数的语言。所以就产生了模板这种抽象方式。

 

模板抽象

 

我们修改app/Resources/views/base.html.twig文件内容如下:

 

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>{% block title %}自定义标题{% endblock title %}</title>
<!-- 新 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<!-- 可选的Bootstrap主题文件(一般不用引入) -->
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
</head>
<body>
<div class="row bg-primary">
    <div class="col-sm-1 col-xs-1"></div>
    <div class="col-sm-11 col-xs-11"><h1><a href="" style="text-decoration: none;color: white;">MyWebSite</a></h1></div>
</div>
{% block body %}
{% endblock body %}
</body>
</html>

 

可以看到这里面相比前面两个文件出现了这样两句:

<title>{% block title %}自定义标题{% endblock title %}</title>

{% block body %}
{% endblock body %}

 

这里的block就是可以重载的部分,那么我们现在可以修改list.html.twig了,改成如下:

{% extends "base.html.twig" %}
{% block title %}MyWebSite{% endblock title %}
{% block body %}
<div class="row jumbotron">
    <div class="col-md-1 col-xs-1"></div>
    <div class="col-md-10 col-xs-10"><h1>Welcome to MyWebSite!</h1></div>
    <div class="col-md-1 col-xs-1"></div>
</div>
{% endblock body %}

修改show.html.twig,改成如下:

{% extends "base.html.twig" %}
{% block title %}博客内容{% endblock title %}
{% block body %}
<div class="row jumbotron">
    <div class="col-md-1 col-xs-1"></div>
    <div class="col-md-10 col-xs-10"><h1>我的第一篇博客</h1></div>
    <div class="col-md-1 col-xs-1"></div>
</div>
{% endblock body %}

这时候在浏览器重新浏览http://172.16.142.130/app_dev.php/bloglist/http://172.16.142.130/app_dev.php/blogshow/效果是不是和之前一样,但是代码简单了许多

请尊重原创,转载请注明来源网站www.lcsays.com以及原始链接地址

 

变量传递

 

在MVC三个部分之controller里面我们调用render来渲染html的内容,那么你不禁会问:怎么把controller里的变量传递到html里来使用呢?下面我们来做个试验,修改src/AppBundle/Controller/BlogController.php文件,把

return $this->render('blog/show.html.twig');

改成

return $this->render('blog/show.html.twig', array('title' => '博客标题', 'content' => '博客内容'));

多传递一个php数组,数组里存了title和content

 

然后在show.html.twig中block body部分改成

<div class="row jumbotron">
    <div class="col-md-1 col-xs-1"></div>
    <div class="col-md-10 col-xs-10"><h1>{{ title }}</h1></div>
    <div class="col-md-1 col-xs-1"></div>
</div>
<div class="row">
    <div class="col-md-1 col-xs-1"></div>
    <div class="col-md-10 col-xs-10"><h4>{{ content }}</h4></div>
    <div class="col-md-1 col-xs-1"></div>
</div>

重新浏览http://172.16.142.130/app_dev.php/blogshow/看到我们在php代码里指定的title和content展示了出来

 

变量传递通过 {{ }} 这样的符号来传递

tips 1: 以刚刚传递的content变量为例,如果传递的content是一个array结构,加入里面有time、category、text等字段,那么,可以这样读取:{{ content['time'] }}

tips 2: 如果content是一个类结构的实例,这个类有time、category、text等属性(也就是有getTime()、getCategory()、getText()方法),那么可以这样读取:{{ content.time }}

tips 3: {{ }} 里还可以调用php原生方法,如:去掉 html标签方法{{ content | strip_tags }}、以原始html形式展示方法{{ content | raw }}

 

 

流程控制

 

有想过在html里面使用if else或者for循环吗?模板可以帮你,再次修改src/AppBundle/Controller/BlogController.php文件,把

return $this->render('blog/show.html.twig', array('title' => '博客标题', 'content' => '博客内容'));

改成

        $content = array();
        $content[] = array('category' => '类别1', 'text' => '内容1');
        $content[] = array('category' => '类别2', 'text' => '内容2');
        $content[] = array('category' => '类别1', 'text' => '内容3');
        $content[] = array('category' => '类别2', 'text' => '内容4');
        return $this->render('blog/show.html.twig', array('title' => '博客标题', 'content' => $content));

修改app/Resources/views/blog/show.html.twig,把第二组div改成:

{% for cont in content %}
{% if cont['category'] == '类别2' %}
<div class="row">
    <div class="col-md-1 col-xs-1"></div>
    <div class="col-md-10 col-xs-10"><h4>{{ cont['text'] }}</h4></div>
    <div class="col-md-1 col-xs-1"></div>
</div>
{% endif %}
{% endfor %}

 

浏览http://172.16.142.130/app_dev.php/blogshow/看到了什么效果?

 

传递过来的content是个数组,首先用{% for cont in content %}对content做了循环遍历,之后在循环体里用{% if cont['category'] == '类别2' %}做了逻辑判断,只展示类别为“类别2”的内容

 

链接生成

 

如果我们希望点击“内容”都能跳转到bloglist页面,怎么做呢?首先想到我们可以增加个超链接,像这样:

<a href='http://172.16.142.130/app_dev.php/bloglist/'>{{ cont['text'] }}</a>

但是这有一个问题:当网站发布时是不是还要改成

<a href='http://www.****.com/bloglist/'>{{ cont['text'] }}</a>

这是不够灵活的,所以模板为我们提供了链接生成的功能,还记得router.yml的内容吗?

app:
    resource: "@AppBundle/Controller/"
    type:     annotation
blog_list:
    path:     /bloglist/
    defaults: { _controller: AppBundle:Blog:list }
blog_show:
    path:     /blogshow/
    defaults: { _controller: AppBundle:Blog:show }

 

这里面的blog_list、blog_show这种名字开始起作用了,我们可以把“内容”改成如下:

<a href='{{ path('blog_list') }}'>{{ cont['text'] }}</a>

这样便可以自动根据实际环境生成blog_list对应的正确链接,amazing!

 

以上模板功能对于基本的网站开发足够了,如果想了解更多内容可以百度一下twig