symfony 控制器_在Symfony 2中制作广告管理器

symfony 控制器

Just this once won’t hurt – I am not going to write about Sass but Symfony. I had to do a little bit of backend at work and ended up with an interesting problem to solve, involving quite a lot of things so I thought it wouldn’t be such a bad idea to write an article about it.

只是这一次不会伤害–我不会写有关Sass的文章,而是Symfony。 我不得不在工作中做一些后端工作,最终解决了一个有趣的问题,涉及很多事情,所以我认为写一篇有关它的想法不是一个坏主意。

alt

But first, let me explain. The main idea was to build an ad manager. What the hell is an ad manager you say? Let’s say you have some places on your site/application to display ads. We do have things like this on our site, and one of our teams is (partially) dedicated to bringing those places to life with content.

但是首先,让我解释一下。 主要想法是建立一个广告经理。 您说的广告经理到底是怎么回事? 假设您在网站/应用程序上有一些位置可以展示广告。 我们的网站上确实有这样的事情,我们的一个团队(部分)致力于将这些地方带入生活。

Now for some boring reasons I won’t list here, we couldn’t use an existing tool, so we were doomed to build something from scratch. As usual, we wanted to do a lot without much coding, while keeping an overall simplicity for the end user (who is not a developer). I think we came up with a fairly decent solution for our little project.

现在由于一些无聊的原因,我将不在此处列出,我们无法使用现有工具,因此我们注定要从头开始构建某些东西。 像往常一样,我们想做很多事情而无需编写太多代码,同时又要为最终用户(不是开发人员)保持整体简便性。 我认为我们为我们的小项目提出了一个相当不错的解决方案。

Here are the features we set up:

这是我们设置的功能:

  • YAML configuration + FTP access;

    YAML配置+ FTP访问;
  • Either images, videos or HTML content;

    图片,视频或HTML内容;
  • Ability to customize cache duration;

    能够自定义缓存持续时间;
  • Either sliders (yes, the pattern sucks) or random item in collection.

    集合中的滑块(是的,图案很烂)或随机项目。

The idea is quite simple. In Twig templates, we use render_esi (more info here) hitting on a controller action passing it a unique key (basically the name of the ad spot), for instance:

这个想法很简单。 在Twig模板中,我们使用render_esi ( 在此处 render_esi更多信息)击中一个控制器操作,并为其传递一个唯一键(基本上是广告位的名称),例如:

{{ render_esi(url('ads_manager', { 'id': 'home_sidebar_spot' })) }}

Then the action fetches the YAML configuration file, grabs the data associated with the given id, and renders a template. At this point, the template does some very simple logic depending on which type of content the given items are (images, videos, HTML…).

然后,该操作将获取YAML配置文件,获取与给定ID相关联的数据,并呈现一个模板。 此时,模板会根据给定项目的内容类型(图像,视频,HTML ...)执行一些非常简单的逻辑。

Ready? Let’s go.

准备? 我们走吧。

全局配置 (Global configuration)

There are two things we need to have in a global configuration (likely the parameters.yml file): path to the configuration file (ads.yml), and an array of allowed media types.

在全局配置(可能是parameters.yml文件)中,我们需要做两件事:配置文件的路径( ads.yml )和允许的媒体类型数组。

ads:
    uri: http://location.com/path/to/ads.yml
    allowed_types: ['image', 'video', 'html']

配置文件 (The configuration file)

The configuration file is maintained by the team in charge of making the ads. YAML is a human-friendly language that’s perfect for when it comes to simple configuration.

配置文件由负责制作广告的团队维护。 YAML是一种对人类友好的语言,非常适合进行简单配置时使用。

This file is built like this:

该文件的构建如下:

home_sidebar_spot:
    cache_public: true
    cache_shared_max_age: 86400
    cache_max_age: 28800
    random: true
    data: 
        - type: "image"
          link: "http://cdn.domain.tld/path/to/file.png"
          target: "http://google.fr/"
          weight: 1

Where:

