Browse Source

Add list of dependents, fixes #134

Jordi Boggiano 9 years ago
parent
commit
6ce98e34e7

+ 18 - 0
src/Packagist/WebBundle/Controller/Controller.php

@@ -13,6 +13,8 @@
 namespace Packagist\WebBundle\Controller;
 
 use Symfony\Bundle\FrameworkBundle\Controller\Controller as BaseController;
+use Pagerfanta\Adapter\DoctrineORMAdapter;
+use Pagerfanta\Pagerfanta;
 
 /**
  * @author Jordi Boggiano <j.boggiano@seld.be>
@@ -56,4 +58,20 @@ class Controller extends BaseController
             );
         } catch (\Predis\Connection\ConnectionException $e) {}
     }
+
+    /**
+     * Initializes the pager for a query.
+     *
+     * @param \Doctrine\ORM\QueryBuilder $query Query for packages
+     * @param int                        $page  Pagenumber to retrieve.
+     * @return \Pagerfanta\Pagerfanta
+     */
+    protected function setupPager($query, $page)
+    {
+        $paginator = new Pagerfanta(new DoctrineORMAdapter($query, true));
+        $paginator->setMaxPerPage(15);
+        $paginator->setCurrentPage($page, false, true);
+
+        return $paginator;
+    }
 }

+ 30 - 0
src/Packagist/WebBundle/Controller/PackageController.php

@@ -15,6 +15,8 @@ use DateTimeImmutable;
 
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+use Pagerfanta\Adapter\FixedAdapter;
+use Pagerfanta\Pagerfanta;
 
 class PackageController extends Controller
 {
@@ -161,6 +163,34 @@ class PackageController extends Controller
         return $data;
     }
 
+    /**
+     * @Route(
+     *      "/packages/{name}/dependents",
+     *      name="view_package_dependents",
+     *      requirements={"name"="[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?"}
+     * )
+     * @Template()
+     */
+    public function dependentsAction(Request $req, $name)
+    {
+        $page = $req->query->get('page', 1);
+
+        $repo = $this->getDoctrine()->getRepository('PackagistWebBundle:Package');
+        $depCount = $repo->getDependentCount($name);
+        $packages = $repo->getDependents($name, ($page - 1) * 15, 15);
+
+        $paginator = new Pagerfanta(new FixedAdapter($depCount, $packages));
+        $paginator->setMaxPerPage(15);
+        $paginator->setCurrentPage($page, false, true);
+
+        $data['packages'] = $paginator;
+
+        $data['meta'] = $this->getPackagesMetadata($data['packages']);
+        $data['name'] = $name;
+
+        return $data;
+    }
+
     /**
      * @Route(
      *      "/packages/{name}/stats/all.json",

+ 2 - 16
src/Packagist/WebBundle/Controller/WebController.php

@@ -689,6 +689,8 @@ class WebController extends Controller
         } catch (ConnectionException $e) {
         }
 
+        $data['dependents'] = $repo->getDependentCount($package->getName());
+
         if ($maintainerForm = $this->createAddMaintainerForm($package)) {
             $data['addMaintainerForm'] = $maintainerForm->createView();
         }
@@ -1215,22 +1217,6 @@ class WebController extends Controller
         return $this->createFormBuilder(array())->getForm();
     }
 
-    /**
-     * Initializes the pager for a query.
-     *
-     * @param \Doctrine\ORM\QueryBuilder $query Query for packages
-     * @param int                        $page  Pagenumber to retrieve.
-     * @return \Pagerfanta\Pagerfanta
-     */
-    protected function setupPager($query, $page)
-    {
-        $paginator = new Pagerfanta(new DoctrineORMAdapter($query, true));
-        $paginator->setMaxPerPage(15);
-        $paginator->setCurrentPage($page, false, true);
-
-        return $paginator;
-    }
-
     /**
      * @param array $orderBys
      *

+ 4 - 2
src/Packagist/WebBundle/Entity/DevRequireLink.php

@@ -16,7 +16,9 @@ use Doctrine\ORM\Mapping as ORM;
 
 /**
  * @ORM\Entity
- * @ORM\Table(name="link_require_dev")
+ * @ORM\Table(name="link_require_dev", indexes={
+ *     @ORM\Index(name="package_name_idx",columns={"version_id", "packageName"})
+ * })
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
 class DevRequireLink extends PackageLink
@@ -25,4 +27,4 @@ class DevRequireLink extends PackageLink
      * @ORM\ManyToOne(targetEntity="Packagist\WebBundle\Entity\Version", inversedBy="devRequire")
      */
     protected $version;
-}
+}

+ 46 - 1
src/Packagist/WebBundle/Entity/PackageRepository.php

@@ -62,7 +62,6 @@ class PackageRepository extends EntityRepository
         $names = null;
         $apc = extension_loaded('apc');
 
-        // TODO use container to set caching key and ttl
         if ($apc) {
             $names = apc_fetch('packagist_package_names');
         }
@@ -311,6 +310,52 @@ class PackageRepository extends EntityRepository
         return true;
     }
 
+    public function getDependentCount($name)
+    {
+        $apc = extension_loaded('apc');
+
+        if ($apc) {
+            $count = apc_fetch('packagist_dependentsCount_'.$name);
+        }
+
+        if (!isset($count) || !is_int($count)) {
+            $count = $this->getEntityManager()->getConnection()->fetchColumn(
+                "SELECT COUNT(DISTINCT v.package_id)
+                FROM package_version v
+                LEFT JOIN link_require r ON v.id = r.version_id AND r.packageName = :name
+                LEFT JOIN link_require_dev rd ON v.id = rd.version_id AND rd.packageName = :name
+                WHERE v.development AND (r.packageName IS NOT NULL OR rd.packageName IS NOT NULL)",
+                ['name' => $name]
+            );
+
+            if ($apc) {
+                apc_store('packagist_dependentsCount_'.$name, $count, 7*86400);
+            }
+        }
+
+        return $count;
+    }
+
+    public function getDependents($name, $offset = 0, $limit = 15)
+    {
+        $qb = $this->getEntityManager()->createQueryBuilder();
+        $qb->select('p')
+            ->from('Packagist\WebBundle\Entity\Package', 'p')
+            ->leftJoin('p.versions', 'v')
+            ->leftJoin('v.devRequire', 'dr')
+            ->leftJoin('v.require', 'r')
+            ->where('v.development = true')
+            ->andWhere('(r.packageName = :name OR dr.packageName = :name)')
+            ->groupBy('p.id')
+            ->orderBy('p.name')
+            ->setParameter('name', $name);
+
+        return $qb->getQuery()
+            ->setMaxResults($limit)
+            ->setFirstResult($offset)
+            ->useResultCache(true, 7*86400, 'dependents_'.$name.'_'.$offset.'_'.$limit)
+            ->getResult();
+    }
 
     private function addFilters(QueryBuilder $qb, array $filters)
     {

+ 4 - 2
src/Packagist/WebBundle/Entity/RequireLink.php

@@ -16,7 +16,9 @@ use Doctrine\ORM\Mapping as ORM;
 
 /**
  * @ORM\Entity
- * @ORM\Table(name="link_require")
+ * @ORM\Table(name="link_require", indexes={
+ *     @ORM\Index(name="package_name_idx",columns={"version_id", "packageName"})
+ * })
  * @author Jordi Boggiano <j.boggiano@seld.be>
  */
 class RequireLink extends PackageLink
@@ -25,4 +27,4 @@ class RequireLink extends PackageLink
      * @ORM\ManyToOne(targetEntity="Packagist\WebBundle\Entity\Version", inversedBy="require")
      */
     protected $version;
-}
+}

+ 28 - 0
src/Packagist/WebBundle/Resources/views/Package/dependents.html.twig