哪里:

  • cache_public defines whether the cache should public or private;

    cache_public定义缓存是公共的还是私有的;

  • cache_shared_max_age defines the max age for the cache on the server network;

    cache_shared_max_age定义服务器网络上缓存的最长期限;

  • cache_max_age defines the max age for the cache on the client browser;

    cache_max_age定义客户端浏览器上缓存的最大期限;

  • random defines whether the spot should be treated as a slider (multiple items coming one after another) or a static item, randomly chosen;

    random定义将斑点视为随机选择的滑块(多个项目接连出现)还是静态项目;

  • data is an array of items, either all displayed if random is false or reduced to a single item if random is true.

    data是项目的阵列,或者如果所有显示的randomfalse或减少为单个项目,如果random是真实的。

Each item from data is an object composed of:

data中的每个项目都是一个对象,包含:

  • type defines the media type, either image, video or html;

    type定义媒体类型,可以是imagevideohtml

  • link defines the media content so an absolute URL to an image file, a video service or an HTML file;

    link定义了媒体内容,因此是图像文件,视频服务或HTML文件的绝对URL;

  • target defines the target of the link behind the image if type is image, otherwise is ignored;

    如果typeimage ,则target定义图像后面的链接的目标,否则将被忽略;

  • weight defines a boost when data contains several items and random is set to true.

    data包含多个项目并且random设置为true时, weight定义了增强。

And there is such a block for every ad spot on our site, so basically a dozen of those.

而且,我们网站上的每个广告位都有一个街区,因此基本上是一打。

控制器 (The Controller)

The controller is very simple: it has a single action. The scenario is:

控制器非常简单:它只有一个动作。 该方案是:

  1. fetch configuration file;

    获取配置文件;
  2. parse it;

    解析
  3. find data from given id;

    从给定的id中查找数据;
  4. set cache configuration;

    设置缓存配置;
  5. reduce to a single item if it’s random;

    减少到单个项目,如果它是random

  6. render view.

    渲染视图。
<?php
// Namespace and uses
class AdsManagerController extends Controller
{

    /**
     * @Route("/ads_manager/{id}", name="ads_manager")
     */
    public function indexAction ($id)
    {
        // Fetch data
        $data = $this->getData($id);

        // Prepare response
        $response = new Response();

        // Configure cache
        if ($data['cache_public'] === true) {
            $response->setPublic();
        } else {
            $response->setPrivate();
        }

        // Set max age
        $response->setMaxAge($data['cache_max_age']);
        $response->setSharedMaxAge($data['cache_shared_max_age']);

        // Handle the weight random
        if ($data['random'] === true) {
            $data['data'] = [$this->randomItem($data['data'])];
        }

        // If content is HTML, fetch content from file in a `content` key
        foreach ($data['data'] as $item) {
            if (strtolower($item['type']) === 'html') {
                $item['content'] = file_get_contents($item['link']) || $item['link'];
            }
        }

        // Set content
        $response->setContent($this->renderView('FrontBundle:AdsManager:index.html.twig', [
            'allowed_type' => $this->container->getParameter('ads')['allowed_types'],
            'content' => $data,
            'id' => $id
        ]));

        return $response;
    }
}

private function getData($id)
{
    // Get path to Ads configuration
    $url = $this->container->getParameter('ads')['uri'];
    // Instanciate a new Parser
    $parser = new Parser();

    // Read configuration and store it in `$data` or throw if we cannot parse it
    try {
        $data = $parser->parse(file_get_contents($url));
    } catch (ParseException $e) {
        throw new ParseException('Unable to parse the YAML string:' . $e->getMessage());
    }

    // If `$id` exists in data, fetch content or throw if it's not found
    try {
        return $data = $data[$id];
    } catch (\Exception $e) {
        throw new \Exception('Cannot find `' + $id + '` id in configuration:' . $e->getMessage());
    }
}

private function randomItem($array) {
    $weights = array_column($array, 'weight');
    $total   = array_sum($weights);
    $random  = rand(1, $total);
    $sum     = 0;

    foreach ($weights as $index => $weight) {
        $sum += $weight;

        if ($random <= $sum) {
            return $array[$index];
        }
    }
}

?>

I know opinions are split between avoiding private methods in controllers and exploding long actions into smaller chunks of code. I went for the latter, but feel free to correct me if you feel like it’s a mistake. I’m no PHP developer. ;)