@@ -0,0 +1,28 @@
+{% extends "PackagistWebBundle::layout.html.twig" %}
+
+{% set showSearchDesc = 'hide' %}
+
+{% block head_additions %}<meta name="robots" content="noindex, nofollow">{% endblock %}
+
+{% block title %}Dependent Packages - {{ name }} - {{ parent() }}{% endblock %}
+
+{% block content %}
+    <div class="row">
+        <div class="col-xs-12 package">
+            <div class="package-header">
+                <h2 class="title">
+                    <a href="{{ path("view_package", {name: name}) }}">{{ name }}</a> dependents
+                </h2>
+            </div>
+        </div>
+    </div>
+
+    <section class="row">
+        <section class="col-md-12">
+        {% embed "PackagistWebBundle:Web:list.html.twig" with {noLayout: 'true', showAutoUpdateWarning: false} %}
+            {% block content_title %}
+            {% endblock %}
+        {% endembed %}
+        </section>
+    </section>
+{% endblock %}

+ 4 - 2
src/Packagist/WebBundle/Resources/views/Package/stats.html.twig

@@ -2,14 +2,16 @@
 
 {% set showSearchDesc = 'hide' %}
 
+{% block head_additions %}<meta name="robots" content="noindex, nofollow">{% endblock %}
+
 {% block title %}Install Statistics - {{ package.name }} - {{ parent() }}{% endblock %}
 
 {% block content %}
     <div class="row">
-        <div class="col-xs-12 package"{% if app.user and package.crawledAt is null and (is_granted('ROLE_EDIT_PACKAGES') or package.maintainers.contains(app.user)) %} data-force-crawl="true"{% endif %}>
+        <div class="col-xs-12 package">
             <div class="package-header">
                 <h2 class="title">
-                    <a href="{{ path("view_vendor", {"vendor": package.vendor}) }}">{{ package.vendor }}/</a><a href="{{ path("view_package", {name: package.name}) }}">{{ package.packageName }}</a> install statistics
+                    <a href="{{ path("view_package", {name: package.name}) }}">{{ package.name }}</a> install statistics
                 </h2>
             </div>
         </div>

+ 3 - 3
src/Packagist/WebBundle/Resources/views/Web/viewPackage.html.twig

@@ -142,16 +142,16 @@
                             {% if version and version.support.docs is defined %}
                                 <p><a href="{{ version.support.docs }}">Documentation</a></p>
                             {% endif %}
-                            <p><a href="{{ path('view_package_stats', {name: package.name}) }}">Statistics</a></p>
                         </div>
 
                         <div class="facts col-xs-12 col-sm-6 col-md-12">
-                            <p><span>Installs:</span> {% if downloads.total is defined %}{{ downloads.total|number_format(0, '.', '&#8201;')|raw }}{% else %}N/A{% endif %}</p>
-                            {% if package.language is not empty %}<p><span>Language:</span> {{ package.language }}</p>{% endif %}
+                            <p><span><a href="{{ path('view_package_stats', {name: package.name}) }}" rel="nofollow">Installs</a>:</span> {% if downloads.total is defined %}{{ downloads.total|number_format(0, '.', '&#8201;')|raw }}{% else %}N/A{% endif %}</p>
+                            <p><span><a href="{{ path('view_package_dependents', {name: package.name}) }}" rel="nofollow">Dependents</a>:</span> {{ dependents|default(38429)|number_format(0, '.', '&#8201;')|raw }}</p>
                             {% if package.gitHubStars is not null %}<p><span>Stars:</span> {{ package.gitHubStars|number_format(0, '.', '&#8201;')|raw }}</p>{% endif %}
                             {% if package.gitHubWatches is not null %}<p><span>Watches:</span> {{ package.gitHubWatches|number_format(0, '.', '&#8201;')|raw }}</p>{% endif %}
                             {% if package.gitHubForks is not null %}<p><span>Forks:</span> {{ package.gitHubForks|number_format(0, '.', '&#8201;')|raw }}</p>{% endif %}
                             {% if package.gitHubOpenIssues is not null %}<p><span>Open Issues:</span> {{ package.gitHubOpenIssues|number_format(0, '.', '&#8201;')|raw }}</p>{% endif %}
+                            {% if package.language is not empty %}<p><span>Language:</span> {{ package.language }}</p>{% endif %}
                         </div>
                     </div>
                 </div>