我知道在避免使用控制器中的私有方法和将长操作分解为较小的代码块之间存在意见分歧。 我选择了后者,但是如果您觉得这是一个错误,请随时纠正我。 我不是PHP开发人员。 ;)

风景 (The View)

At this point, our controller is done. We only have to deal with the view. We have a little bit of logic in the templates but it actually makes sense since it’s strictly about how to display the content, so that shouldn’t be too much.

至此,我们的控制器已完成。 我们只需要处理这个观点。 我们在模板中有一点逻辑,但是实际上是有道理的,因为这完全是关于如何显示内容的,所以不应该太多。

The main idea is: either the data key from content contains several items, in which case we output a slider (Bootstrap carousel in our case), or it has a single item so we output only one. In either case, we don’t output an item directly; we include a partial that deals with type checking in case something is wrong, and redirects to the appropriate partial. But let’s start at the beginning.

主要思想是: content中的data键包含多个项目,在这种情况下,我们输出一个滑块(在本例中为Bootstrap旋转木马),或者它只有一个项目,因此仅输出一个。 无论哪种情况,我们都不直接输出项目。 我们包括一个用于处理类型检查的部分,以防万一出了问题,然后重定向到适当的部分。 但是,让我们从头开始。

{# If there are several items to display #}
{% if content.data|length > 1 %}
  {# Output a carousel #}
  <div class="carousel  slide" data-ride="carousel" data-interval="3000">
    {# Carousel indicators #}
    {% for i in 0..(content.data|length)-1 %}
    {% if loop.first %}
    <ol class="carousel-indicators">
    {% endif %}
      <li data-target=".carousel" data-slide-to="{{ i }}" {% if loop.first %}class="active"{% endif %}></li>
    {% if loop.last %}
    </ol>
    {% endif %}
    {% endfor %}

    {# Carousel items #}
    {% for item in content.data %}
    {% if loop.first %}
    <div class="carousel-inner">
    {% endif %}
      <div class="item{% if loop.first %}  active{% endif %}">
        {% include '@Front/AdsManager/_type.html.twig' with {
          type: item.type, 
          item: item
        } %}
      </div>
    {% if loop.last %}
    </div>
    {% endif %}
    {% endfor %}
  </div>

{# If there is a single item, include it #}
{% else %}

  {% include '@Front/AdsManager/_type.html.twig' with {
    type: (content.data|first).type, 
    item: (content.data|first)
  } %}

{% endif %}

Let’s see what the _type partial looks like:

让我们看看_type部分的样子:

{# If type is allowed, include the relevant partial #}
{% if type|lower in allowed_type %}
  {% include '@Front/AdsManager/_' ~ type ~ '.html.twig' with { item: item } %}
{# Else print an error #}
{% else %}
  <p>Unknown type <code>{{ type }}</code> for id <code>{{ id }}</code>.</p>
{% endif %}

Last, but not least, our partials for specific types:

最后但并非最不重要的一点是,我们的特定类型的局部函数:

{# _image.html.twig #}
<div class="epub">
  <a href="{{ item.target|default('#') }}" class="epub__link">
    <img src="{{ item.link|default('http://cdn.domain.tld/path/to/default.png') }}" 
         alt="{{ item.description|default('Default description') }}" 
         class="epub__image" />
  </a>
</div>
{# _video.html.twig #}
{% if item.link %}
<div class="video-wrapper">
  <iframe src="{{ item.link }}" frameborder="0" allowfullscreen></iframe>
</div>
{% endif %}
{# _html.html.twig #}
{{ item.content|default('') }}

最后的想法 (Final thoughts)

That’s it! Wasn’t that hard in the end, was it? Yet, it is both a simple and powerful way to manage ads when you cannot rely on third-party services. There is always room for improvement, so feel free to suggest updates and tweaks.

而已! 最后不是那么难吗? 但是,当您不能依赖第三方服务时,它既是一种简单强大的管理广告的方式。 总是有改进的空间,请随时提出更新和调整建议。

Cheers!

干杯!

翻译自: https://www.sitepoint.com/building-ad-manager-symfony-2/

symfony 控制器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值